SocketHttpAdapter::prepareRequest()   A
last analyzed

Complexity

Conditions 3
Paths 4

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 8
cts 8
cp 1
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 8
nc 4
nop 1
crap 3
1
<?php
2
3
/*
4
 * This file is part of the Ivory Http Adapter package.
5
 *
6
 * (c) Eric GELOEN <[email protected]>
7
 *
8
 * For the full copyright and license information, please read the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Ivory\HttpAdapter;
13
14
use Ivory\HttpAdapter\Extractor\ProtocolVersionExtractor;
15
use Ivory\HttpAdapter\Extractor\StatusCodeExtractor;
16
use Ivory\HttpAdapter\Message\InternalRequestInterface;
17
use Ivory\HttpAdapter\Normalizer\BodyNormalizer;
18
use Ivory\HttpAdapter\Normalizer\HeadersNormalizer;
19
20
/**
21
 * @author GeLo <[email protected]>
22
 */
23
class SocketHttpAdapter extends AbstractHttpAdapter
24
{
25
    /**
26
     * {@inheritdoc}
27
     */
28 45
    public function getName()
29
    {
30 45
        return 'socket';
31
    }
32
33
    /**
34
     * {@inheritdoc}
35
     */
36 756
    protected function sendInternalRequest(InternalRequestInterface $internalRequest)
37
    {
38 756
        $uri = $internalRequest->getUri();
39 756
        $https = $uri->getScheme() === 'https';
40
41 756
        $socket = @stream_socket_client(
42 756
            ($https ? 'ssl' : 'tcp').'://'.$uri->getHost().':'.($uri->getPort() ?: ($https ? 443 : 80)),
43 588
            $errno,
44 588
            $errstr,
45 756
            $this->getConfiguration()->getTimeout()
46 588
        );
47
48 756
        if ($socket === false) {
49 18
            throw HttpAdapterException::cannotFetchUri($uri, $this->getName(), $errstr);
50
        }
51
52 747
        stream_set_timeout($socket, $this->getConfiguration()->getTimeout());
53 747
        fwrite($socket, $this->prepareRequest($internalRequest));
54 747
        list($responseHeaders, $body) = $this->parseResponse($socket);
55 747
        $hasTimeout = $this->detectTimeout($socket);
56 747
        fclose($socket);
57
58 747
        if ($hasTimeout) {
59 18
            throw HttpAdapterException::timeoutExceeded(
60 14
                $uri,
61 18
                $this->getConfiguration()->getTimeout(),
62 18
                $this->getName()
63 14
            );
64
        }
65
66 729
        return $this->getConfiguration()->getMessageFactory()->createResponse(
67 729
            StatusCodeExtractor::extract($responseHeaders),
68 729
            ProtocolVersionExtractor::extract($responseHeaders),
69 729
            $responseHeaders = HeadersNormalizer::normalize($responseHeaders),
70 729
            BodyNormalizer::normalize($this->decodeBody($responseHeaders, $body), $internalRequest->getMethod())
71 567
        );
72
    }
73
74
    /**
75
     * @param InternalRequestInterface $internalRequest
76
     *
77
     * @return string
78
     */
79 747
    private function prepareRequest(InternalRequestInterface $internalRequest)
80
    {
81 747
        $uri = $internalRequest->getUri();
82 747
        $path = $uri->getPath().($uri->getQuery() ? '?'.$uri->getQuery() : '');
83
84 747
        $request = $internalRequest->getMethod().' '.$path.' HTTP/'.$internalRequest->getProtocolVersion()."\r\n";
85 747
        $request .= 'Host: '.$uri->getHost().($uri->getPort() !== null ? ':'.$uri->getPort() : '')."\r\n";
86 747
        $request .= implode("\r\n", $this->prepareHeaders($internalRequest, false, true, true))."\r\n\r\n";
87 747
        $request .= $this->prepareBody($internalRequest)."\r\n";
88
89 747
        return $request;
90
    }
91
92
    /**
93
     * @param resource $socket
94
     *
95
     * @return array
96
     */
97 747
    private function parseResponse($socket)
98
    {
99 747
        $headers = '';
100 747
        $body = '';
101 747
        $processHeaders = true;
102
103 747
        while (!feof($socket) && !$this->detectTimeout($socket)) {
104 747
            $line = fgets($socket);
105
106 747
            if ($line === "\r\n") {
107 729
                $processHeaders = false;
108 747
            } elseif ($processHeaders) {
109 747
                $headers .= $line;
110 581
            } else {
111 675
                $body .= $line;
112
            }
113 581
        }
114
115 747
        return [$headers, $body];
116 581
    }
117
118
    /**
119
     * @param array  $headers
120
     * @param string $body
121
     *
122
     * @return string
123
     */
124 729
    private function decodeBody(array $headers, $body)
125
    {
126 729
        $headers = array_change_key_case($headers);
127
128 729
        if (isset($headers['transfer-encoding']) && $headers['transfer-encoding'] === 'chunked') {
129
            for ($decodedBody = ''; !empty($body); $body = trim($body)) {
130
                $pos = strpos($body, "\r\n");
131
                $length = hexdec(substr($body, 0, $pos));
132
                $decodedBody .= substr($body, $pos + 2, $length);
133
                $body = substr($body, $pos + $length + 2);
134
            }
135
136
            return $decodedBody;
137
        }
138
139 729
        return $body;
140
    }
141
142
    /**
143
     * @param resource $socket
144
     *
145
     * @return bool
146
     */
147 747
    private function detectTimeout($socket)
148
    {
149 747
        $info = stream_get_meta_data($socket);
150
151 747
        return $info['timed_out'];
152
    }
153
}
154