Verzeichnisstruktur phpBB-3.2.0
- Veröffentlicht
- 06.01.2017
So funktioniert es
|
Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück |
Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis. |
|
(Beispiel Datei-Icons)
|
Auf das Icon klicken um den Quellcode anzuzeigen |
CurlFactory.php
001 <?php
002 namespace GuzzleHttp\Ring\Client;
003
004 use GuzzleHttp\Ring\Core;
005 use GuzzleHttp\Ring\Exception\ConnectException;
006 use GuzzleHttp\Ring\Exception\RingException;
007 use GuzzleHttp\Stream\LazyOpenStream;
008 use GuzzleHttp\Stream\StreamInterface;
009
010 /**
011 * Creates curl resources from a request
012 */
013 class CurlFactory
014 {
015 /**
016 * Creates a cURL handle, header resource, and body resource based on a
017 * transaction.
018 *
019 * @param array $request Request hash
020 * @param null|resource $handle Optionally provide a curl handle to modify
021 *
022 * @return array Returns an array of the curl handle, headers array, and
023 * response body handle.
024 * @throws \RuntimeException when an option cannot be applied
025 */
026 public function __invoke(array $request, $handle = null)
027 {
028 $headers = [];
029 $options = $this->getDefaultOptions($request, $headers);
030 $this->applyMethod($request, $options);
031
032 if (isset($request['client'])) {
033 $this->applyHandlerOptions($request, $options);
034 }
035
036 $this->applyHeaders($request, $options);
037 unset($options['_headers']);
038
039 // Add handler options from the request's configuration options
040 if (isset($request['client']['curl'])) {
041 $options = $this->applyCustomCurlOptions(
042 $request['client']['curl'],
043 $options
044 );
045 }
046
047 if (!$handle) {
048 $handle = curl_init();
049 }
050
051 $body = $this->getOutputBody($request, $options);
052 curl_setopt_array($handle, $options);
053
054 return [$handle, &$headers, $body];
055 }
056
057 /**
058 * Creates a response hash from a cURL result.
059 *
060 * @param callable $handler Handler that was used.
061 * @param array $request Request that sent.
062 * @param array $response Response hash to update.
063 * @param array $headers Headers received during transfer.
064 * @param resource $body Body fopen response.
065 *
066 * @return array
067 */
068 public static function createResponse(
069 callable $handler,
070 array $request,
071 array $response,
072 array $headers,
073 $body
074 ) {
075 if (isset($response['transfer_stats']['url'])) {
076 $response['effective_url'] = $response['transfer_stats']['url'];
077 }
078
079 if (!empty($headers)) {
080 $startLine = explode(' ', array_shift($headers), 3);
081 $headerList = Core::headersFromLines($headers);
082 $response['headers'] = $headerList;
083 $response['version'] = isset($startLine[0]) ? substr($startLine[0], 5) : null;
084 $response['status'] = isset($startLine[1]) ? (int) $startLine[1] : null;
085 $response['reason'] = isset($startLine[2]) ? $startLine[2] : null;
086 $response['body'] = $body;
087 Core::rewindBody($response);
088 }
089
090 return !empty($response['curl']['errno']) || !isset($response['status'])
091 ? self::createErrorResponse($handler, $request, $response)
092 : $response;
093 }
094
095 private static function createErrorResponse(
096 callable $handler,
097 array $request,
098 array $response
099 ) {
100 static $connectionErrors = [
101 CURLE_OPERATION_TIMEOUTED => true,
102 CURLE_COULDNT_RESOLVE_HOST => true,
103 CURLE_COULDNT_CONNECT => true,
104 CURLE_SSL_CONNECT_ERROR => true,
105 CURLE_GOT_NOTHING => true,
106 ];
107
108 // Retry when nothing is present or when curl failed to rewind.
109 if (!isset($response['err_message'])
110 && (empty($response['curl']['errno'])
111 || $response['curl']['errno'] == 65)
112 ) {
113 return self::retryFailedRewind($handler, $request, $response);
114 }
115
116 $message = isset($response['err_message'])
117 ? $response['err_message']
118 : sprintf('cURL error %s: %s',
119 $response['curl']['errno'],
120 isset($response['curl']['error'])
121 ? $response['curl']['error']
122 : 'See http://curl.haxx.se/libcurl/c/libcurl-errors.html');
123
124 $error = isset($response['curl']['errno'])
125 && isset($connectionErrors[$response['curl']['errno']])
126 ? new ConnectException($message)
127 : new RingException($message);
128
129 return $response + [
130 'status' => null,
131 'reason' => null,
132 'body' => null,
133 'headers' => [],
134 'error' => $error,
135 ];
136 }
137
138 private function getOutputBody(array $request, array &$options)
139 {
140 // Determine where the body of the response (if any) will be streamed.
141 if (isset($options[CURLOPT_WRITEFUNCTION])) {
142 return $request['client']['save_to'];
143 }
144
145 if (isset($options[CURLOPT_FILE])) {
146 return $options[CURLOPT_FILE];
147 }
148
149 if ($request['http_method'] != 'HEAD') {
150 // Create a default body if one was not provided
151 return $options[CURLOPT_FILE] = fopen('php://temp', 'w+');
152 }
153
154 return null;
155 }
156
157 private function getDefaultOptions(array $request, array &$headers)
158 {
159 $url = Core::url($request);
160 $startingResponse = false;
161
162 $options = [
163 '_headers' => $request['headers'],
164 CURLOPT_CUSTOMREQUEST => $request['http_method'],
165 CURLOPT_URL => $url,
166 CURLOPT_RETURNTRANSFER => false,
167 CURLOPT_HEADER => false,
168 CURLOPT_CONNECTTIMEOUT => 150,
169 CURLOPT_HEADERFUNCTION => function ($ch, $h) use (&$headers, &$startingResponse) {
170 $value = trim($h);
171 if ($value === '') {
172 $startingResponse = true;
173 } elseif ($startingResponse) {
174 $startingResponse = false;
175 $headers = [$value];
176 } else {
177 $headers[] = $value;
178 }
179 return strlen($h);
180 },
181 ];
182
183 if (isset($request['version'])) {
184 if ($request['version'] == 2.0) {
185 $options[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2_0;
186 } else if ($request['version'] == 1.1) {
187 $options[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_1;
188 } else {
189 $options[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0;
190 }
191 }
192
193 if (defined('CURLOPT_PROTOCOLS')) {
194 $options[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
195 }
196
197 return $options;
198 }
199
200 private function applyMethod(array $request, array &$options)
201 {
202 if (isset($request['body'])) {
203 $this->applyBody($request, $options);
204 return;
205 }
206
207 switch ($request['http_method']) {
208 case 'PUT':
209 case 'POST':
210 // See http://tools.ietf.org/html/rfc7230#section-3.3.2
211 if (!Core::hasHeader($request, 'Content-Length')) {
212 $options[CURLOPT_HTTPHEADER][] = 'Content-Length: 0';
213 }
214 break;
215 case 'HEAD':
216 $options[CURLOPT_NOBODY] = true;
217 unset(
218 $options[CURLOPT_WRITEFUNCTION],
219 $options[CURLOPT_READFUNCTION],
220 $options[CURLOPT_FILE],
221 $options[CURLOPT_INFILE]
222 );
223 }
224 }
225
226 private function applyBody(array $request, array &$options)
227 {
228 $contentLength = Core::firstHeader($request, 'Content-Length');
229 $size = $contentLength !== null ? (int) $contentLength : null;
230
231 // Send the body as a string if the size is less than 1MB OR if the
232 // [client][curl][body_as_string] request value is set.
233 if (($size !== null && $size < 1000000) ||
234 isset($request['client']['curl']['body_as_string']) ||
235 is_string($request['body'])
236 ) {
237 $options[CURLOPT_POSTFIELDS] = Core::body($request);
238 // Don't duplicate the Content-Length header
239 $this->removeHeader('Content-Length', $options);
240 $this->removeHeader('Transfer-Encoding', $options);
241 } else {
242 $options[CURLOPT_UPLOAD] = true;
243 if ($size !== null) {
244 // Let cURL handle setting the Content-Length header
245 $options[CURLOPT_INFILESIZE] = $size;
246 $this->removeHeader('Content-Length', $options);
247 }
248 $this->addStreamingBody($request, $options);
249 }
250
251 // If the Expect header is not present, prevent curl from adding it
252 if (!Core::hasHeader($request, 'Expect')) {
253 $options[CURLOPT_HTTPHEADER][] = 'Expect:';
254 }
255
256 // cURL sometimes adds a content-type by default. Prevent this.
257 if (!Core::hasHeader($request, 'Content-Type')) {
258 $options[CURLOPT_HTTPHEADER][] = 'Content-Type:';
259 }
260 }
261
262 private function addStreamingBody(array $request, array &$options)
263 {
264 $body = $request['body'];
265
266 if ($body instanceof StreamInterface) {
267 $options[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body) {
268 return (string) $body->read($length);
269 };
270 if (!isset($options[CURLOPT_INFILESIZE])) {
271 if ($size = $body->getSize()) {
272 $options[CURLOPT_INFILESIZE] = $size;
273 }
274 }
275 } elseif (is_resource($body)) {
276 $options[CURLOPT_INFILE] = $body;
277 } elseif ($body instanceof \Iterator) {
278 $buf = '';
279 $options[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body, &$buf) {
280 if ($body->valid()) {
281 $buf .= $body->current();
282 $body->next();
283 }
284 $result = (string) substr($buf, 0, $length);
285 $buf = substr($buf, $length);
286 return $result;
287 };
288 } else {
289 throw new \InvalidArgumentException('Invalid request body provided');
290 }
291 }
292
293 private function applyHeaders(array $request, array &$options)
294 {
295 foreach ($options['_headers'] as $name => $values) {
296 foreach ($values as $value) {
297 $options[CURLOPT_HTTPHEADER][] = "$name: $value";
298 }
299 }
300
301 // Remove the Accept header if one was not set
302 if (!Core::hasHeader($request, 'Accept')) {
303 $options[CURLOPT_HTTPHEADER][] = 'Accept:';
304 }
305 }
306
307 /**
308 * Takes an array of curl options specified in the 'curl' option of a
309 * request's configuration array and maps them to CURLOPT_* options.
310 *
311 * This method is only called when a request has a 'curl' config setting.
312 *
313 * @param array $config Configuration array of custom curl option
314 * @param array $options Array of existing curl options
315 *
316 * @return array Returns a new array of curl options
317 */
318 private function applyCustomCurlOptions(array $config, array $options)
319 {
320 $curlOptions = [];
321 foreach ($config as $key => $value) {
322 if (is_int($key)) {
323 $curlOptions[$key] = $value;
324 }
325 }
326
327 return $curlOptions + $options;
328 }
329
330 /**
331 * Remove a header from the options array.
332 *
333 * @param string $name Case-insensitive header to remove
334 * @param array $options Array of options to modify
335 */
336 private function removeHeader($name, array &$options)
337 {
338 foreach (array_keys($options['_headers']) as $key) {
339 if (!strcasecmp($key, $name)) {
340 unset($options['_headers'][$key]);
341 return;
342 }
343 }
344 }
345
346 /**
347 * Applies an array of request client options to a the options array.
348 *
349 * This method uses a large switch rather than double-dispatch to save on
350 * high overhead of calling functions in PHP.
351 */
352 private function applyHandlerOptions(array $request, array &$options)
353 {
354 foreach ($request['client'] as $key => $value) {
355 switch ($key) {
356 // Violating PSR-4 to provide more room.
357 case 'verify':
358
359 if ($value === false) {
360 unset($options[CURLOPT_CAINFO]);
361 $options[CURLOPT_SSL_VERIFYHOST] = 0;
362 $options[CURLOPT_SSL_VERIFYPEER] = false;
363 continue;
364 }
365
366 $options[CURLOPT_SSL_VERIFYHOST] = 2;
367 $options[CURLOPT_SSL_VERIFYPEER] = true;
368
369 if (is_string($value)) {
370 $options[CURLOPT_CAINFO] = $value;
371 if (!file_exists($value)) {
372 throw new \InvalidArgumentException(
373 "SSL CA bundle not found: $value"
374 );
375 }
376 }
377 break;
378
379 case 'decode_content':
380
381 if ($value === false) {
382 continue;
383 }
384
385 $accept = Core::firstHeader($request, 'Accept-Encoding');
386 if ($accept) {
387 $options[CURLOPT_ENCODING] = $accept;
388 } else {
389 $options[CURLOPT_ENCODING] = '';
390 // Don't let curl send the header over the wire
391 $options[CURLOPT_HTTPHEADER][] = 'Accept-Encoding:';
392 }
393 break;
394
395 case 'save_to':
396
397 if (is_string($value)) {
398 if (!is_dir(dirname($value))) {
399 throw new \RuntimeException(sprintf(
400 'Directory %s does not exist for save_to value of %s',
401 dirname($value),
402 $value
403 ));
404 }
405 $value = new LazyOpenStream($value, 'w+');
406 }
407
408 if ($value instanceof StreamInterface) {
409 $options[CURLOPT_WRITEFUNCTION] =
410 function ($ch, $write) use ($value) {
411 return $value->write($write);
412 };
413 } elseif (is_resource($value)) {
414 $options[CURLOPT_FILE] = $value;
415 } else {
416 throw new \InvalidArgumentException('save_to must be a '
417 . 'GuzzleHttp\Stream\StreamInterface or resource');
418 }
419 break;
420
421 case 'timeout':
422
423 if (defined('CURLOPT_TIMEOUT_MS')) {
424 $options[CURLOPT_TIMEOUT_MS] = $value * 1000;
425 } else {
426 $options[CURLOPT_TIMEOUT] = $value;
427 }
428 break;
429
430 case 'connect_timeout':
431
432 if (defined('CURLOPT_CONNECTTIMEOUT_MS')) {
433 $options[CURLOPT_CONNECTTIMEOUT_MS] = $value * 1000;
434 } else {
435 $options[CURLOPT_CONNECTTIMEOUT] = $value;
436 }
437 break;
438
439 case 'proxy':
440
441 if (!is_array($value)) {
442 $options[CURLOPT_PROXY] = $value;
443 } elseif (isset($request['scheme'])) {
444 $scheme = $request['scheme'];
445 if (isset($value[$scheme])) {
446 $options[CURLOPT_PROXY] = $value[$scheme];
447 }
448 }
449 break;
450
451 case 'cert':
452
453 if (is_array($value)) {
454 $options[CURLOPT_SSLCERTPASSWD] = $value[1];
455 $value = $value[0];
456 }
457
458 if (!file_exists($value)) {
459 throw new \InvalidArgumentException(
460 "SSL certificate not found: {$value}"
461 );
462 }
463
464 $options[CURLOPT_SSLCERT] = $value;
465 break;
466
467 case 'ssl_key':
468
469 if (is_array($value)) {
470 $options[CURLOPT_SSLKEYPASSWD] = $value[1];
471 $value = $value[0];
472 }
473
474 if (!file_exists($value)) {
475 throw new \InvalidArgumentException(
476 "SSL private key not found: {$value}"
477 );
478 }
479
480 $options[CURLOPT_SSLKEY] = $value;
481 break;
482
483 case 'progress':
484
485 if (!is_callable($value)) {
486 throw new \InvalidArgumentException(
487 'progress client option must be callable'
488 );
489 }
490
491 $options[CURLOPT_NOPROGRESS] = false;
492 $options[CURLOPT_PROGRESSFUNCTION] =
493 function () use ($value) {
494 $args = func_get_args();
495 // PHP 5.5 pushed the handle onto the start of the args
496 if (is_resource($args[0])) {
497 array_shift($args);
498 }
499 call_user_func_array($value, $args);
500 };
501 break;
502
503 case 'debug':
504
505 if ($value) {
506 $options[CURLOPT_STDERR] = Core::getDebugResource($value);
507 $options[CURLOPT_VERBOSE] = true;
508 }
509 break;
510 }
511 }
512 }
513
514 /**
515 * This function ensures that a response was set on a transaction. If one
516 * was not set, then the request is retried if possible. This error
517 * typically means you are sending a payload, curl encountered a
518 * "Connection died, retrying a fresh connect" error, tried to rewind the
519 * stream, and then encountered a "necessary data rewind wasn't possible"
520 * error, causing the request to be sent through curl_multi_info_read()
521 * without an error status.
522 */
523 private static function retryFailedRewind(
524 callable $handler,
525 array $request,
526 array $response
527 ) {
528 // If there is no body, then there is some other kind of issue. This
529 // is weird and should probably never happen.
530 if (!isset($request['body'])) {
531 $response['err_message'] = 'No response was received for a request '
532 . 'with no body. This could mean that you are saturating your '
533 . 'network.';
534 return self::createErrorResponse($handler, $request, $response);
535 }
536
537 if (!Core::rewindBody($request)) {
538 $response['err_message'] = 'The connection unexpectedly failed '
539 . 'without providing an error. The request would have been '
540 . 'retried, but attempting to rewind the request body failed.';
541 return self::createErrorResponse($handler, $request, $response);
542 }
543
544 // Retry no more than 3 times before giving up.
545 if (!isset($request['curl']['retries'])) {
546 $request['curl']['retries'] = 1;
547 } elseif ($request['curl']['retries'] == 2) {
548 $response['err_message'] = 'The cURL request was retried 3 times '
549 . 'and did no succeed. cURL was unable to rewind the body of '
550 . 'the request and subsequent retries resulted in the same '
551 . 'error. Turn on the debug option to see what went wrong. '
552 . 'See https://bugs.php.net/bug.php?id=47204 for more information.';
553 return self::createErrorResponse($handler, $request, $response);
554 } else {
555 $request['curl']['retries']++;
556 }
557
558 return $handler($request);
559 }
560 }
561