1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* This file is part of Phiremock. |
4
|
|
|
* |
5
|
|
|
* Phiremock is free software: you can redistribute it and/or modify |
6
|
|
|
* it under the terms of the GNU Lesser General Public License as published by |
7
|
|
|
* the Free Software Foundation, either version 3 of the License, or |
8
|
|
|
* (at your option) any later version. |
9
|
|
|
* |
10
|
|
|
* Phiremock is distributed in the hope that it will be useful, |
11
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
12
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13
|
|
|
* GNU General Public License for more details. |
14
|
|
|
* |
15
|
|
|
* You should have received a copy of the GNU General Public License |
16
|
|
|
* along with Phiremock. If not, see <http://www.gnu.org/licenses/>. |
17
|
|
|
*/ |
18
|
|
|
|
19
|
|
|
namespace Mcustiel\Phiremock\Server\Http\Implementation; |
20
|
|
|
|
21
|
|
|
use Mcustiel\Phiremock\Common\StringStream; |
22
|
|
|
use Mcustiel\Phiremock\Server\Cli\Options\HostInterface; |
23
|
|
|
use Mcustiel\Phiremock\Server\Cli\Options\Port; |
24
|
|
|
use Mcustiel\Phiremock\Server\Cli\Options\SecureOptions; |
25
|
|
|
use Mcustiel\Phiremock\Server\Http\RequestHandlerInterface; |
26
|
|
|
use Mcustiel\Phiremock\Server\Http\ServerInterface; |
27
|
|
|
use Psr\Http\Message\ResponseInterface; |
28
|
|
|
use Psr\Http\Message\ServerRequestInterface; |
29
|
|
|
use Psr\Log\LoggerInterface; |
30
|
|
|
use React\EventLoop\Factory as EventLoop; |
31
|
|
|
use React\EventLoop\LoopInterface; |
32
|
|
|
use React\Http\Middleware\RequestBodyBufferMiddleware; |
33
|
|
|
use React\Http\Middleware\RequestBodyParserMiddleware; |
34
|
|
|
use React\Http\Middleware\StreamingRequestMiddleware; |
35
|
|
|
use React\Http\Server; |
36
|
|
|
use React\Socket\Server as ReactSocket; |
37
|
|
|
|
38
|
|
|
class ReactPhpServer implements ServerInterface |
39
|
|
|
{ |
40
|
|
|
/** @var RequestHandlerInterface */ |
41
|
|
|
private $requestHandler; |
42
|
|
|
|
43
|
|
|
/** @var LoopInterface */ |
44
|
|
|
private $loop; |
45
|
|
|
|
46
|
|
|
/** @var ReactSocket */ |
47
|
|
|
private $socket; |
48
|
|
|
|
49
|
|
|
/** @var Server */ |
50
|
|
|
private $http; |
51
|
|
|
|
52
|
|
|
/** @var LoggerInterface */ |
53
|
|
|
private $logger; |
54
|
|
|
|
55
|
|
|
public function __construct(RequestHandlerInterface $requestHandler, LoggerInterface $logger) |
56
|
|
|
{ |
57
|
|
|
$this->loop = EventLoop::create(); |
|
|
|
|
58
|
|
|
$this->logger = $logger; |
59
|
|
|
$this->requestHandler = $requestHandler; |
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
public function listen(HostInterface $host, Port $port, ?SecureOptions $secureOptions): void |
63
|
|
|
{ |
64
|
|
|
$this->http = new Server( |
|
|
|
|
65
|
|
|
$this->loop, |
66
|
|
|
new StreamingRequestMiddleware(), |
67
|
|
|
new RequestBodyBufferMiddleware(), |
68
|
|
|
new RequestBodyParserMiddleware(), |
69
|
|
|
function (ServerRequestInterface $request) { |
70
|
|
|
return $this->onRequest($request); |
71
|
|
|
} |
72
|
|
|
); |
73
|
|
|
|
74
|
|
|
$listenConfig = "{$host->asString()}:{$port->asInt()}"; |
75
|
|
|
$this->initSocket($listenConfig, $secureOptions); |
76
|
|
|
$this->http->listen($this->socket); |
77
|
|
|
|
78
|
|
|
// Dispatch pending signals periodically |
79
|
|
|
if (\function_exists('pcntl_signal_dispatch')) { |
80
|
|
|
$this->loop->addPeriodicTimer(0.5, function () { |
81
|
|
|
pcntl_signal_dispatch(); |
82
|
|
|
}); |
83
|
|
|
} |
84
|
|
|
$this->loop->run(); |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
public function shutdown(): void |
88
|
|
|
{ |
89
|
|
|
$this->http->removeAllListeners(); |
90
|
|
|
$this->socket->close(); |
91
|
|
|
$this->loop->stop(); |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
private function onRequest(ServerRequestInterface $request): ResponseInterface |
95
|
|
|
{ |
96
|
|
|
$start = microtime(true); |
97
|
|
|
|
98
|
|
|
// TODO: Remove this patch if ReactPHP is fixed |
99
|
|
|
if ($request->getParsedBody() !== null) { |
100
|
|
|
$request = $request->withBody(new StringStream(http_build_query($request->getParsedBody()))); |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
$psrResponse = $this->requestHandler->dispatch(new ServerRequestWithCachedBody($request)); |
104
|
|
|
$this->logger->debug('Processing took ' . number_format((microtime(true) - $start) * 1000, 3) . ' milliseconds'); |
105
|
|
|
|
106
|
|
|
return $psrResponse; |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
private function initSocket(string $listenConfig, ?SecureOptions $secureOptions): void |
110
|
|
|
{ |
111
|
|
|
$this->logger->info( |
112
|
|
|
sprintf( |
113
|
|
|
'Phiremock http server listening on %s over %s', |
114
|
|
|
$listenConfig, |
115
|
|
|
null === $secureOptions ? 'http' : 'https' |
116
|
|
|
) |
117
|
|
|
); |
118
|
|
|
$context = []; |
119
|
|
|
if ($secureOptions !== null) { |
120
|
|
|
$tlsContext = []; |
121
|
|
|
$listenConfig = sprintf('tls://%s', $listenConfig); |
122
|
|
|
$tlsContext['local_cert'] = $secureOptions->getCertificate()->asString(); |
123
|
|
|
$tlsContext['local_pk'] = $secureOptions->getCertificateKey()->asString(); |
124
|
|
|
if ($secureOptions->hasPassphrase()) { |
125
|
|
|
$tlsContext['passphrase'] = $secureOptions->getPassphrase()->asString(); |
126
|
|
|
} |
127
|
|
|
$context['tls'] = $tlsContext; |
128
|
|
|
} |
129
|
|
|
$this->socket = new ReactSocket($listenConfig, $this->loop, $context); |
|
|
|
|
130
|
|
|
} |
131
|
|
|
} |
132
|
|
|
|
This function has been deprecated. The supplier of the function has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.