20 private $lastHeaders = [];
33 if (isset($options[
'delay'])) {
34 usleep($options[
'delay'] * 1000);
41 $request = $request->withoutHeader(
'Expect');
45 if (0 === $request->getBody()->getSize()) {
46 $request = $request->withHeader(
'Content-Length',
'0');
49 return $this->createResponse(
52 $this->createStream($request, $options),
57 }
catch (\Exception $e) {
59 $message = $e->getMessage();
61 if (strpos($message,
'getaddrinfo')
62 || strpos($message,
'Connection refused')
63 || strpos($message,
"couldn't connect to host")
64 || strpos($message,
"connection attempt failed")
69 $this->invokeStats($options, $request, $startTime,
null, $e);
71 return \GuzzleHttp\Promise\rejection_for($e);
75 private function invokeStats(
82 if (isset($options[
'on_stats'])) {
90 call_user_func($options[
'on_stats'], $stats);
94 private function createResponse(
95 RequestInterface $request,
100 $hdrs = $this->lastHeaders;
101 $this->lastHeaders = [];
102 $parts = explode(
' ', array_shift($hdrs), 3);
103 $ver = explode(
'/', $parts[0])[1];
105 $reason = isset($parts[2]) ? $parts[2] :
null;
106 $headers = \GuzzleHttp\headers_from_lines($hdrs);
107 list($stream, $headers) = $this->checkDecode($options, $headers, $stream);
111 if (strcasecmp(
'HEAD', $request->getMethod())) {
112 $sink = $this->createSink($stream, $options);
115 $response =
new Psr7\Response($status, $headers, $sink, $ver, $reason);
117 if (isset($options[
'on_headers'])) {
119 $options[
'on_headers']($response);
120 }
catch (\Exception $e) {
121 $msg =
'An error was encountered during the on_headers event';
122 $ex =
new RequestException($msg, $request, $response, $e);
123 return \GuzzleHttp\Promise\rejection_for($ex);
129 if ($sink !== $stream) {
133 $response->getHeaderLine(
'Content-Length')
137 $this->invokeStats($options, $request, $startTime, $response,
null);
139 return new FulfilledPromise($response);
142 private function createSink(StreamInterface $stream, array $options)
144 if (!empty($options[
'stream'])) {
148 $sink = isset($options[
'sink'])
150 : fopen(
'php://temp',
'r+');
152 return is_string($sink)
153 ?
new Psr7\LazyOpenStream($sink,
'w+')
157 private function checkDecode(array $options, array $headers, $stream)
160 if (!empty($options[
'decode_content'])) {
161 $normalizedKeys = \GuzzleHttp\normalize_header_keys($headers);
162 if (isset($normalizedKeys[
'content-encoding'])) {
163 $encoding = $headers[$normalizedKeys[
'content-encoding']];
164 if ($encoding[0] ===
'gzip' || $encoding[0] ===
'deflate') {
165 $stream =
new Psr7\InflateStream(
168 $headers[
'x-encoded-content-encoding']
169 = $headers[$normalizedKeys[
'content-encoding']];
171 unset($headers[$normalizedKeys[
'content-encoding']]);
173 if (isset($normalizedKeys[
'content-length'])) {
174 $headers[
'x-encoded-content-length']
175 = $headers[$normalizedKeys[
'content-length']];
177 $length = (int) $stream->getSize();
179 unset($headers[$normalizedKeys[
'content-length']]);
181 $headers[$normalizedKeys[
'content-length']] = [$length];
188 return [$stream, $headers];
202 private function drain(
203 StreamInterface $source,
204 StreamInterface $sink,
214 (strlen($contentLength) > 0 && (
int) $contentLength > 0) ? (
int) $contentLength : -1
231 private function createResource(callable $callback)
234 set_error_handler(
function ($_, $msg, $file, $line) use (&$errors) {
243 $resource = $callback();
244 restore_error_handler();
247 $message =
'Error creating resource: ';
248 foreach ($errors as $err) {
249 foreach ($err as $key => $value) {
250 $message .=
"[$key] $value" . PHP_EOL;
253 throw new \RuntimeException(trim($message));
259 private function createStream(RequestInterface $request, array $options)
263 $methods = array_flip(get_class_methods(__CLASS__));
268 if ($request->getProtocolVersion() ==
'1.1'
269 && !$request->hasHeader(
'Connection')
271 $request = $request->withHeader(
'Connection',
'close');
275 if (!isset($options[
'verify'])) {
276 $options[
'verify'] =
true;
280 $context = $this->getDefaultContext($request);
282 if (isset($options[
'on_headers']) && !is_callable($options[
'on_headers'])) {
283 throw new \InvalidArgumentException(
'on_headers must be callable');
286 if (!empty($options)) {
287 foreach ($options as $key => $value) {
288 $method =
"add_{$key}";
289 if (isset($methods[$method])) {
290 $this->{$method}($request, $context, $value, $params);
295 if (isset($options[
'stream_context'])) {
296 if (!is_array($options[
'stream_context'])) {
297 throw new \InvalidArgumentException(
'stream_context must be an array');
299 $context = array_replace_recursive(
301 $options[
'stream_context']
306 if (isset($options[
'auth'])
307 && is_array($options[
'auth'])
308 && isset($options[
'auth'][2])
309 &&
'ntlm' == $options[
'auth'][2]
311 throw new \InvalidArgumentException(
'Microsoft NTLM authentication only supported with curl handler');
314 $uri = $this->resolveHost($request, $options);
316 $context = $this->createResource(
317 function () use ($context, $params) {
318 return stream_context_create($context, $params);
322 return $this->createResource(
323 function () use ($uri, &$http_response_header, $context, $options) {
324 $resource = fopen((
string) $uri,
'r',
null, $context);
325 $this->lastHeaders = $http_response_header;
327 if (isset($options[
'read_timeout'])) {
328 $readTimeout = $options[
'read_timeout'];
329 $sec = (int) $readTimeout;
330 $usec = ($readTimeout - $sec) * 100000;
331 stream_set_timeout($resource, $sec, $usec);
339 private function resolveHost(RequestInterface $request, array $options)
341 $uri = $request->getUri();
343 if (isset($options[
'force_ip_resolve']) && !filter_var($uri->getHost(), FILTER_VALIDATE_IP)) {
344 if (
'v4' === $options[
'force_ip_resolve']) {
345 $records = dns_get_record($uri->getHost(), DNS_A);
346 if (!isset($records[0][
'ip'])) {
347 throw new ConnectException(
349 "Could not resolve IPv4 address for host '%s'",
355 $uri = $uri->withHost($records[0][
'ip']);
356 } elseif (
'v6' === $options[
'force_ip_resolve']) {
357 $records = dns_get_record($uri->getHost(), DNS_AAAA);
358 if (!isset($records[0][
'ipv6'])) {
359 throw new ConnectException(
361 "Could not resolve IPv6 address for host '%s'",
367 $uri = $uri->withHost(
'[' . $records[0][
'ipv6'] .
']');
374 private function getDefaultContext(RequestInterface $request)
377 foreach ($request->getHeaders() as $name => $value) {
378 foreach ($value as $val) {
379 $headers .=
"$name: $val\r\n";
385 'method' => $request->getMethod(),
386 'header' => $headers,
387 'protocol_version' => $request->getProtocolVersion(),
388 'ignore_errors' =>
true,
389 'follow_location' => 0,
393 $body = (string) $request->getBody();
396 $context[
'http'][
'content'] = $body;
398 if (!$request->hasHeader(
'Content-Type')) {
399 $context[
'http'][
'header'] .=
"Content-Type:\r\n";
403 $context[
'http'][
'header'] = rtrim($context[
'http'][
'header']);
408 private function add_proxy(RequestInterface $request, &$options, $value, &$params)
410 if (!is_array($value)) {
411 $options[
'http'][
'proxy'] = $value;
413 $scheme = $request->getUri()->getScheme();
414 if (isset($value[$scheme])) {
415 if (!isset($value[
'no'])
417 $request->getUri()->getHost(),
421 $options[
'http'][
'proxy'] = $value[$scheme];
427 private function add_timeout(RequestInterface $request, &$options, $value, &$params)
430 $options[
'http'][
'timeout'] = $value;
434 private function add_verify(RequestInterface $request, &$options, $value, &$params)
436 if ($value ===
true) {
439 if (PHP_VERSION_ID < 50600) {
440 $options[
'ssl'][
'cafile'] = \GuzzleHttp\default_ca_bundle();
442 } elseif (is_string($value)) {
443 $options[
'ssl'][
'cafile'] = $value;
444 if (!file_exists($value)) {
445 throw new \RuntimeException(
"SSL CA bundle not found: $value");
447 } elseif ($value ===
false) {
448 $options[
'ssl'][
'verify_peer'] =
false;
449 $options[
'ssl'][
'verify_peer_name'] =
false;
452 throw new \InvalidArgumentException(
'Invalid verify request option');
455 $options[
'ssl'][
'verify_peer'] =
true;
456 $options[
'ssl'][
'verify_peer_name'] =
true;
457 $options[
'ssl'][
'allow_self_signed'] =
false;
460 private function add_cert(RequestInterface $request, &$options, $value, &$params)
462 if (is_array($value)) {
463 $options[
'ssl'][
'passphrase'] = $value[1];
467 if (!file_exists($value)) {
468 throw new \RuntimeException(
"SSL certificate not found: {$value}");
471 $options[
'ssl'][
'local_cert'] = $value;
474 private function add_progress(RequestInterface $request, &$options, $value, &$params)
476 $this->addNotification(
478 function ($code, $a, $b, $c, $transferred, $total) use ($value) {
479 if ($code == STREAM_NOTIFY_PROGRESS) {
480 $value($total, $transferred,
null,
null);
486 private function add_debug(RequestInterface $request, &$options, $value, &$params)
488 if ($value ===
false) {
493 STREAM_NOTIFY_CONNECT =>
'CONNECT',
494 STREAM_NOTIFY_AUTH_REQUIRED =>
'AUTH_REQUIRED',
495 STREAM_NOTIFY_AUTH_RESULT =>
'AUTH_RESULT',
496 STREAM_NOTIFY_MIME_TYPE_IS =>
'MIME_TYPE_IS',
497 STREAM_NOTIFY_FILE_SIZE_IS =>
'FILE_SIZE_IS',
498 STREAM_NOTIFY_REDIRECTED =>
'REDIRECTED',
499 STREAM_NOTIFY_PROGRESS =>
'PROGRESS',
500 STREAM_NOTIFY_FAILURE =>
'FAILURE',
501 STREAM_NOTIFY_COMPLETED =>
'COMPLETED',
502 STREAM_NOTIFY_RESOLVE =>
'RESOLVE',
504 static $args = [
'severity',
'message',
'message_code',
505 'bytes_transferred',
'bytes_max'];
507 $value = \GuzzleHttp\debug_resource($value);
508 $ident = $request->getMethod() .
' ' . $request->getUri()->withFragment(
'');
509 $this->addNotification(
511 function () use ($ident, $value, $map, $args) {
512 $passed = func_get_args();
513 $code = array_shift($passed);
514 fprintf($value,
'<%s> [%s] ', $ident, $map[$code]);
515 foreach (array_filter($passed) as $i => $v) {
516 fwrite($value, $args[$i] .
': "' . $v .
'" ');
518 fwrite($value,
"\n");
523 private function addNotification(array &$params, callable $notify)
526 if (!isset($params[
'notification'])) {
527 $params[
'notification'] = $notify;
529 $params[
'notification'] = $this->callArray([
530 $params[
'notification'],
536 private function callArray(array $functions)
538 return function () use ($functions) {
539 $args = func_get_args();
540 foreach ($functions as $fn) {
541 call_user_func_array($fn, $args);