Completed
Push — master ( 316225...77f0b6 )
by Filipe
04:26 queued 11s
created

CurlHttpClient::sendRequest()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 22
rs 8.6346
c 0
b 0
f 0
cc 7
nc 7
nop 1
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 Exception;
13
use Psr\Http\Client\ClientInterface;
14
use Psr\Http\Message\RequestInterface;
15
use Psr\Http\Message\ResponseInterface;
16
use React\Promise\Deferred;
17
use React\Promise\PromiseInterface;
18
use Slick\Http\Client\Exception\ClientErrorException;
19
use Slick\Http\Client\Exception\NetworkException;
20
use Slick\Http\Client\Exception\RequestException;
21
use Slick\Http\Client\Exception\RuntimeException;
22
use Slick\Http\Client\Exception\ServerErrorException;
23
use Slick\Http\HttpClientInterface;
24
use Slick\Http\Message\Response;
25
use Slick\Http\Message\Uri;
26
27
/**
28
 * CurlHttpClient
29
 *
30
 * @package Slick\Http\Client
31
*/
32
final class CurlHttpClient implements ClientInterface, HttpClientInterface
0 ignored issues
show
Deprecated Code introduced by
The interface Slick\Http\HttpClientInterface has been deprecated with message: User the PSR-18 HTTP Client interface

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
33
{
34
    /**
35
     * @var null|Uri
36
     */
37
    private $url;
38
39
    /**
40
     * @var null|HttpClientAuthentication
41
     */
42
    private $auth;
43
44
    /**
45
     * @var array
46
     */
47
    private $options = [
48
        CURLOPT_RETURNTRANSFER => true,
49
        CURLOPT_HEADER => true
50
    ];
51
52
    /**
53
     * @var resource
54
     */
55
    private $handler;
56
57
    /**
58
     * Creates a CURL HTTP Client
59
     *
60
     * @param Uri|null                      $url
61
     * @param HttpClientAuthentication|null $auth
62
     * @param array                         $options
63
     */
64
    public function __construct(Uri $url = null, HttpClientAuthentication $auth = null, array $options = [])
65
    {
66
        $this->handler = curl_init();
67
        $this->url = $url;
68
        $this->auth = $auth;
69
70
        foreach ($options as $name => $option) {
71
            $this->options[$name] = $option;
72
        }
73
    }
74
75
    /**
76
     * Send out an HTTP requests returning a promise
77
     *
78
     * @param RequestInterface $request
79
     *
80
     * @return PromiseInterface
81
     * @deprecated please use CurlHttpClient::sendRequest() instead
82
     */
83
    public function send(RequestInterface $request)
84
    {
85
        $deferred = new Deferred();
86
        try {
87
            $response = $this->call($request);
88
            $deferred->resolve($response);
89
        } catch (Exception $caught) {
90
            $deferred->reject($caught);
91
        }
92
93
        return $deferred->promise();
94
    }
95
96
    /**
97
     * @inheritDoc
98
     */
99
    public function sendRequest(RequestInterface $request): ResponseInterface
100
    {
101
        $this->prepare($request);
102
        $result = curl_exec($this->handler);
103
        $errno = curl_errno($this->handler);
104
105
        switch ($errno) {
106
            case CURLE_OK:
107
                // All OK, no actions needed.
108
                break;
109
            case CURLE_COULDNT_RESOLVE_PROXY:
110
            case CURLE_COULDNT_RESOLVE_HOST:
111
            case CURLE_COULDNT_CONNECT:
112
            case CURLE_OPERATION_TIMEOUTED:
113
            case CURLE_SSL_CONNECT_ERROR:
114
                throw new NetworkException ($request, curl_error($this->handler));
115
            default:
116
                throw new RequestException($request, curl_error($this->handler));
117
        }
118
119
        return $this->createResponse($result);
120
    }
121
122
    /**
123
     * Call the HTTP server returning the response
124
     *
125
     * @param RequestInterface $request
126
     *
127
     * @return Response
128
     *
129
     * @throws RuntimeException If any error occur while preparing and connecting to the server
130
     * @throws ClientErrorException for responses with status codes between 400 and 499
131
     * @throws ServerErrorException for responses with status codes grater or equal to 500
132
     */
133
    private function call(RequestInterface $request)
134
    {
135
        $this->prepare($request);
136
        $result = curl_exec($this->handler);
137
138
        if (curl_errno($this->handler) !== 0) {
139
            throw new RuntimeException(
140
                "Error connecting to server: ".curl_error($this->handler)
141
            );
142
        }
143
144
        $response = $this->createResponse($result);
145
146
        $this->checkClientError($response, $request);
147
148
        $this->checkServerError($response, $request);
149
150
        return $response;
151
    }
152
153
    /**
154
     * Prepares the cURL handler options
155
     *
156
     * @param RequestInterface $request
157
     */
158
    private function prepare(RequestInterface $request)
159
    {
160
        $this->reset($this->handler);
161
        $this->setUrl($request);
162
        $this->options[CURLOPT_CUSTOMREQUEST] = $request->getMethod();
163
        $this->setHeaders($request);
164
        $this->options[CURLOPT_POSTFIELDS] = (string) $request->getBody();
165
166
        if ($this->auth instanceof HttpClientAuthentication) {
167
            $this->options[CURLOPT_USERPWD] = "{$this->auth->username()}:{$this->auth->password()}";
168
            $this->options[CURLOPT_HTTPAUTH] = $this->auth->type();
169
        }
170
171
        curl_setopt_array($this->handler, $this->options);
172
    }
173
174
    /**
175
     * Sets the URL for cURL to use
176
     *
177
     * @param RequestInterface $request
178
     */
179
    private function setUrl(RequestInterface $request)
180
    {
181
        $target = $request->getRequestTarget();
182
        $parts = parse_url($target);
183
184
        $uri = $this->url instanceof Uri
185
            ? $this->url
186
            : $request->getUri();
187
188
        $uri = $uri->withPath($parts['path']);
189
        $uri = array_key_exists('query', $parts)
190
            ? $uri->withQuery($parts['query'])
191
            : $uri;
192
193
        $this->options[CURLOPT_URL] = (string) $uri;
194
    }
195
196
    /**
197
     * Sets the headers from the request
198
     *
199
     * @param RequestInterface $request
200
     */
201
    private function setHeaders(RequestInterface $request)
202
    {
203
        $headers = [];
204
        foreach ($request->getHeaders() as $header => $values) {
205
            $headers[] = "{$header}: ".implode('; ', $values);
206
        }
207
        $this->options[CURLOPT_HTTPHEADER] = $headers;
208
    }
209
210
    /**
211
     * Resets the cURL handler
212
     *
213
     * @param resource $ch
214
     */
215
    private function reset(&$ch)
216
    {
217
        $ch = curl_init();
218
    }
219
220
    /**
221
     * Creates a response from cURL execution result
222
     *
223
     * @param string $result
224
     *
225
     * @return Response
226
     */
227
    private function createResponse($result)
228
    {
229
        $status = curl_getinfo($this->handler, CURLINFO_HTTP_CODE);
230
        list($header, $body) = $this->splitHeaderFromBody($result);
231
        return new Response($status, trim($body), $this->parseHeaders($header));
232
    }
233
234
    /**
235
     * Splits the cURL execution result into header and body
236
     *
237
     * @param $result
238
     *
239
     * @return array
240
     */
241
    private function splitHeaderFromBody($result)
242
    {
243
        $header_size = curl_getinfo($this->handler, CURLINFO_HEADER_SIZE);
244
245
        $header = substr($result, 0, $header_size);
246
        $body = substr($result, $header_size);
247
248
        return [trim($header), trim($body)];
249
    }
250
251
    /**
252
     * Parses the HTTP message headers from header part
253
     *
254
     * @param string $header
255
     *
256
     * @return array
257
     */
258
    private function parseHeaders($header)
259
    {
260
        $lines = explode("\n", $header);
261
        $headers = [];
262
        foreach ($lines as $line) {
263
            if (strpos($line, ':') === false) {
264
                continue;
265
            }
266
267
            $middle=explode(":", $line);
268
            $headers[trim($middle[0])] = trim($middle[1]);
269
        }
270
        return $headers;
271
    }
272
273
    /**
274
     * Checks provided response is an HTTP client error (4xx)
275
     *
276
     * @param ResponseInterface $response
277
     * @param RequestInterface $request
278
     *
279
     * @throws ClientErrorException for responses with status codes between 400 and 499
280
     */
281
    private function checkClientError(ResponseInterface $response, RequestInterface $request)
282
    {
283
        if ($response->getStatusCode() >= 400 && $response->getStatusCode() < 500) {
284
            throw new ClientErrorException($request, $response);
285
        }
286
    }
287
288
    /**
289
     * Checks provided response is an HTTP server error (5xx)
290
     *
291
     * @param ResponseInterface $response
292
     * @param RequestInterface $request
293
     *
294
     * @throws ServerErrorException for responses with status codes grater or equal to 500
295
     */
296
    private function checkServerError(ResponseInterface $response, RequestInterface $request)
297
    {
298
        if ($response->getStatusCode() >= 500) {
299
            throw new ServerErrorException($request, $response);
300
        }
301
    }
302
303
    /**
304
     * Close the cURL handler on destruct
305
     */
306
    public function __destruct()
307
    {
308
        if (is_resource($this->handler)) {
309
            curl_close($this->handler);
310
        }
311
    }
312
}
313