Response::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 1
1
<?php
2
/**
3
 * This file is a part of a nekland library
4
 *
5
 * (c) Nekland <[email protected]>
6
 *
7
 * For the full license, take a look to the LICENSE file
8
 * on the root directory of this project
9
 */
10
11
namespace Nekland\Woketo\Http;
12
13
14
use Nekland\Woketo\Exception\Http\HttpException;
15
use Nekland\Woketo\Exception\Http\IncompleteHttpMessageException;
16
use React\Socket\ConnectionInterface;
17
18
/**
19
 * Class Response
20
 *
21
 * @internal
22
 */
23
class Response extends AbstractHttpMessage
24
{
25
    const SWITCHING_PROTOCOLS = '101 Switching Protocols';
26
    const BAD_REQUEST = '400 Bad Request';
27
28
    /**
29
     * @var string For example "404 Not Found"
30
     */
31
    private $httpResponse;
32
33
    /**
34
     * @var int
35
     */
36
    private $statusCode;
37
38
    /**
39
     * @var string
40
     */
41
    private $reason;
42
43 16
    public function __construct()
44
    {
45 16
        $this->setHttpVersion('HTTP/1.1');
46 16
    }
47
48
    /**
49
     * @param string $httpResponse
50
     * @return Response
51
     */
52 10
    public function setHttpResponse($httpResponse)
53
    {
54 10
        $this->httpResponse = $httpResponse;
55
56 10
        return $this;
57
    }
58
59
    /**
60
     * @return int
61
     */
62 2
    public function getStatusCode(): int
63
    {
64 2
        return (int) $this->statusCode;
65
    }
66
67
    /**
68
     * @param int $statusCode
69
     * @return Response
70
     */
71 5
    public function setStatusCode(int $statusCode)
72
    {
73 5
        $this->statusCode = $statusCode;
74
75 5
        return $this;
76
    }
77
78
    /**
79
     * @return string
80
     */
81 2
    public function getReason(): string
82
    {
83 2
        return $this->reason;
84
    }
85
86
    /**
87
     * @param string $reason
88
     * @return Response
89
     */
90 5
    public function setReason(string $reason)
91
    {
92 5
        $this->reason = $reason;
93
94 5
        return $this;
95
    }
96
97
    /**
98
     * @return string
99
     */
100 2
    public function getAcceptKey()
101
    {
102 2
        return $this->getHeader('Sec-WebSocket-Accept');
103
    }
104
105
    /**
106
     * @param ConnectionInterface $stream
107
     */
108 10
    public function send(ConnectionInterface $stream)
109
    {
110 10
        $stringResponse = $this->getHttpVersion() . ' ' . $this->httpResponse . "\r\n";
111
112 10
        foreach ($this->getHeaders() as $name => $content) {
113 10
            $stringResponse .= $name . ': '. $content . "\r\n";
114
        }
115
116
        // No content to concatenate
117 10
        $stringResponse .= "\r\n";
118
119 10
        $stream->write($stringResponse);
120 10
    }
121
122 9
    public static function createSwitchProtocolResponse()
123
    {
124 9
        $response = new Response();
125
126 9
        $response->setHttpResponse(Response::SWITCHING_PROTOCOLS);
127 9
        $response->addHeader('Upgrade', 'websocket');
128 9
        $response->addHeader('Connection', 'Upgrade');
129
130 9
        return $response;
131
    }
132
133
    /**
134
     * @param string $data
135
     * @return Response
136
     * @throws HttpException
137
     */
138 6
    public static function create(string &$data) : Response
139
    {
140 6
        if (!\preg_match('/\\r\\n\\r\\n/', $data)) {
141
            throw new IncompleteHttpMessageException();
142
        }
143
144
        // Split response headers from potential content
145 6
        $exploded = explode("\r\n\r\n", $data);
146 6
        $responseString = '';
147
148 6
        if (count($exploded) > 1) {
149
            // Removes the request keep content in data reference
150 6
            $responseString = $exploded[0];
151 6
            unset($exploded[0]);
152 6
            $data = implode("\r\n\r\n", $exploded);
153
        }
154
155 6
        $response = new Response();
156
157 6
        $lines = \explode("\r\n", $responseString);
158 6
        Response::initResponse($lines[0], $response);
159
160 5
        unset($lines[0]);
161 5
        Response::initHeaders($lines, $response);
162
163 5
        if (strtolower($response->getHeader('Upgrade')) !== 'websocket') {
164 1
            throw new HttpException('Missing or wrong upgrade header.');
165
        }
166 4
        if (strtolower($response->getHeader('Connection')) !== 'upgrade') {
167 1
            throw new HttpException('Missing "Connection: Upgrade" header.');
168
        }
169
170 3
        return $response;
171
    }
172
173
    /**
174
     * @param string   $firstLine
175
     * @param Response $response
176
     * @throws HttpException
177
     */
178 6
    protected static function initResponse(string $firstLine, Response $response)
179
    {
180 6
        $httpElements = \explode(' ', $firstLine);
181
182 6
        if (!\preg_match('/HTTP\/[1-2\.]+/',$httpElements[0])) {
183
            throw Response::createNotHttpException($firstLine);
184
        }
185 6
        $response->setHttpVersion($httpElements[0]);
186 6
        unset($httpElements[0]);
187
188 6
        if ($httpElements[1] != 101) {
189 1
            throw new HttpException(
190 1
                sprintf('Attempted 101 response but got %s, message: %s', $httpElements[1], $firstLine)
191
            );
192
        }
193 5
        $response->setStatusCode($httpElements[1]);
194 5
        unset($httpElements[1]);
195
196 5
        $response->setReason(implode(' ', $httpElements));
197 5
    }
198
}
199