Completed
Push — master ( 4c74f5...2b33f7 )
by Aurimas
04:08
created

HttpServer::create()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2
Metric Value
dl 0
loc 4
ccs 0
cts 4
cp 0
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 2
1
<?php
2
3
namespace Thruster\Component\HttpServer;
4
5
use GuzzleHttp\Psr7\Response;
6
use Psr\Http\Message\ResponseInterface;
7
use Psr\Http\Message\ServerRequestInterface;
8
use Thruster\Component\Socket\Server;
9
use Thruster\Component\Socket\Connection;
10
use Thruster\Component\Socket\ServerInterface;
11
use Thruster\Component\Socket\ConnectionInterface;
12
use Thruster\Component\Promise\PromiseInterface;
13
use Thruster\Component\HttpServer\ResponseModifier\DateModifier;
14
use Thruster\Component\HttpServer\ResponseModifier\PoweredByModifier;
15
use Thruster\Component\ServerApplication\ServerApplicationInterface;
16
17
/**
18
 * Class HttpServer
19
 *
20
 * @package Thruster\Component\HttpServer
21
 * @author  Aurimas Niekis <[email protected]>
22
 */
23
class HttpServer
24
{
25
    const VERSION = '0.2';
26
27
    /**
28
     * @var ServerApplicationInterface
29
     */
30
    private $application;
31
32
    /**
33
     * @var Server[]
34
     */
35
    private $servers;
36
37
    /**
38
     * @var bool
39
     */
40
    private $debug;
41
42
    /**
43
     * @var ResponseModifierInterface[]
44
     */
45
    private $responseModifiers;
46
47
    /**
48
     * @var int
49
     */
50
    private $servedRequests;
51
52
    /**
53
     * @var int
54
     */
55
    private $failedRequests;
56
57
    /**
58
     * @var int
59
     */
60
    private $upTime;
61
62
    private function __construct(ServerApplicationInterface $application)
63
    {
64
        $this->application    = $application;
65
        $this->servers        = [];
66
        $this->debug          = false;
67
        $this->servedRequests = 0;
68
        $this->failedRequests = 0;
69
        $this->upTime         = time();
70
71
        $this->responseModifiers = [
72
            new DateModifier(),
73
            new PoweredByModifier('Thruster/' . static::VERSION),
74
        ];
75
    }
76
77
    public static function create(ServerApplicationInterface $application) : self
78
    {
79
        return new static($application);
80
    }
81
82
    public function attachTo(ServerInterface $server) : self
83
    {
84
        if (false !== array_search($server, $this->servers, true)) {
85
            return $this;
86
        }
87
88
        $this->servers[] = $server;
89
        $this->listenTo($server);
90
91
        return $this;
92
    }
93
94
    public function withResponseModifier(ResponseModifierInterface $modifier) : self
95
    {
96
        if (false !== array_search($modifier, $this->responseModifiers, true)) {
97
            return $this;
98
        }
99
100
        $this->responseModifiers[] = $modifier;
101
102
        return $this;
103
    }
104
105
    public function enableDebug() : self
106
    {
107
        $this->debug = true;
108
109
        return $this;
110
    }
111
112
    public function getStatistics() : array
113
    {
114
        return [
115
            'served_requests' => $this->servedRequests,
116
            'failed_requests' => $this->failedRequests,
117
            'up_time'         => (time() - $this->upTime),
118
        ];
119
    }
120
121
    private function listenTo(ServerInterface $server, array $options = [])
122
    {
123
        $server->on(
124
            'connection',
125
            function (ConnectionInterface $connection) use ($options) {
126
                $request = new Request($connection, $options);
127
128
                $request->on(
129
                    'received_head',
130
                    function ($headers, $httpMethod, $uri, $protocolVersion) use ($request, $connection) {
131
                        $this->servedRequests++;
132
133
                        $response = $this->application->processHead($headers, $httpMethod, $uri, $protocolVersion);
134
135
                        if (null !== $response) {
136
                            $connection->removeListener('data', [$request, 'feed']);
137
138
                            $response = $this->modifyResponse($response);
139
140
                            $request->sendResponse($response);
141
                        }
142
                    }
143
                );
144
145
                $request->on(
146
                    'request',
147
                    function (ServerRequestInterface $serverRequest) use ($request, $connection) {
148
149
                        $connection->removeListener('data', [$request, 'feed']);
150
151
                        $fulfilled = function (ResponseInterface $response) use ($request) {
152
                            $response = $this->modifyResponse($response);
153
                            $request->sendResponse($response);
154
                        };
155
156
                        $rejected = function (\Throwable $exception) use ($request) {
157
                            $this->failedRequests++;
158
159
                            $response = new Response(500);
160
                            if (true === $this->debug) {
161
                                $error = [
162
                                    'class' => get_class($exception),
163
                                    'message' => $exception->getMessage(),
164
                                    'code' => $exception->getCode(),
165
                                    'file' => $exception->getFile(),
166
                                    'line' => $exception->getLine()
167
                                ];
168
169
                                $response = $response->withHeader('Content-Type', 'application/json');
170
                                $response->getBody()->write(json_encode($error));
171
                            }
172
173
                            $response = $this->modifyResponse($response);
174
                            $request->sendResponse($response);
175
                        };
176
177
                        $this->application->processRequest($serverRequest)->done($fulfilled, $rejected);
178
                    }
179
                );
180
181
                $connection->on('data', [$request, 'feed']);
182
            }
183
        );
184
    }
185
186
    private function modifyResponse(ResponseInterface $response) : ResponseInterface
187
    {
188
        foreach ($this->responseModifiers as $responseModifier) {
189
            $response = $responseModifier->modify($response);
190
        }
191
192
        return $response;
193
    }
194
}
195