Chris@0
|
1 <?php
|
Chris@0
|
2 namespace GuzzleHttp\Handler;
|
Chris@0
|
3
|
Chris@0
|
4 use GuzzleHttp\Exception\RequestException;
|
Chris@0
|
5 use GuzzleHttp\Exception\ConnectException;
|
Chris@0
|
6 use GuzzleHttp\Promise\FulfilledPromise;
|
Chris@0
|
7 use GuzzleHttp\Promise\RejectedPromise;
|
Chris@0
|
8 use GuzzleHttp\Psr7;
|
Chris@0
|
9 use GuzzleHttp\Psr7\LazyOpenStream;
|
Chris@0
|
10 use GuzzleHttp\TransferStats;
|
Chris@0
|
11 use Psr\Http\Message\RequestInterface;
|
Chris@0
|
12
|
Chris@0
|
13 /**
|
Chris@0
|
14 * Creates curl resources from a request
|
Chris@0
|
15 */
|
Chris@0
|
16 class CurlFactory implements CurlFactoryInterface
|
Chris@0
|
17 {
|
Chris@0
|
18 /** @var array */
|
Chris@0
|
19 private $handles = [];
|
Chris@0
|
20
|
Chris@0
|
21 /** @var int Total number of idle handles to keep in cache */
|
Chris@0
|
22 private $maxHandles;
|
Chris@0
|
23
|
Chris@0
|
24 /**
|
Chris@0
|
25 * @param int $maxHandles Maximum number of idle handles.
|
Chris@0
|
26 */
|
Chris@0
|
27 public function __construct($maxHandles)
|
Chris@0
|
28 {
|
Chris@0
|
29 $this->maxHandles = $maxHandles;
|
Chris@0
|
30 }
|
Chris@0
|
31
|
Chris@0
|
32 public function create(RequestInterface $request, array $options)
|
Chris@0
|
33 {
|
Chris@0
|
34 if (isset($options['curl']['body_as_string'])) {
|
Chris@0
|
35 $options['_body_as_string'] = $options['curl']['body_as_string'];
|
Chris@0
|
36 unset($options['curl']['body_as_string']);
|
Chris@0
|
37 }
|
Chris@0
|
38
|
Chris@0
|
39 $easy = new EasyHandle;
|
Chris@0
|
40 $easy->request = $request;
|
Chris@0
|
41 $easy->options = $options;
|
Chris@0
|
42 $conf = $this->getDefaultConf($easy);
|
Chris@0
|
43 $this->applyMethod($easy, $conf);
|
Chris@0
|
44 $this->applyHandlerOptions($easy, $conf);
|
Chris@0
|
45 $this->applyHeaders($easy, $conf);
|
Chris@0
|
46 unset($conf['_headers']);
|
Chris@0
|
47
|
Chris@0
|
48 // Add handler options from the request configuration options
|
Chris@0
|
49 if (isset($options['curl'])) {
|
Chris@0
|
50 $conf = array_replace($conf, $options['curl']);
|
Chris@0
|
51 }
|
Chris@0
|
52
|
Chris@0
|
53 $conf[CURLOPT_HEADERFUNCTION] = $this->createHeaderFn($easy);
|
Chris@0
|
54 $easy->handle = $this->handles
|
Chris@0
|
55 ? array_pop($this->handles)
|
Chris@0
|
56 : curl_init();
|
Chris@0
|
57 curl_setopt_array($easy->handle, $conf);
|
Chris@0
|
58
|
Chris@0
|
59 return $easy;
|
Chris@0
|
60 }
|
Chris@0
|
61
|
Chris@0
|
62 public function release(EasyHandle $easy)
|
Chris@0
|
63 {
|
Chris@0
|
64 $resource = $easy->handle;
|
Chris@0
|
65 unset($easy->handle);
|
Chris@0
|
66
|
Chris@0
|
67 if (count($this->handles) >= $this->maxHandles) {
|
Chris@0
|
68 curl_close($resource);
|
Chris@0
|
69 } else {
|
Chris@0
|
70 // Remove all callback functions as they can hold onto references
|
Chris@0
|
71 // and are not cleaned up by curl_reset. Using curl_setopt_array
|
Chris@0
|
72 // does not work for some reason, so removing each one
|
Chris@0
|
73 // individually.
|
Chris@0
|
74 curl_setopt($resource, CURLOPT_HEADERFUNCTION, null);
|
Chris@0
|
75 curl_setopt($resource, CURLOPT_READFUNCTION, null);
|
Chris@0
|
76 curl_setopt($resource, CURLOPT_WRITEFUNCTION, null);
|
Chris@0
|
77 curl_setopt($resource, CURLOPT_PROGRESSFUNCTION, null);
|
Chris@0
|
78 curl_reset($resource);
|
Chris@0
|
79 $this->handles[] = $resource;
|
Chris@0
|
80 }
|
Chris@0
|
81 }
|
Chris@0
|
82
|
Chris@0
|
83 /**
|
Chris@0
|
84 * Completes a cURL transaction, either returning a response promise or a
|
Chris@0
|
85 * rejected promise.
|
Chris@0
|
86 *
|
Chris@0
|
87 * @param callable $handler
|
Chris@0
|
88 * @param EasyHandle $easy
|
Chris@0
|
89 * @param CurlFactoryInterface $factory Dictates how the handle is released
|
Chris@0
|
90 *
|
Chris@0
|
91 * @return \GuzzleHttp\Promise\PromiseInterface
|
Chris@0
|
92 */
|
Chris@0
|
93 public static function finish(
|
Chris@0
|
94 callable $handler,
|
Chris@0
|
95 EasyHandle $easy,
|
Chris@0
|
96 CurlFactoryInterface $factory
|
Chris@0
|
97 ) {
|
Chris@0
|
98 if (isset($easy->options['on_stats'])) {
|
Chris@0
|
99 self::invokeStats($easy);
|
Chris@0
|
100 }
|
Chris@0
|
101
|
Chris@0
|
102 if (!$easy->response || $easy->errno) {
|
Chris@0
|
103 return self::finishError($handler, $easy, $factory);
|
Chris@0
|
104 }
|
Chris@0
|
105
|
Chris@0
|
106 // Return the response if it is present and there is no error.
|
Chris@0
|
107 $factory->release($easy);
|
Chris@0
|
108
|
Chris@0
|
109 // Rewind the body of the response if possible.
|
Chris@0
|
110 $body = $easy->response->getBody();
|
Chris@0
|
111 if ($body->isSeekable()) {
|
Chris@0
|
112 $body->rewind();
|
Chris@0
|
113 }
|
Chris@0
|
114
|
Chris@0
|
115 return new FulfilledPromise($easy->response);
|
Chris@0
|
116 }
|
Chris@0
|
117
|
Chris@0
|
118 private static function invokeStats(EasyHandle $easy)
|
Chris@0
|
119 {
|
Chris@0
|
120 $curlStats = curl_getinfo($easy->handle);
|
Chris@0
|
121 $stats = new TransferStats(
|
Chris@0
|
122 $easy->request,
|
Chris@0
|
123 $easy->response,
|
Chris@0
|
124 $curlStats['total_time'],
|
Chris@0
|
125 $easy->errno,
|
Chris@0
|
126 $curlStats
|
Chris@0
|
127 );
|
Chris@0
|
128 call_user_func($easy->options['on_stats'], $stats);
|
Chris@0
|
129 }
|
Chris@0
|
130
|
Chris@0
|
131 private static function finishError(
|
Chris@0
|
132 callable $handler,
|
Chris@0
|
133 EasyHandle $easy,
|
Chris@0
|
134 CurlFactoryInterface $factory
|
Chris@0
|
135 ) {
|
Chris@0
|
136 // Get error information and release the handle to the factory.
|
Chris@0
|
137 $ctx = [
|
Chris@0
|
138 'errno' => $easy->errno,
|
Chris@0
|
139 'error' => curl_error($easy->handle),
|
Chris@0
|
140 ] + curl_getinfo($easy->handle);
|
Chris@0
|
141 $factory->release($easy);
|
Chris@0
|
142
|
Chris@0
|
143 // Retry when nothing is present or when curl failed to rewind.
|
Chris@0
|
144 if (empty($easy->options['_err_message'])
|
Chris@0
|
145 && (!$easy->errno || $easy->errno == 65)
|
Chris@0
|
146 ) {
|
Chris@0
|
147 return self::retryFailedRewind($handler, $easy, $ctx);
|
Chris@0
|
148 }
|
Chris@0
|
149
|
Chris@0
|
150 return self::createRejection($easy, $ctx);
|
Chris@0
|
151 }
|
Chris@0
|
152
|
Chris@0
|
153 private static function createRejection(EasyHandle $easy, array $ctx)
|
Chris@0
|
154 {
|
Chris@0
|
155 static $connectionErrors = [
|
Chris@0
|
156 CURLE_OPERATION_TIMEOUTED => true,
|
Chris@0
|
157 CURLE_COULDNT_RESOLVE_HOST => true,
|
Chris@0
|
158 CURLE_COULDNT_CONNECT => true,
|
Chris@0
|
159 CURLE_SSL_CONNECT_ERROR => true,
|
Chris@0
|
160 CURLE_GOT_NOTHING => true,
|
Chris@0
|
161 ];
|
Chris@0
|
162
|
Chris@0
|
163 // If an exception was encountered during the onHeaders event, then
|
Chris@0
|
164 // return a rejected promise that wraps that exception.
|
Chris@0
|
165 if ($easy->onHeadersException) {
|
Chris@0
|
166 return \GuzzleHttp\Promise\rejection_for(
|
Chris@0
|
167 new RequestException(
|
Chris@0
|
168 'An error was encountered during the on_headers event',
|
Chris@0
|
169 $easy->request,
|
Chris@0
|
170 $easy->response,
|
Chris@0
|
171 $easy->onHeadersException,
|
Chris@0
|
172 $ctx
|
Chris@0
|
173 )
|
Chris@0
|
174 );
|
Chris@0
|
175 }
|
Chris@0
|
176
|
Chris@0
|
177 $message = sprintf(
|
Chris@0
|
178 'cURL error %s: %s (%s)',
|
Chris@0
|
179 $ctx['errno'],
|
Chris@0
|
180 $ctx['error'],
|
Chris@0
|
181 'see http://curl.haxx.se/libcurl/c/libcurl-errors.html'
|
Chris@0
|
182 );
|
Chris@0
|
183
|
Chris@0
|
184 // Create a connection exception if it was a specific error code.
|
Chris@0
|
185 $error = isset($connectionErrors[$easy->errno])
|
Chris@0
|
186 ? new ConnectException($message, $easy->request, null, $ctx)
|
Chris@0
|
187 : new RequestException($message, $easy->request, $easy->response, null, $ctx);
|
Chris@0
|
188
|
Chris@0
|
189 return \GuzzleHttp\Promise\rejection_for($error);
|
Chris@0
|
190 }
|
Chris@0
|
191
|
Chris@0
|
192 private function getDefaultConf(EasyHandle $easy)
|
Chris@0
|
193 {
|
Chris@0
|
194 $conf = [
|
Chris@0
|
195 '_headers' => $easy->request->getHeaders(),
|
Chris@0
|
196 CURLOPT_CUSTOMREQUEST => $easy->request->getMethod(),
|
Chris@0
|
197 CURLOPT_URL => (string) $easy->request->getUri()->withFragment(''),
|
Chris@0
|
198 CURLOPT_RETURNTRANSFER => false,
|
Chris@0
|
199 CURLOPT_HEADER => false,
|
Chris@0
|
200 CURLOPT_CONNECTTIMEOUT => 150,
|
Chris@0
|
201 ];
|
Chris@0
|
202
|
Chris@0
|
203 if (defined('CURLOPT_PROTOCOLS')) {
|
Chris@0
|
204 $conf[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
|
Chris@0
|
205 }
|
Chris@0
|
206
|
Chris@0
|
207 $version = $easy->request->getProtocolVersion();
|
Chris@0
|
208 if ($version == 1.1) {
|
Chris@0
|
209 $conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_1;
|
Chris@0
|
210 } elseif ($version == 2.0) {
|
Chris@0
|
211 $conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2_0;
|
Chris@0
|
212 } else {
|
Chris@0
|
213 $conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0;
|
Chris@0
|
214 }
|
Chris@0
|
215
|
Chris@0
|
216 return $conf;
|
Chris@0
|
217 }
|
Chris@0
|
218
|
Chris@0
|
219 private function applyMethod(EasyHandle $easy, array &$conf)
|
Chris@0
|
220 {
|
Chris@0
|
221 $body = $easy->request->getBody();
|
Chris@0
|
222 $size = $body->getSize();
|
Chris@0
|
223
|
Chris@0
|
224 if ($size === null || $size > 0) {
|
Chris@0
|
225 $this->applyBody($easy->request, $easy->options, $conf);
|
Chris@0
|
226 return;
|
Chris@0
|
227 }
|
Chris@0
|
228
|
Chris@0
|
229 $method = $easy->request->getMethod();
|
Chris@0
|
230 if ($method === 'PUT' || $method === 'POST') {
|
Chris@0
|
231 // See http://tools.ietf.org/html/rfc7230#section-3.3.2
|
Chris@0
|
232 if (!$easy->request->hasHeader('Content-Length')) {
|
Chris@0
|
233 $conf[CURLOPT_HTTPHEADER][] = 'Content-Length: 0';
|
Chris@0
|
234 }
|
Chris@0
|
235 } elseif ($method === 'HEAD') {
|
Chris@0
|
236 $conf[CURLOPT_NOBODY] = true;
|
Chris@0
|
237 unset(
|
Chris@0
|
238 $conf[CURLOPT_WRITEFUNCTION],
|
Chris@0
|
239 $conf[CURLOPT_READFUNCTION],
|
Chris@0
|
240 $conf[CURLOPT_FILE],
|
Chris@0
|
241 $conf[CURLOPT_INFILE]
|
Chris@0
|
242 );
|
Chris@0
|
243 }
|
Chris@0
|
244 }
|
Chris@0
|
245
|
Chris@0
|
246 private function applyBody(RequestInterface $request, array $options, array &$conf)
|
Chris@0
|
247 {
|
Chris@0
|
248 $size = $request->hasHeader('Content-Length')
|
Chris@0
|
249 ? (int) $request->getHeaderLine('Content-Length')
|
Chris@0
|
250 : null;
|
Chris@0
|
251
|
Chris@0
|
252 // Send the body as a string if the size is less than 1MB OR if the
|
Chris@0
|
253 // [curl][body_as_string] request value is set.
|
Chris@0
|
254 if (($size !== null && $size < 1000000) ||
|
Chris@0
|
255 !empty($options['_body_as_string'])
|
Chris@0
|
256 ) {
|
Chris@0
|
257 $conf[CURLOPT_POSTFIELDS] = (string) $request->getBody();
|
Chris@0
|
258 // Don't duplicate the Content-Length header
|
Chris@0
|
259 $this->removeHeader('Content-Length', $conf);
|
Chris@0
|
260 $this->removeHeader('Transfer-Encoding', $conf);
|
Chris@0
|
261 } else {
|
Chris@0
|
262 $conf[CURLOPT_UPLOAD] = true;
|
Chris@0
|
263 if ($size !== null) {
|
Chris@0
|
264 $conf[CURLOPT_INFILESIZE] = $size;
|
Chris@0
|
265 $this->removeHeader('Content-Length', $conf);
|
Chris@0
|
266 }
|
Chris@0
|
267 $body = $request->getBody();
|
Chris@0
|
268 if ($body->isSeekable()) {
|
Chris@0
|
269 $body->rewind();
|
Chris@0
|
270 }
|
Chris@0
|
271 $conf[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body) {
|
Chris@0
|
272 return $body->read($length);
|
Chris@0
|
273 };
|
Chris@0
|
274 }
|
Chris@0
|
275
|
Chris@0
|
276 // If the Expect header is not present, prevent curl from adding it
|
Chris@0
|
277 if (!$request->hasHeader('Expect')) {
|
Chris@0
|
278 $conf[CURLOPT_HTTPHEADER][] = 'Expect:';
|
Chris@0
|
279 }
|
Chris@0
|
280
|
Chris@0
|
281 // cURL sometimes adds a content-type by default. Prevent this.
|
Chris@0
|
282 if (!$request->hasHeader('Content-Type')) {
|
Chris@0
|
283 $conf[CURLOPT_HTTPHEADER][] = 'Content-Type:';
|
Chris@0
|
284 }
|
Chris@0
|
285 }
|
Chris@0
|
286
|
Chris@0
|
287 private function applyHeaders(EasyHandle $easy, array &$conf)
|
Chris@0
|
288 {
|
Chris@0
|
289 foreach ($conf['_headers'] as $name => $values) {
|
Chris@0
|
290 foreach ($values as $value) {
|
Chris@0
|
291 $conf[CURLOPT_HTTPHEADER][] = "$name: $value";
|
Chris@0
|
292 }
|
Chris@0
|
293 }
|
Chris@0
|
294
|
Chris@0
|
295 // Remove the Accept header if one was not set
|
Chris@0
|
296 if (!$easy->request->hasHeader('Accept')) {
|
Chris@0
|
297 $conf[CURLOPT_HTTPHEADER][] = 'Accept:';
|
Chris@0
|
298 }
|
Chris@0
|
299 }
|
Chris@0
|
300
|
Chris@0
|
301 /**
|
Chris@0
|
302 * Remove a header from the options array.
|
Chris@0
|
303 *
|
Chris@0
|
304 * @param string $name Case-insensitive header to remove
|
Chris@0
|
305 * @param array $options Array of options to modify
|
Chris@0
|
306 */
|
Chris@0
|
307 private function removeHeader($name, array &$options)
|
Chris@0
|
308 {
|
Chris@0
|
309 foreach (array_keys($options['_headers']) as $key) {
|
Chris@0
|
310 if (!strcasecmp($key, $name)) {
|
Chris@0
|
311 unset($options['_headers'][$key]);
|
Chris@0
|
312 return;
|
Chris@0
|
313 }
|
Chris@0
|
314 }
|
Chris@0
|
315 }
|
Chris@0
|
316
|
Chris@0
|
317 private function applyHandlerOptions(EasyHandle $easy, array &$conf)
|
Chris@0
|
318 {
|
Chris@0
|
319 $options = $easy->options;
|
Chris@0
|
320 if (isset($options['verify'])) {
|
Chris@0
|
321 if ($options['verify'] === false) {
|
Chris@0
|
322 unset($conf[CURLOPT_CAINFO]);
|
Chris@0
|
323 $conf[CURLOPT_SSL_VERIFYHOST] = 0;
|
Chris@0
|
324 $conf[CURLOPT_SSL_VERIFYPEER] = false;
|
Chris@0
|
325 } else {
|
Chris@0
|
326 $conf[CURLOPT_SSL_VERIFYHOST] = 2;
|
Chris@0
|
327 $conf[CURLOPT_SSL_VERIFYPEER] = true;
|
Chris@0
|
328 if (is_string($options['verify'])) {
|
Chris@0
|
329 // Throw an error if the file/folder/link path is not valid or doesn't exist.
|
Chris@0
|
330 if (!file_exists($options['verify'])) {
|
Chris@0
|
331 throw new \InvalidArgumentException(
|
Chris@0
|
332 "SSL CA bundle not found: {$options['verify']}"
|
Chris@0
|
333 );
|
Chris@0
|
334 }
|
Chris@0
|
335 // If it's a directory or a link to a directory use CURLOPT_CAPATH.
|
Chris@0
|
336 // If not, it's probably a file, or a link to a file, so use CURLOPT_CAINFO.
|
Chris@0
|
337 if (is_dir($options['verify']) ||
|
Chris@0
|
338 (is_link($options['verify']) && is_dir(readlink($options['verify'])))) {
|
Chris@0
|
339 $conf[CURLOPT_CAPATH] = $options['verify'];
|
Chris@0
|
340 } else {
|
Chris@0
|
341 $conf[CURLOPT_CAINFO] = $options['verify'];
|
Chris@0
|
342 }
|
Chris@0
|
343 }
|
Chris@0
|
344 }
|
Chris@0
|
345 }
|
Chris@0
|
346
|
Chris@0
|
347 if (!empty($options['decode_content'])) {
|
Chris@0
|
348 $accept = $easy->request->getHeaderLine('Accept-Encoding');
|
Chris@0
|
349 if ($accept) {
|
Chris@0
|
350 $conf[CURLOPT_ENCODING] = $accept;
|
Chris@0
|
351 } else {
|
Chris@0
|
352 $conf[CURLOPT_ENCODING] = '';
|
Chris@0
|
353 // Don't let curl send the header over the wire
|
Chris@0
|
354 $conf[CURLOPT_HTTPHEADER][] = 'Accept-Encoding:';
|
Chris@0
|
355 }
|
Chris@0
|
356 }
|
Chris@0
|
357
|
Chris@0
|
358 if (isset($options['sink'])) {
|
Chris@0
|
359 $sink = $options['sink'];
|
Chris@0
|
360 if (!is_string($sink)) {
|
Chris@0
|
361 $sink = \GuzzleHttp\Psr7\stream_for($sink);
|
Chris@0
|
362 } elseif (!is_dir(dirname($sink))) {
|
Chris@0
|
363 // Ensure that the directory exists before failing in curl.
|
Chris@0
|
364 throw new \RuntimeException(sprintf(
|
Chris@0
|
365 'Directory %s does not exist for sink value of %s',
|
Chris@0
|
366 dirname($sink),
|
Chris@0
|
367 $sink
|
Chris@0
|
368 ));
|
Chris@0
|
369 } else {
|
Chris@0
|
370 $sink = new LazyOpenStream($sink, 'w+');
|
Chris@0
|
371 }
|
Chris@0
|
372 $easy->sink = $sink;
|
Chris@0
|
373 $conf[CURLOPT_WRITEFUNCTION] = function ($ch, $write) use ($sink) {
|
Chris@0
|
374 return $sink->write($write);
|
Chris@0
|
375 };
|
Chris@0
|
376 } else {
|
Chris@0
|
377 // Use a default temp stream if no sink was set.
|
Chris@0
|
378 $conf[CURLOPT_FILE] = fopen('php://temp', 'w+');
|
Chris@0
|
379 $easy->sink = Psr7\stream_for($conf[CURLOPT_FILE]);
|
Chris@0
|
380 }
|
Chris@0
|
381 $timeoutRequiresNoSignal = false;
|
Chris@0
|
382 if (isset($options['timeout'])) {
|
Chris@0
|
383 $timeoutRequiresNoSignal |= $options['timeout'] < 1;
|
Chris@0
|
384 $conf[CURLOPT_TIMEOUT_MS] = $options['timeout'] * 1000;
|
Chris@0
|
385 }
|
Chris@0
|
386
|
Chris@0
|
387 // CURL default value is CURL_IPRESOLVE_WHATEVER
|
Chris@0
|
388 if (isset($options['force_ip_resolve'])) {
|
Chris@0
|
389 if ('v4' === $options['force_ip_resolve']) {
|
Chris@0
|
390 $conf[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V4;
|
Chris@0
|
391 } else if ('v6' === $options['force_ip_resolve']) {
|
Chris@0
|
392 $conf[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V6;
|
Chris@0
|
393 }
|
Chris@0
|
394 }
|
Chris@0
|
395
|
Chris@0
|
396 if (isset($options['connect_timeout'])) {
|
Chris@0
|
397 $timeoutRequiresNoSignal |= $options['connect_timeout'] < 1;
|
Chris@0
|
398 $conf[CURLOPT_CONNECTTIMEOUT_MS] = $options['connect_timeout'] * 1000;
|
Chris@0
|
399 }
|
Chris@0
|
400
|
Chris@0
|
401 if ($timeoutRequiresNoSignal && strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') {
|
Chris@0
|
402 $conf[CURLOPT_NOSIGNAL] = true;
|
Chris@0
|
403 }
|
Chris@0
|
404
|
Chris@0
|
405 if (isset($options['proxy'])) {
|
Chris@0
|
406 if (!is_array($options['proxy'])) {
|
Chris@0
|
407 $conf[CURLOPT_PROXY] = $options['proxy'];
|
Chris@0
|
408 } else {
|
Chris@0
|
409 $scheme = $easy->request->getUri()->getScheme();
|
Chris@0
|
410 if (isset($options['proxy'][$scheme])) {
|
Chris@0
|
411 $host = $easy->request->getUri()->getHost();
|
Chris@0
|
412 if (!isset($options['proxy']['no']) ||
|
Chris@0
|
413 !\GuzzleHttp\is_host_in_noproxy($host, $options['proxy']['no'])
|
Chris@0
|
414 ) {
|
Chris@0
|
415 $conf[CURLOPT_PROXY] = $options['proxy'][$scheme];
|
Chris@0
|
416 }
|
Chris@0
|
417 }
|
Chris@0
|
418 }
|
Chris@0
|
419 }
|
Chris@0
|
420
|
Chris@0
|
421 if (isset($options['cert'])) {
|
Chris@0
|
422 $cert = $options['cert'];
|
Chris@0
|
423 if (is_array($cert)) {
|
Chris@0
|
424 $conf[CURLOPT_SSLCERTPASSWD] = $cert[1];
|
Chris@0
|
425 $cert = $cert[0];
|
Chris@0
|
426 }
|
Chris@0
|
427 if (!file_exists($cert)) {
|
Chris@0
|
428 throw new \InvalidArgumentException(
|
Chris@0
|
429 "SSL certificate not found: {$cert}"
|
Chris@0
|
430 );
|
Chris@0
|
431 }
|
Chris@0
|
432 $conf[CURLOPT_SSLCERT] = $cert;
|
Chris@0
|
433 }
|
Chris@0
|
434
|
Chris@0
|
435 if (isset($options['ssl_key'])) {
|
Chris@0
|
436 $sslKey = $options['ssl_key'];
|
Chris@0
|
437 if (is_array($sslKey)) {
|
Chris@0
|
438 $conf[CURLOPT_SSLKEYPASSWD] = $sslKey[1];
|
Chris@0
|
439 $sslKey = $sslKey[0];
|
Chris@0
|
440 }
|
Chris@0
|
441 if (!file_exists($sslKey)) {
|
Chris@0
|
442 throw new \InvalidArgumentException(
|
Chris@0
|
443 "SSL private key not found: {$sslKey}"
|
Chris@0
|
444 );
|
Chris@0
|
445 }
|
Chris@0
|
446 $conf[CURLOPT_SSLKEY] = $sslKey;
|
Chris@0
|
447 }
|
Chris@0
|
448
|
Chris@0
|
449 if (isset($options['progress'])) {
|
Chris@0
|
450 $progress = $options['progress'];
|
Chris@0
|
451 if (!is_callable($progress)) {
|
Chris@0
|
452 throw new \InvalidArgumentException(
|
Chris@0
|
453 'progress client option must be callable'
|
Chris@0
|
454 );
|
Chris@0
|
455 }
|
Chris@0
|
456 $conf[CURLOPT_NOPROGRESS] = false;
|
Chris@0
|
457 $conf[CURLOPT_PROGRESSFUNCTION] = function () use ($progress) {
|
Chris@0
|
458 $args = func_get_args();
|
Chris@0
|
459 // PHP 5.5 pushed the handle onto the start of the args
|
Chris@0
|
460 if (is_resource($args[0])) {
|
Chris@0
|
461 array_shift($args);
|
Chris@0
|
462 }
|
Chris@0
|
463 call_user_func_array($progress, $args);
|
Chris@0
|
464 };
|
Chris@0
|
465 }
|
Chris@0
|
466
|
Chris@0
|
467 if (!empty($options['debug'])) {
|
Chris@0
|
468 $conf[CURLOPT_STDERR] = \GuzzleHttp\debug_resource($options['debug']);
|
Chris@0
|
469 $conf[CURLOPT_VERBOSE] = true;
|
Chris@0
|
470 }
|
Chris@0
|
471 }
|
Chris@0
|
472
|
Chris@0
|
473 /**
|
Chris@0
|
474 * This function ensures that a response was set on a transaction. If one
|
Chris@0
|
475 * was not set, then the request is retried if possible. This error
|
Chris@0
|
476 * typically means you are sending a payload, curl encountered a
|
Chris@0
|
477 * "Connection died, retrying a fresh connect" error, tried to rewind the
|
Chris@0
|
478 * stream, and then encountered a "necessary data rewind wasn't possible"
|
Chris@0
|
479 * error, causing the request to be sent through curl_multi_info_read()
|
Chris@0
|
480 * without an error status.
|
Chris@0
|
481 */
|
Chris@0
|
482 private static function retryFailedRewind(
|
Chris@0
|
483 callable $handler,
|
Chris@0
|
484 EasyHandle $easy,
|
Chris@0
|
485 array $ctx
|
Chris@0
|
486 ) {
|
Chris@0
|
487 try {
|
Chris@0
|
488 // Only rewind if the body has been read from.
|
Chris@0
|
489 $body = $easy->request->getBody();
|
Chris@0
|
490 if ($body->tell() > 0) {
|
Chris@0
|
491 $body->rewind();
|
Chris@0
|
492 }
|
Chris@0
|
493 } catch (\RuntimeException $e) {
|
Chris@0
|
494 $ctx['error'] = 'The connection unexpectedly failed without '
|
Chris@0
|
495 . 'providing an error. The request would have been retried, '
|
Chris@0
|
496 . 'but attempting to rewind the request body failed. '
|
Chris@0
|
497 . 'Exception: ' . $e;
|
Chris@0
|
498 return self::createRejection($easy, $ctx);
|
Chris@0
|
499 }
|
Chris@0
|
500
|
Chris@0
|
501 // Retry no more than 3 times before giving up.
|
Chris@0
|
502 if (!isset($easy->options['_curl_retries'])) {
|
Chris@0
|
503 $easy->options['_curl_retries'] = 1;
|
Chris@0
|
504 } elseif ($easy->options['_curl_retries'] == 2) {
|
Chris@0
|
505 $ctx['error'] = 'The cURL request was retried 3 times '
|
Chris@0
|
506 . 'and did not succeed. The most likely reason for the failure '
|
Chris@0
|
507 . 'is that cURL was unable to rewind the body of the request '
|
Chris@0
|
508 . 'and subsequent retries resulted in the same error. Turn on '
|
Chris@0
|
509 . 'the debug option to see what went wrong. See '
|
Chris@0
|
510 . 'https://bugs.php.net/bug.php?id=47204 for more information.';
|
Chris@0
|
511 return self::createRejection($easy, $ctx);
|
Chris@0
|
512 } else {
|
Chris@0
|
513 $easy->options['_curl_retries']++;
|
Chris@0
|
514 }
|
Chris@0
|
515
|
Chris@0
|
516 return $handler($easy->request, $easy->options);
|
Chris@0
|
517 }
|
Chris@0
|
518
|
Chris@0
|
519 private function createHeaderFn(EasyHandle $easy)
|
Chris@0
|
520 {
|
Chris@0
|
521 if (isset($easy->options['on_headers'])) {
|
Chris@0
|
522 $onHeaders = $easy->options['on_headers'];
|
Chris@0
|
523
|
Chris@0
|
524 if (!is_callable($onHeaders)) {
|
Chris@0
|
525 throw new \InvalidArgumentException('on_headers must be callable');
|
Chris@0
|
526 }
|
Chris@0
|
527 } else {
|
Chris@0
|
528 $onHeaders = null;
|
Chris@0
|
529 }
|
Chris@0
|
530
|
Chris@0
|
531 return function ($ch, $h) use (
|
Chris@0
|
532 $onHeaders,
|
Chris@0
|
533 $easy,
|
Chris@0
|
534 &$startingResponse
|
Chris@0
|
535 ) {
|
Chris@0
|
536 $value = trim($h);
|
Chris@0
|
537 if ($value === '') {
|
Chris@0
|
538 $startingResponse = true;
|
Chris@0
|
539 $easy->createResponse();
|
Chris@0
|
540 if ($onHeaders !== null) {
|
Chris@0
|
541 try {
|
Chris@0
|
542 $onHeaders($easy->response);
|
Chris@0
|
543 } catch (\Exception $e) {
|
Chris@0
|
544 // Associate the exception with the handle and trigger
|
Chris@0
|
545 // a curl header write error by returning 0.
|
Chris@0
|
546 $easy->onHeadersException = $e;
|
Chris@0
|
547 return -1;
|
Chris@0
|
548 }
|
Chris@0
|
549 }
|
Chris@0
|
550 } elseif ($startingResponse) {
|
Chris@0
|
551 $startingResponse = false;
|
Chris@0
|
552 $easy->headers = [$value];
|
Chris@0
|
553 } else {
|
Chris@0
|
554 $easy->headers[] = $value;
|
Chris@0
|
555 }
|
Chris@0
|
556 return strlen($h);
|
Chris@0
|
557 };
|
Chris@0
|
558 }
|
Chris@0
|
559 }
|