Passed
Push — master ( 0b7dea...b9611f )
by Mariano
04:34
created

ReactPhpServer   A

Complexity

Total Complexity 10

Size/Duplication

Total Lines 89
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 44
c 2
b 0
f 0
dl 0
loc 89
rs 10
wmc 10

5 Methods

Rating   Name   Duplication   Size   Complexity  
A shutdown() 0 5 1
A initSocket() 0 21 4
A listen() 0 20 2
A __construct() 0 5 1
A onRequest() 0 13 2
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\Server;
33
use React\Socket\Server as ReactSocket;
34
35
class ReactPhpServer implements ServerInterface
36
{
37
    /** @var RequestHandlerInterface */
38
    private $requestHandler;
39
40
    /** @var LoopInterface */
41
    private $loop;
42
43
    /** @var ReactSocket */
44
    private $socket;
45
46
    /** @var Server */
47
    private $http;
48
49
    /** @var LoggerInterface */
50
    private $logger;
51
52
    public function __construct(RequestHandlerInterface $requestHandler, LoggerInterface $logger)
53
    {
54
        $this->loop = EventLoop::create();
55
        $this->logger = $logger;
56
        $this->requestHandler = $requestHandler;
57
    }
58
59
    public function listen(HostInterface $host, Port $port, ?SecureOptions $secureOptions): void
60
    {
61
        $this->http = new Server(
62
            $this->loop,
63
            function (ServerRequestInterface $request) {
64
                return $this->onRequest($request);
65
            }
66
        );
67
68
        $listenConfig = "{$host->asString()}:{$port->asInt()}";
69
        $this->initSocket($listenConfig, $secureOptions);
70
        $this->http->listen($this->socket);
71
72
        // Dispatch pending signals periodically
73
        if (function_exists('pcntl_signal_dispatch')) {
74
            $this->loop->addPeriodicTimer(0.5, function () {
75
                pcntl_signal_dispatch();
76
            });
77
        }
78
        $this->loop->run();
79
    }
80
81
    public function shutdown(): void
82
    {
83
        $this->http->removeAllListeners();
84
        $this->socket->close();
85
        $this->loop->stop();
86
    }
87
88
    private function onRequest(ServerRequestInterface $request): ResponseInterface
89
    {
90
        $start = microtime(true);
91
92
        // TODO: Remove this patch if ReactPHP is fixed
93
        if ($request->getParsedBody() !== null) {
94
            $request = $request->withBody(new StringStream(http_build_query($request->getParsedBody())));
95
        }
96
97
        $psrResponse = $this->requestHandler->dispatch(new ServerRequestWithCachedBody($request));
98
        $this->logger->debug('Processing took ' . number_format((microtime(true) - $start) * 1000, 3) . ' milliseconds');
99
100
        return $psrResponse;
101
    }
102
103
    private function initSocket(string $listenConfig, ?SecureOptions $secureOptions): void
104
    {
105
        $this->logger->info(
106
            sprintf(
107
                'Phiremock http server listening on %s over %s',
108
                $listenConfig,
109
                null === $secureOptions ? 'http' : 'https'
110
            )
111
        );
112
        $context = [];
113
        if ($secureOptions !== null) {
114
            $tlsContext = [];
115
            $listenConfig = sprintf('tls://%s', $listenConfig);
116
            $tlsContext['local_cert'] = $secureOptions->getCertificate()->asString();
117
            $tlsContext['local_pk'] = $secureOptions->getCertificateKey()->asString();
118
            if ($secureOptions->hasPassphrase()) {
119
                $tlsContext['passphrase'] = $secureOptions->getPassphrase()->asString();
120
            }
121
            $context['tls'] = $tlsContext;
122
        }
123
        $this->socket = new ReactSocket($listenConfig, $this->loop, $context);
124
    }
125
}
126