Completed
Push — master ( 77f0b6...4a1e92 )
by Filipe
01:42 queued 11s
created

CurlHttpClient   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 199
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Importance

Changes 0
Metric Value
wmc 24
lcom 1
cbo 6
dl 0
loc 199
rs 10
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 2
B sendRequest() 0 22 7
A prepare() 0 15 2
A setUrl() 0 16 3
A setHeaders() 0 8 2
A reset() 0 4 1
A createResponse() 0 6 1
A splitHeaderFromBody() 0 9 1
A parseHeaders() 0 14 3
A __destruct() 0 6 2
1
<?php
2
3
/**
4
 * This file is part of slick/http
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
namespace Slick\Http\Client;
11
12
use Psr\Http\Client\ClientInterface;
13
use Psr\Http\Message\RequestInterface;
14
use Psr\Http\Message\ResponseInterface;
15
use Slick\Http\Client\Exception\NetworkException;
16
use Slick\Http\Client\Exception\RequestException;
17
use Slick\Http\Message\Response;
18
use Slick\Http\Message\Uri;
19
20
/**
21
 * CurlHttpClient
22
 *
23
 * @package Slick\Http\Client
24
*/
25
final class CurlHttpClient implements ClientInterface
26
{
27
    /**
28
     * @var null|Uri
29
     */
30
    private $url;
31
32
    /**
33
     * @var null|HttpClientAuthentication
34
     */
35
    private $auth;
36
37
    /**
38
     * @var array
39
     */
40
    private $options = [
41
        CURLOPT_RETURNTRANSFER => true,
42
        CURLOPT_HEADER => true
43
    ];
44
45
    /**
46
     * @var resource
47
     */
48
    private $handler;
49
50
    /**
51
     * Creates a CURL HTTP Client
52
     *
53
     * @param Uri|null                      $url
54
     * @param HttpClientAuthentication|null $auth
55
     * @param array                         $options
56
     */
57
    public function __construct(Uri $url = null, HttpClientAuthentication $auth = null, array $options = [])
58
    {
59
        $this->handler = curl_init();
60
        $this->url = $url;
61
        $this->auth = $auth;
62
63
        foreach ($options as $name => $option) {
64
            $this->options[$name] = $option;
65
        }
66
    }
67
68
    /**
69
     * @inheritDoc
70
     */
71
    public function sendRequest(RequestInterface $request): ResponseInterface
72
    {
73
        $this->prepare($request);
74
        $result = curl_exec($this->handler);
75
        $errno = curl_errno($this->handler);
76
77
        switch ($errno) {
78
            case CURLE_OK:
79
                // All OK, no actions needed.
80
                break;
81
            case CURLE_COULDNT_RESOLVE_PROXY:
82
            case CURLE_COULDNT_RESOLVE_HOST:
83
            case CURLE_COULDNT_CONNECT:
84
            case CURLE_OPERATION_TIMEOUTED:
85
            case CURLE_SSL_CONNECT_ERROR:
86
                throw new NetworkException($request, curl_error($this->handler));
87
            default:
88
                throw new RequestException($request, curl_error($this->handler));
89
        }
90
91
        return $this->createResponse($result);
92
    }
93
94
    /**
95
     * Prepares the cURL handler options
96
     *
97
     * @param RequestInterface $request
98
     */
99
    private function prepare(RequestInterface $request)
100
    {
101
        $this->reset($this->handler);
102
        $this->setUrl($request);
103
        $this->options[CURLOPT_CUSTOMREQUEST] = $request->getMethod();
104
        $this->setHeaders($request);
105
        $this->options[CURLOPT_POSTFIELDS] = (string) $request->getBody();
106
107
        if ($this->auth instanceof HttpClientAuthentication) {
108
            $this->options[CURLOPT_USERPWD] = "{$this->auth->username()}:{$this->auth->password()}";
109
            $this->options[CURLOPT_HTTPAUTH] = $this->auth->type();
110
        }
111
112
        curl_setopt_array($this->handler, $this->options);
113
    }
114
115
    /**
116
     * Sets the URL for cURL to use
117
     *
118
     * @param RequestInterface $request
119
     */
120
    private function setUrl(RequestInterface $request)
121
    {
122
        $target = $request->getRequestTarget();
123
        $parts = parse_url($target);
124
125
        $uri = $this->url instanceof Uri
126
            ? $this->url
127
            : $request->getUri();
128
129
        $uri = $uri->withPath($parts['path']);
130
        $uri = array_key_exists('query', $parts)
131
            ? $uri->withQuery($parts['query'])
132
            : $uri;
133
134
        $this->options[CURLOPT_URL] = (string) $uri;
135
    }
136
137
    /**
138
     * Sets the headers from the request
139
     *
140
     * @param RequestInterface $request
141
     */
142
    private function setHeaders(RequestInterface $request)
143
    {
144
        $headers = [];
145
        foreach ($request->getHeaders() as $header => $values) {
146
            $headers[] = "{$header}: ".implode('; ', $values);
147
        }
148
        $this->options[CURLOPT_HTTPHEADER] = $headers;
149
    }
150
151
    /**
152
     * Resets the cURL handler
153
     *
154
     * @param resource $ch
155
     */
156
    private function reset(&$ch)
157
    {
158
        $ch = curl_init();
159
    }
160
161
    /**
162
     * Creates a response from cURL execution result
163
     *
164
     * @param string $result
165
     *
166
     * @return Response
167
     */
168
    private function createResponse($result)
169
    {
170
        $status = curl_getinfo($this->handler, CURLINFO_HTTP_CODE);
171
        list($header, $body) = $this->splitHeaderFromBody($result);
172
        return new Response($status, trim($body), $this->parseHeaders($header));
173
    }
174
175
    /**
176
     * Splits the cURL execution result into header and body
177
     *
178
     * @param $result
179
     *
180
     * @return array
181
     */
182
    private function splitHeaderFromBody($result)
183
    {
184
        $header_size = curl_getinfo($this->handler, CURLINFO_HEADER_SIZE);
185
186
        $header = substr($result, 0, $header_size);
187
        $body = substr($result, $header_size);
188
189
        return [trim($header), trim($body)];
190
    }
191
192
    /**
193
     * Parses the HTTP message headers from header part
194
     *
195
     * @param string $header
196
     *
197
     * @return array
198
     */
199
    private function parseHeaders($header)
200
    {
201
        $lines = explode("\n", $header);
202
        $headers = [];
203
        foreach ($lines as $line) {
204
            if (strpos($line, ':') === false) {
205
                continue;
206
            }
207
208
            $middle=explode(":", $line);
209
            $headers[trim($middle[0])] = trim($middle[1]);
210
        }
211
        return $headers;
212
    }
213
214
    /**
215
     * Close the cURL handler on destruct
216
     */
217
    public function __destruct()
218
    {
219
        if (is_resource($this->handler)) {
220
            curl_close($this->handler);
221
        }
222
    }
223
}
224