Completed
Pull Request — master (#131)
by Eric
63:39 queued 61:20
created

SocketHttpAdapter::getName()   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

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 2
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 2
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
    public function getName()
29
    {
30
        return 'socket';
31
    }
32
33
    /**
34
     * {@inheritdoc}
35
     */
36
    protected function sendInternalRequest(InternalRequestInterface $internalRequest)
37
    {
38
        $uri = $internalRequest->getUri();
39
        $https = $uri->getScheme() === 'https';
40
41
        $socket = @stream_socket_client(
42
            ($https ? 'ssl' : 'tcp').'://'.$uri->getHost().':'.($uri->getPort() ?: ($https ? 443 : 80)),
43
            $errno,
44
            $errstr,
45
            $this->getConfiguration()->getTimeout()
46
        );
47
48
        if ($socket === false) {
49
            throw HttpAdapterException::cannotFetchUri($uri, $this->getName(), $errstr);
50
        }
51
52
        stream_set_timeout($socket, $this->getConfiguration()->getTimeout());
53
        fwrite($socket, $this->prepareRequest($internalRequest));
54
        list($responseHeaders, $body) = $this->parseResponse($socket);
55
        $hasTimeout = $this->detectTimeout($socket);
56
        fclose($socket);
57
58
        if ($hasTimeout) {
59
            throw HttpAdapterException::timeoutExceeded(
60
                $uri,
61
                $this->getConfiguration()->getTimeout(),
62
                $this->getName()
63
            );
64
        }
65
66
        return $this->getConfiguration()->getMessageFactory()->createResponse(
67
            StatusCodeExtractor::extract($responseHeaders),
68
            ProtocolVersionExtractor::extract($responseHeaders),
69
            $responseHeaders = HeadersNormalizer::normalize($responseHeaders),
70
            BodyNormalizer::normalize($this->decodeBody($responseHeaders, $body), $internalRequest->getMethod())
71
        );
72
    }
73
74
    /**
75
     * @param InternalRequestInterface $internalRequest
76
     *
77
     * @return string
78
     */
79
    private function prepareRequest(InternalRequestInterface $internalRequest)
80
    {
81
        $uri = $internalRequest->getUri();
82
        $path = $uri->getPath().($uri->getQuery() ? '?'.$uri->getQuery() : '');
83
84
        $request = $internalRequest->getMethod().' '.$path.' HTTP/'.$internalRequest->getProtocolVersion()."\r\n";
85
        $request .= 'Host: '.$uri->getHost().($uri->getPort() !== null ? ':'.$uri->getPort() : '')."\r\n";
86
        $request .= implode("\r\n", $this->prepareHeaders($internalRequest, false, true, true))."\r\n\r\n";
87
        $request .= $this->prepareBody($internalRequest)."\r\n";
88
89
        return $request;
90
    }
91
92
    /**
93
     * @param resource $socket
94
     *
95
     * @return array
96
     */
97
    private function parseResponse($socket)
98
    {
99
        $headers = '';
100
        $body = '';
101
        $processHeaders = true;
102
103
        while (!feof($socket) && !$this->detectTimeout($socket)) {
104
            $line = fgets($socket);
105
106
            if ($line === "\r\n") {
107
                $processHeaders = false;
108
            } elseif ($processHeaders) {
109
                $headers .= $line;
110
            } else {
111
                $body .= $line;
112
            }
113
        }
114
115
        return [$headers, $body];
116
    }
117
118
    /**
119
     * @param array  $headers
120
     * @param string $body
121
     *
122
     * @return string
123
     */
124
    private function decodeBody(array $headers, $body)
125
    {
126
        $headers = array_change_key_case($headers);
127
128
        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
        return $body;
140
    }
141
142
    /**
143
     * @param resource $socket
144
     *
145
     * @return bool
146
     */
147
    private function detectTimeout($socket)
148
    {
149
        $info = stream_get_meta_data($socket);
150
151
        return $info['timed_out'];
152
    }
153
}
154