-
-
Notifications
You must be signed in to change notification settings - Fork 377
Expand file tree
/
Copy pathServer.php
More file actions
201 lines (189 loc) · 6.56 KB
/
Server.php
File metadata and controls
201 lines (189 loc) · 6.56 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
<?php declare(strict_types=1);
/**
* Copyright (c) 2011-2018 Michael Dowling, https://github.com/mtdowling <mtdowling@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
namespace Bref\Test;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
/**
* The Server class is used to control a scripted webserver using node.js that
* will respond to HTTP requests with queued responses.
*
* Queued responses will be served to requests using a FIFO order. All requests
* received by the server are stored on the node.js server and can be retrieved
* by calling {@see Server::received()}.
*
* Mock responses that don't require data to be transmitted over HTTP a great
* for testing. Mock response, however, cannot test the actual sending of an
* HTTP request using cURL. This test server allows the simulation of any
* number of HTTP request response transactions to test the actual sending of
* requests over the wire without having to leave an internal network.
*/
class Server
{
/** @var Client|null */
private static $client;
/** @var bool */
private static $started = false;
/** @var string */
public static $url = 'http://127.0.0.1:8126/';
/** @var int */
public static $port = 8126;
/**
* Flush the received requests from the server
*
* @throws \RuntimeException
*/
public static function flush(): ResponseInterface
{
return self::getClient()->request('DELETE', 'guzzle-server/requests');
}
/**
* Queue an array of responses or a single response on the server.
*
* Any currently queued responses will be overwritten. Subsequent requests
* on the server will return queued responses in FIFO order.
*
* @param array|ResponseInterface $responses A single or array of Responses
* to queue.
* @throws \Exception
*/
public static function enqueue($responses)
{
$data = [];
foreach ((array) $responses as $response) {
if (! ($response instanceof ResponseInterface)) {
throw new \Exception('Invalid response given.');
}
$headers = array_map(function ($h) {
return implode(' ,', $h);
}, $response->getHeaders());
$data[] = [
'status' => (string) $response->getStatusCode(),
'reason' => $response->getReasonPhrase(),
'headers' => $headers,
'body' => base64_encode((string) $response->getBody()),
];
}
self::getClient()->request('PUT', 'guzzle-server/responses', [
'json' => $data,
]);
}
/**
* Get all of the received requests
*
* @return RequestInterface[]
* @throws \RuntimeException
*/
public static function received()
{
if (! self::$started) {
return [];
}
$response = self::getClient()->request('GET', 'guzzle-server/requests');
$data = json_decode((string) $response->getBody(), true);
return array_map(
function ($message) {
$uri = $message['uri'];
if (isset($message['query_string'])) {
$uri .= '?' . $message['query_string'];
}
$response = new Psr7\Request(
$message['http_method'],
$uri,
$message['headers'],
$message['body'],
$message['version']
);
return $response->withUri(
$response->getUri()
->withScheme('http')
->withHost($response->getHeaderLine('host'))
);
},
$data
);
}
/**
* Stop running the node.js server
*/
public static function stop()
{
if (self::$started) {
self::getClient()->request('DELETE', 'guzzle-server');
}
$tries = 0;
while (self::isListening() && ++$tries < 5) {
usleep(100000);
}
if (self::isListening()) {
throw new \RuntimeException('Unable to stop node.js server');
}
self::$started = false;
}
public static function wait(int $maxTries = 5)
{
$tries = 0;
while (! self::isListening() && ++$tries < $maxTries) {
usleep(100000);
}
if (! self::isListening()) {
throw new \RuntimeException('Unable to contact node.js server');
}
}
public static function start()
{
if (self::$started) {
return;
}
if (self::isListening()) {
throw new \Exception('Server is already running');
}
exec('node ' . __DIR__ . '/server.js '
. self::$port . ' >> /tmp/server.log 2>&1 &');
self::wait();
self::$started = true;
}
private static function isListening(): bool
{
try {
self::getClient()->request('GET', 'guzzle-server/perf', [
'connect_timeout' => 5,
'timeout' => 5,
]);
return true;
} catch (\Throwable $e) {
return false;
}
}
private static function getClient(): Client
{
if (! self::$client) {
self::$client = new Client([
'base_uri' => self::$url,
'sync' => true,
]);
}
return self::$client;
}
}