Skip to content

Commit 50c9c6f

Browse files
committed
Init
0 parents  commit 50c9c6f

File tree

8 files changed

+539
-0
lines changed

8 files changed

+539
-0
lines changed

.editorconfig

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# EditorConfig is awesome: https://editorconfig.org/
2+
3+
# top-most EditorConfig file
4+
root = true
5+
6+
# Unix-style newlines with a newline ending for every file
7+
# Indent with 4 spaces
8+
[*]
9+
charset = utf-8
10+
end_of_line = lf
11+
indent_style = space
12+
indent_size = 4
13+
insert_final_newline = true
14+
trim_trailing_whitespace = true
15+
16+
[Makefile]
17+
indent_style = tab

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
composer.lock
2+
vendor/
3+
/phpunit.xml

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Change Log
2+
3+
## 0.1.0
4+
5+
First release extracted from Guzzle v7.3.

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2011 Michael Dowling <mtdowling@gmail.com>
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in
13+
all copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
THE SOFTWARE.

README.md

Whitespace-only changes.

composer.json

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"name": "guzzlehttp/test-server",
3+
"description": "Node.js server and PHP controller",
4+
"keywords": [],
5+
"license": "MIT",
6+
"authors": [
7+
{
8+
"name": "Michael Dowling",
9+
"email": "mtdowling@gmail.com"
10+
}
11+
],
12+
"require": {
13+
"php": "^7.2.5 || ^8.0",
14+
"guzzlehttp/guzzle": "^7.3"
15+
},
16+
"config": {
17+
"preferred-install": "dist",
18+
"sort-packages": true
19+
},
20+
"autoload": {
21+
"psr-4": {
22+
"GuzzleHttp\\Server\\": "src/"
23+
}
24+
}
25+
}

src/Server.php

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
<?php
2+
3+
namespace GuzzleHttp\Server;
4+
5+
use GuzzleHttp\Client;
6+
use GuzzleHttp\Psr7;
7+
use Psr\Http\Message\RequestInterface;
8+
use Psr\Http\Message\ResponseInterface;
9+
10+
/**
11+
* The Server class is used to control a scripted webserver using node.js that
12+
* will respond to HTTP requests with queued responses.
13+
*
14+
* Queued responses will be served to requests using a FIFO order. All requests
15+
* received by the server are stored on the node.js server and can be retrieved
16+
* by calling {@see Server::received()}.
17+
*
18+
* Mock responses that don't require data to be transmitted over HTTP a great
19+
* for testing. Mock response, however, cannot test the actual sending of an
20+
* HTTP request using cURL. This test server allows the simulation of any
21+
* number of HTTP request response transactions to test the actual sending of
22+
* requests over the wire without having to leave an internal network.
23+
*/
24+
class Server
25+
{
26+
/**
27+
* @var Client
28+
*/
29+
private static $client;
30+
private static $started = false;
31+
public static $url = 'http://127.0.0.1:8126/';
32+
public static $port = 8126;
33+
34+
/**
35+
* Flush the received requests from the server
36+
*
37+
* @throws \RuntimeException
38+
*/
39+
public static function flush()
40+
{
41+
return self::getClient()->request('DELETE', 'guzzle-server/requests');
42+
}
43+
44+
/**
45+
* Queue an array of responses or a single response on the server.
46+
*
47+
* Any currently queued responses will be overwritten. Subsequent requests
48+
* on the server will return queued responses in FIFO order.
49+
*
50+
* @param array|ResponseInterface $responses A single or array of Responses
51+
* to queue.
52+
*
53+
* @throws \Exception
54+
*/
55+
public static function enqueue($responses)
56+
{
57+
$data = [];
58+
foreach ((array) $responses as $response) {
59+
if (!($response instanceof ResponseInterface)) {
60+
throw new \Exception('Invalid response given.');
61+
}
62+
$headers = \array_map(static function ($h) {
63+
return \implode(' ,', $h);
64+
}, $response->getHeaders());
65+
66+
$data[] = [
67+
'status' => (string) $response->getStatusCode(),
68+
'reason' => $response->getReasonPhrase(),
69+
'headers' => $headers,
70+
'body' => \base64_encode((string) $response->getBody())
71+
];
72+
}
73+
74+
self::getClient()->request('PUT', 'guzzle-server/responses', [
75+
'json' => $data
76+
]);
77+
}
78+
79+
/**
80+
* Queue a single raw response manually, to handle cases where PSR7 response is not suitable.
81+
*
82+
* @param int|string $statusCode Status code for the response, e.g. 200
83+
* @param string $reasonPhrase Status reason response e.g "OK"
84+
* @param array $headers Array of headers to send in response
85+
* @param string|null $body Body to send in response
86+
*
87+
* @throws \GuzzleHttp\Exception\GuzzleException
88+
*/
89+
public static function enqueueRaw($statusCode, $reasonPhrase, $headers, $body)
90+
{
91+
$data = [
92+
[
93+
'status' => (string) $statusCode,
94+
'reason' => $reasonPhrase,
95+
'headers' => $headers,
96+
'body' => \base64_encode((string) $body)
97+
]
98+
];
99+
100+
self::getClient()->request('PUT', 'guzzle-server/responses', [
101+
'json' => $data
102+
]);
103+
}
104+
105+
/**
106+
* Get all of the received requests
107+
*
108+
* @return RequestInterface[]
109+
*
110+
* @throws \RuntimeException
111+
*/
112+
public static function received()
113+
{
114+
if (!self::$started) {
115+
return [];
116+
}
117+
118+
$response = self::getClient()->request('GET', 'guzzle-server/requests');
119+
$data = \json_decode($response->getBody(), true);
120+
121+
return \array_map(
122+
static function ($message) {
123+
$uri = $message['uri'];
124+
if (isset($message['query_string'])) {
125+
$uri .= '?' . $message['query_string'];
126+
}
127+
$response = new Psr7\Request(
128+
$message['http_method'],
129+
$uri,
130+
$message['headers'],
131+
$message['body'],
132+
$message['version']
133+
);
134+
return $response->withUri(
135+
$response->getUri()
136+
->withScheme('http')
137+
->withHost($response->getHeaderLine('host'))
138+
);
139+
},
140+
$data
141+
);
142+
}
143+
144+
/**
145+
* Stop running the node.js server
146+
*/
147+
public static function stop()
148+
{
149+
if (self::$started) {
150+
self::getClient()->request('DELETE', 'guzzle-server');
151+
}
152+
153+
self::$started = false;
154+
}
155+
156+
public static function wait($maxTries = 5)
157+
{
158+
$tries = 0;
159+
while (!self::isListening() && ++$tries < $maxTries) {
160+
\usleep(100000);
161+
}
162+
163+
if (!self::isListening()) {
164+
throw new \RuntimeException('Unable to contact node.js server');
165+
}
166+
}
167+
168+
public static function start()
169+
{
170+
if (self::$started) {
171+
return;
172+
}
173+
174+
if (!self::isListening()) {
175+
\exec('node ' . __DIR__ . '/server.js '
176+
. self::$port . ' >> /tmp/server.log 2>&1 &');
177+
self::wait();
178+
}
179+
180+
self::$started = true;
181+
}
182+
183+
private static function isListening()
184+
{
185+
try {
186+
self::getClient()->request('GET', 'guzzle-server/perf', [
187+
'connect_timeout' => 5,
188+
'timeout' => 5
189+
]);
190+
return true;
191+
} catch (\Exception $e) {
192+
return false;
193+
}
194+
}
195+
196+
private static function getClient()
197+
{
198+
if (!self::$client) {
199+
self::$client = new Client([
200+
'base_uri' => self::$url,
201+
'sync' => true,
202+
]);
203+
}
204+
205+
return self::$client;
206+
}
207+
}

0 commit comments

Comments
 (0)