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 |
|
|
|
|
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
|
|
|
|
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.