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