bug #30967 [HttpClient] Document the state object that is passed around by the HttpClient (derrabus)
This PR was merged into the 4.3-dev branch.
Discussion
----------
[HttpClient] Document the state object that is passed around by the HttpClient
| Q | A
| ------------- | ---
| Branch? | master
| Bug fix? | yes
| New feature? | no
| BC breaks? | no
| Deprecations? | no
| Tests pass? | yes
| Fixed tickets | N/A
| License | MIT
| Doc PR | N/A
In an attempt to make the code of the new HttpClient component more understandable, I've introduced internal classes that document the `$multi` object that is being passed around between *Client and *Response classes.
My goal is to make the code more accessible to potential contributors and static code analyzers.
Commits
-------
20f4eb3204
Document the state object that is passed around by the HttpClient.
This commit is contained in:
commit
9edd84bce0
@ -15,6 +15,8 @@ use Psr\Log\LoggerAwareInterface;
|
|||||||
use Psr\Log\LoggerAwareTrait;
|
use Psr\Log\LoggerAwareTrait;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Symfony\Component\HttpClient\Exception\TransportException;
|
use Symfony\Component\HttpClient\Exception\TransportException;
|
||||||
|
use Symfony\Component\HttpClient\Internal\CurlClientState;
|
||||||
|
use Symfony\Component\HttpClient\Internal\PushedResponse;
|
||||||
use Symfony\Component\HttpClient\Response\CurlResponse;
|
use Symfony\Component\HttpClient\Response\CurlResponse;
|
||||||
use Symfony\Component\HttpClient\Response\ResponseStream;
|
use Symfony\Component\HttpClient\Response\ResponseStream;
|
||||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||||
@ -37,6 +39,12 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface
|
|||||||
use LoggerAwareTrait;
|
use LoggerAwareTrait;
|
||||||
|
|
||||||
private $defaultOptions = self::OPTIONS_DEFAULTS;
|
private $defaultOptions = self::OPTIONS_DEFAULTS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An internal object to share state between the client and its responses.
|
||||||
|
*
|
||||||
|
* @var CurlClientState
|
||||||
|
*/
|
||||||
private $multi;
|
private $multi;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -56,22 +64,13 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface
|
|||||||
[, $this->defaultOptions] = self::prepareRequest(null, null, $defaultOptions, self::OPTIONS_DEFAULTS);
|
[, $this->defaultOptions] = self::prepareRequest(null, null, $defaultOptions, self::OPTIONS_DEFAULTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
$mh = curl_multi_init();
|
$this->multi = $multi = new CurlClientState();
|
||||||
|
|
||||||
// Don't enable HTTP/1.1 pipelining: it forces responses to be sent in order
|
// Don't enable HTTP/1.1 pipelining: it forces responses to be sent in order
|
||||||
if (\defined('CURLPIPE_MULTIPLEX')) {
|
if (\defined('CURLPIPE_MULTIPLEX')) {
|
||||||
curl_multi_setopt($mh, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX);
|
curl_multi_setopt($this->multi->handle, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX);
|
||||||
}
|
}
|
||||||
curl_multi_setopt($mh, CURLMOPT_MAX_HOST_CONNECTIONS, 0 < $maxHostConnections ? $maxHostConnections : PHP_INT_MAX);
|
curl_multi_setopt($this->multi->handle, CURLMOPT_MAX_HOST_CONNECTIONS, 0 < $maxHostConnections ? $maxHostConnections : PHP_INT_MAX);
|
||||||
|
|
||||||
// Use an internal stdClass object to share state between the client and its responses
|
|
||||||
$this->multi = $multi = (object) [
|
|
||||||
'openHandles' => [],
|
|
||||||
'handlesActivity' => [],
|
|
||||||
'handle' => $mh,
|
|
||||||
'pushedResponses' => [],
|
|
||||||
'dnsCache' => [[], [], []],
|
|
||||||
];
|
|
||||||
|
|
||||||
// Skip configuring HTTP/2 push when it's unsupported or buggy, see https://bugs.php.net/bug.php?id=77535
|
// Skip configuring HTTP/2 push when it's unsupported or buggy, see https://bugs.php.net/bug.php?id=77535
|
||||||
if (0 >= $maxPendingPushes || \PHP_VERSION_ID < 70217 || (\PHP_VERSION_ID >= 70300 && \PHP_VERSION_ID < 70304)) {
|
if (0 >= $maxPendingPushes || \PHP_VERSION_ID < 70217 || (\PHP_VERSION_ID >= 70300 && \PHP_VERSION_ID < 70304)) {
|
||||||
@ -85,7 +84,7 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface
|
|||||||
|
|
||||||
$logger = &$this->logger;
|
$logger = &$this->logger;
|
||||||
|
|
||||||
curl_multi_setopt($mh, CURLMOPT_PUSHFUNCTION, static function ($parent, $pushed, array $requestHeaders) use ($multi, $maxPendingPushes, &$logger) {
|
curl_multi_setopt($this->multi->handle, CURLMOPT_PUSHFUNCTION, static function ($parent, $pushed, array $requestHeaders) use ($multi, $maxPendingPushes, &$logger) {
|
||||||
return self::handlePush($parent, $pushed, $requestHeaders, $multi, $maxPendingPushes, $logger);
|
return self::handlePush($parent, $pushed, $requestHeaders, $multi, $maxPendingPushes, $logger);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -103,7 +102,7 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface
|
|||||||
$host = parse_url($authority, PHP_URL_HOST);
|
$host = parse_url($authority, PHP_URL_HOST);
|
||||||
$url = implode('', $url);
|
$url = implode('', $url);
|
||||||
|
|
||||||
if ([$pushedResponse, $pushedHeaders] = $this->multi->pushedResponses[$url] ?? null) {
|
if ($pushedResponse = $this->multi->pushedResponses[$url] ?? null) {
|
||||||
unset($this->multi->pushedResponses[$url]);
|
unset($this->multi->pushedResponses[$url]);
|
||||||
// Accept pushed responses only if their headers related to authentication match the request
|
// Accept pushed responses only if their headers related to authentication match the request
|
||||||
$expectedHeaders = [
|
$expectedHeaders = [
|
||||||
@ -113,13 +112,13 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface
|
|||||||
$options['headers']['range'] ?? null,
|
$options['headers']['range'] ?? null,
|
||||||
];
|
];
|
||||||
|
|
||||||
if ('GET' === $method && $expectedHeaders === $pushedHeaders && !$options['body']) {
|
if ('GET' === $method && $expectedHeaders === $pushedResponse->headers && !$options['body']) {
|
||||||
$this->logger && $this->logger->debug(sprintf('Connecting request to pushed response: "%s %s"', $method, $url));
|
$this->logger && $this->logger->debug(sprintf('Connecting request to pushed response: "%s %s"', $method, $url));
|
||||||
|
|
||||||
// Reinitialize the pushed response with request's options
|
// Reinitialize the pushed response with request's options
|
||||||
$pushedResponse->__construct($this->multi, $url, $options, $this->logger);
|
$pushedResponse->response->__construct($this->multi, $url, $options, $this->logger);
|
||||||
|
|
||||||
return $pushedResponse;
|
return $pushedResponse->response;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->logger && $this->logger->debug(sprintf('Rejecting pushed response for "%s": authorization headers don\'t match the request', $url));
|
$this->logger && $this->logger->debug(sprintf('Rejecting pushed response for "%s": authorization headers don\'t match the request', $url));
|
||||||
@ -159,14 +158,14 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
// curl's resolve feature varies by host:port but ours varies by host only, let's handle this with our own DNS map
|
// curl's resolve feature varies by host:port but ours varies by host only, let's handle this with our own DNS map
|
||||||
if (isset($this->multi->dnsCache[0][$host])) {
|
if (isset($this->multi->dnsCache->hostnames[$host])) {
|
||||||
$options['resolve'] += [$host => $this->multi->dnsCache[0][$host]];
|
$options['resolve'] += [$host => $this->multi->dnsCache->hostnames[$host]];
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($options['resolve'] || $this->multi->dnsCache[2]) {
|
if ($options['resolve'] || $this->multi->dnsCache->evictions) {
|
||||||
// First reset any old DNS cache entries then add the new ones
|
// First reset any old DNS cache entries then add the new ones
|
||||||
$resolve = $this->multi->dnsCache[2];
|
$resolve = $this->multi->dnsCache->evictions;
|
||||||
$this->multi->dnsCache[2] = [];
|
$this->multi->dnsCache->evictions = [];
|
||||||
$port = parse_url($authority, PHP_URL_PORT) ?: ('http:' === $scheme ? 80 : 443);
|
$port = parse_url($authority, PHP_URL_PORT) ?: ('http:' === $scheme ? 80 : 443);
|
||||||
|
|
||||||
if ($resolve && 0x072a00 > curl_version()['version_number']) {
|
if ($resolve && 0x072a00 > curl_version()['version_number']) {
|
||||||
@ -178,8 +177,8 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface
|
|||||||
|
|
||||||
foreach ($options['resolve'] as $host => $ip) {
|
foreach ($options['resolve'] as $host => $ip) {
|
||||||
$resolve[] = null === $ip ? "-$host:$port" : "$host:$port:$ip";
|
$resolve[] = null === $ip ? "-$host:$port" : "$host:$port:$ip";
|
||||||
$this->multi->dnsCache[0][$host] = $ip;
|
$this->multi->dnsCache->hostnames[$host] = $ip;
|
||||||
$this->multi->dnsCache[1]["-$host:$port"] = "-$host:$port";
|
$this->multi->dnsCache->removals["-$host:$port"] = "-$host:$port";
|
||||||
}
|
}
|
||||||
|
|
||||||
$curlopts[CURLOPT_RESOLVE] = $resolve;
|
$curlopts[CURLOPT_RESOLVE] = $resolve;
|
||||||
@ -299,7 +298,7 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function handlePush($parent, $pushed, array $requestHeaders, \stdClass $multi, int $maxPendingPushes, ?LoggerInterface $logger): int
|
private static function handlePush($parent, $pushed, array $requestHeaders, CurlClientState $multi, int $maxPendingPushes, ?LoggerInterface $logger): int
|
||||||
{
|
{
|
||||||
$headers = [];
|
$headers = [];
|
||||||
$origin = curl_getinfo($parent, CURLINFO_EFFECTIVE_URL);
|
$origin = curl_getinfo($parent, CURLINFO_EFFECTIVE_URL);
|
||||||
@ -336,15 +335,15 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface
|
|||||||
$url .= $headers[':path'];
|
$url .= $headers[':path'];
|
||||||
$logger && $logger->debug(sprintf('Queueing pushed response: "%s"', $url));
|
$logger && $logger->debug(sprintf('Queueing pushed response: "%s"', $url));
|
||||||
|
|
||||||
$multi->pushedResponses[$url] = [
|
$multi->pushedResponses[$url] = new PushedResponse(
|
||||||
new CurlResponse($multi, $pushed),
|
new CurlResponse($multi, $pushed),
|
||||||
[
|
[
|
||||||
$headers['authorization'] ?? null,
|
$headers['authorization'] ?? null,
|
||||||
$headers['cookie'] ?? null,
|
$headers['cookie'] ?? null,
|
||||||
$headers['x-requested-with'] ?? null,
|
$headers['x-requested-with'] ?? null,
|
||||||
null,
|
null,
|
||||||
],
|
]
|
||||||
];
|
);
|
||||||
|
|
||||||
return CURL_PUSH_OK;
|
return CURL_PUSH_OK;
|
||||||
}
|
}
|
||||||
|
25
src/Symfony/Component/HttpClient/Internal/ClientState.php
Normal file
25
src/Symfony/Component/HttpClient/Internal/ClientState.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpClient\Internal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal representation of the client state.
|
||||||
|
*
|
||||||
|
* @author Alexander M. Turek <me@derrabus.de>
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
class ClientState
|
||||||
|
{
|
||||||
|
public $handlesActivity = [];
|
||||||
|
public $openHandles = [];
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpClient\Internal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal representation of the cURL client's state.
|
||||||
|
*
|
||||||
|
* @author Alexander M. Turek <me@derrabus.de>
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class CurlClientState extends ClientState
|
||||||
|
{
|
||||||
|
/** @var resource */
|
||||||
|
public $handle;
|
||||||
|
/** @var PushedResponse[] */
|
||||||
|
public $pushedResponses = [];
|
||||||
|
/** @var DnsCache */
|
||||||
|
public $dnsCache;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->handle = curl_multi_init();
|
||||||
|
$this->dnsCache = new DnsCache();
|
||||||
|
}
|
||||||
|
}
|
39
src/Symfony/Component/HttpClient/Internal/DnsCache.php
Normal file
39
src/Symfony/Component/HttpClient/Internal/DnsCache.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpClient\Internal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache for resolved DNS queries.
|
||||||
|
*
|
||||||
|
* @author Alexander M. Turek <me@derrabus.de>
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class DnsCache
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Resolved hostnames (hostname => IP address).
|
||||||
|
*
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
public $hostnames = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
public $removals = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
public $evictions = [];
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpClient\Internal;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpClient\Response\NativeResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal representation of the native client's state.
|
||||||
|
*
|
||||||
|
* @author Alexander M. Turek <me@derrabus.de>
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class NativeClientState extends ClientState
|
||||||
|
{
|
||||||
|
/** @var int */
|
||||||
|
public $id;
|
||||||
|
/** @var NativeResponse[] */
|
||||||
|
public $pendingResponses = [];
|
||||||
|
/** @var int */
|
||||||
|
public $maxHostConnections = PHP_INT_MAX;
|
||||||
|
/** @var int */
|
||||||
|
public $responseCount = 0;
|
||||||
|
/** @var string[] */
|
||||||
|
public $dnsCache = [];
|
||||||
|
/** @var resource[] */
|
||||||
|
public $handles = [];
|
||||||
|
/** @var bool */
|
||||||
|
public $sleep = false;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->id = random_int(PHP_INT_MIN, PHP_INT_MAX);
|
||||||
|
}
|
||||||
|
}
|
36
src/Symfony/Component/HttpClient/Internal/PushedResponse.php
Normal file
36
src/Symfony/Component/HttpClient/Internal/PushedResponse.php
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpClient\Internal;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpClient\Response\CurlResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A pushed response with headers.
|
||||||
|
*
|
||||||
|
* @author Alexander M. Turek <me@derrabus.de>
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class PushedResponse
|
||||||
|
{
|
||||||
|
/** @var CurlResponse */
|
||||||
|
public $response;
|
||||||
|
|
||||||
|
/** @var string[] */
|
||||||
|
public $headers;
|
||||||
|
|
||||||
|
public function __construct(CurlResponse $response, array $headers)
|
||||||
|
{
|
||||||
|
$this->response = $response;
|
||||||
|
$this->headers = $headers;
|
||||||
|
}
|
||||||
|
}
|
@ -14,6 +14,7 @@ namespace Symfony\Component\HttpClient;
|
|||||||
use Psr\Log\LoggerAwareInterface;
|
use Psr\Log\LoggerAwareInterface;
|
||||||
use Psr\Log\LoggerAwareTrait;
|
use Psr\Log\LoggerAwareTrait;
|
||||||
use Symfony\Component\HttpClient\Exception\TransportException;
|
use Symfony\Component\HttpClient\Exception\TransportException;
|
||||||
|
use Symfony\Component\HttpClient\Internal\NativeClientState;
|
||||||
use Symfony\Component\HttpClient\Response\NativeResponse;
|
use Symfony\Component\HttpClient\Response\NativeResponse;
|
||||||
use Symfony\Component\HttpClient\Response\ResponseStream;
|
use Symfony\Component\HttpClient\Response\ResponseStream;
|
||||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||||
@ -36,6 +37,8 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac
|
|||||||
use LoggerAwareTrait;
|
use LoggerAwareTrait;
|
||||||
|
|
||||||
private $defaultOptions = self::OPTIONS_DEFAULTS;
|
private $defaultOptions = self::OPTIONS_DEFAULTS;
|
||||||
|
|
||||||
|
/** @var NativeClientState */
|
||||||
private $multi;
|
private $multi;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -50,18 +53,8 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac
|
|||||||
[, $this->defaultOptions] = self::prepareRequest(null, null, $defaultOptions, self::OPTIONS_DEFAULTS);
|
[, $this->defaultOptions] = self::prepareRequest(null, null, $defaultOptions, self::OPTIONS_DEFAULTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use an internal stdClass object to share state between the client and its responses
|
$this->multi = new NativeClientState();
|
||||||
$this->multi = (object) [
|
$this->multi->maxHostConnections = 0 < $maxHostConnections ? $maxHostConnections : PHP_INT_MAX;
|
||||||
'openHandles' => [],
|
|
||||||
'handlesActivity' => [],
|
|
||||||
'pendingResponses' => [],
|
|
||||||
'maxHostConnections' => 0 < $maxHostConnections ? $maxHostConnections : PHP_INT_MAX,
|
|
||||||
'responseCount' => 0,
|
|
||||||
'dnsCache' => [],
|
|
||||||
'handles' => [],
|
|
||||||
'sleep' => false,
|
|
||||||
'id' => random_int(PHP_INT_MIN, PHP_INT_MAX),
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -291,7 +284,7 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac
|
|||||||
/**
|
/**
|
||||||
* Resolves the IP of the host using the local DNS cache if possible.
|
* Resolves the IP of the host using the local DNS cache if possible.
|
||||||
*/
|
*/
|
||||||
private static function dnsResolve(array $url, \stdClass $multi, array &$info, ?\Closure $onProgress): array
|
private static function dnsResolve(array $url, NativeClientState $multi, array &$info, ?\Closure $onProgress): array
|
||||||
{
|
{
|
||||||
if ($port = parse_url($url['authority'], PHP_URL_PORT) ?: '') {
|
if ($port = parse_url($url['authority'], PHP_URL_PORT) ?: '') {
|
||||||
$info['primary_port'] = $port;
|
$info['primary_port'] = $port;
|
||||||
@ -342,7 +335,7 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return static function (\stdClass $multi, ?string $location, $context) use ($redirectHeaders, $proxy, $noProxy, &$info, $maxRedirects, $onProgress): ?string {
|
return static function (NativeClientState $multi, ?string $location, $context) use ($redirectHeaders, $proxy, $noProxy, &$info, $maxRedirects, $onProgress): ?string {
|
||||||
if (null === $location || $info['http_code'] < 300 || 400 <= $info['http_code']) {
|
if (null === $location || $info['http_code'] < 300 || 400 <= $info['http_code']) {
|
||||||
$info['redirect_url'] = null;
|
$info['redirect_url'] = null;
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ namespace Symfony\Component\HttpClient\Response;
|
|||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Symfony\Component\HttpClient\Chunk\FirstChunk;
|
use Symfony\Component\HttpClient\Chunk\FirstChunk;
|
||||||
use Symfony\Component\HttpClient\Exception\TransportException;
|
use Symfony\Component\HttpClient\Exception\TransportException;
|
||||||
|
use Symfony\Component\HttpClient\Internal\CurlClientState;
|
||||||
use Symfony\Contracts\HttpClient\ResponseInterface;
|
use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -26,11 +27,12 @@ final class CurlResponse implements ResponseInterface
|
|||||||
use ResponseTrait;
|
use ResponseTrait;
|
||||||
|
|
||||||
private static $performing = false;
|
private static $performing = false;
|
||||||
|
private $multi;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
public function __construct(\stdClass $multi, $ch, array $options = null, LoggerInterface $logger = null, string $method = 'GET', callable $resolveRedirect = null)
|
public function __construct(CurlClientState $multi, $ch, array $options = null, LoggerInterface $logger = null, string $method = 'GET', callable $resolveRedirect = null)
|
||||||
{
|
{
|
||||||
$this->multi = $multi;
|
$this->multi = $multi;
|
||||||
|
|
||||||
@ -186,8 +188,8 @@ final class CurlResponse implements ResponseInterface
|
|||||||
|
|
||||||
$this->multi->pushedResponses = [];
|
$this->multi->pushedResponses = [];
|
||||||
// Schedule DNS cache eviction for the next request
|
// Schedule DNS cache eviction for the next request
|
||||||
$this->multi->dnsCache[2] = $this->multi->dnsCache[2] ?: $this->multi->dnsCache[1];
|
$this->multi->dnsCache->evictions = $this->multi->dnsCache->evictions ?: $this->multi->dnsCache->removals;
|
||||||
$this->multi->dnsCache[1] = $this->multi->dnsCache[0] = [];
|
$this->multi->dnsCache->removals = $this->multi->dnsCache->hostnames = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -195,7 +197,7 @@ final class CurlResponse implements ResponseInterface
|
|||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
protected function close(): void
|
private function close(): void
|
||||||
{
|
{
|
||||||
unset($this->multi->openHandles[$this->id], $this->multi->handlesActivity[$this->id]);
|
unset($this->multi->openHandles[$this->id], $this->multi->handlesActivity[$this->id]);
|
||||||
curl_multi_remove_handle($this->multi->handle, $this->handle);
|
curl_multi_remove_handle($this->multi->handle, $this->handle);
|
||||||
@ -213,7 +215,7 @@ final class CurlResponse implements ResponseInterface
|
|||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
protected static function schedule(self $response, array &$runningResponses): void
|
private static function schedule(self $response, array &$runningResponses): void
|
||||||
{
|
{
|
||||||
if (isset($runningResponses[$i = (int) $response->multi->handle])) {
|
if (isset($runningResponses[$i = (int) $response->multi->handle])) {
|
||||||
$runningResponses[$i][1][$response->id] = $response;
|
$runningResponses[$i][1][$response->id] = $response;
|
||||||
@ -231,7 +233,7 @@ final class CurlResponse implements ResponseInterface
|
|||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
protected static function perform(\stdClass $multi, array &$responses = null): void
|
private static function perform(CurlClientState $multi, array &$responses = null): void
|
||||||
{
|
{
|
||||||
if (self::$performing) {
|
if (self::$performing) {
|
||||||
return;
|
return;
|
||||||
@ -253,7 +255,7 @@ final class CurlResponse implements ResponseInterface
|
|||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
protected static function select(\stdClass $multi, float $timeout): int
|
private static function select(CurlClientState $multi, float $timeout): int
|
||||||
{
|
{
|
||||||
return curl_multi_select($multi->handle, $timeout);
|
return curl_multi_select($multi->handle, $timeout);
|
||||||
}
|
}
|
||||||
@ -261,7 +263,7 @@ final class CurlResponse implements ResponseInterface
|
|||||||
/**
|
/**
|
||||||
* Parses header lines as curl yields them to us.
|
* Parses header lines as curl yields them to us.
|
||||||
*/
|
*/
|
||||||
private static function parseHeaderLine($ch, string $data, array &$info, array &$headers, ?array $options, \stdClass $multi, int $id, ?string &$location, ?callable $resolveRedirect, ?LoggerInterface $logger): int
|
private static function parseHeaderLine($ch, string $data, array &$info, array &$headers, ?array $options, CurlClientState $multi, int $id, ?string &$location, ?callable $resolveRedirect, ?LoggerInterface $logger): int
|
||||||
{
|
{
|
||||||
if (!\in_array($waitFor = @curl_getinfo($ch, CURLINFO_PRIVATE), ['headers', 'destruct'], true)) {
|
if (!\in_array($waitFor = @curl_getinfo($ch, CURLINFO_PRIVATE), ['headers', 'destruct'], true)) {
|
||||||
return \strlen($data); // Ignore HTTP trailers
|
return \strlen($data); // Ignore HTTP trailers
|
||||||
@ -295,11 +297,11 @@ final class CurlResponse implements ResponseInterface
|
|||||||
$info['redirect_url'] = $resolveRedirect($ch, $location);
|
$info['redirect_url'] = $resolveRedirect($ch, $location);
|
||||||
$url = parse_url($location ?? ':');
|
$url = parse_url($location ?? ':');
|
||||||
|
|
||||||
if (isset($url['host']) && null !== $ip = $multi->dnsCache[0][$url['host'] = strtolower($url['host'])] ?? null) {
|
if (isset($url['host']) && null !== $ip = $multi->dnsCache->hostnames[$url['host'] = strtolower($url['host'])] ?? null) {
|
||||||
// Populate DNS cache for redirects if needed
|
// Populate DNS cache for redirects if needed
|
||||||
$port = $url['port'] ?? ('http' === ($url['scheme'] ?? parse_url(curl_getinfo($ch, CURLINFO_EFFECTIVE_URL), PHP_URL_SCHEME)) ? 80 : 443);
|
$port = $url['port'] ?? ('http' === ($url['scheme'] ?? parse_url(curl_getinfo($ch, CURLINFO_EFFECTIVE_URL), PHP_URL_SCHEME)) ? 80 : 443);
|
||||||
curl_setopt($ch, CURLOPT_RESOLVE, ["{$url['host']}:$port:$ip"]);
|
curl_setopt($ch, CURLOPT_RESOLVE, ["{$url['host']}:$port:$ip"]);
|
||||||
$multi->dnsCache[1]["-{$url['host']}:$port"] = "-{$url['host']}:$port";
|
$multi->dnsCache->removals["-{$url['host']}:$port"] = "-{$url['host']}:$port";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ use Symfony\Component\HttpClient\Chunk\ErrorChunk;
|
|||||||
use Symfony\Component\HttpClient\Chunk\FirstChunk;
|
use Symfony\Component\HttpClient\Chunk\FirstChunk;
|
||||||
use Symfony\Component\HttpClient\Exception\InvalidArgumentException;
|
use Symfony\Component\HttpClient\Exception\InvalidArgumentException;
|
||||||
use Symfony\Component\HttpClient\Exception\TransportException;
|
use Symfony\Component\HttpClient\Exception\TransportException;
|
||||||
|
use Symfony\Component\HttpClient\Internal\ClientState;
|
||||||
use Symfony\Contracts\HttpClient\ResponseInterface;
|
use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -130,10 +131,7 @@ class MockResponse implements ResponseInterface
|
|||||||
throw new InvalidArgumentException('MockResponse instances must be issued by MockHttpClient before processing.');
|
throw new InvalidArgumentException('MockResponse instances must be issued by MockHttpClient before processing.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$multi = self::$mainMulti ?? self::$mainMulti = (object) [
|
$multi = self::$mainMulti ?? self::$mainMulti = new ClientState();
|
||||||
'handlesActivity' => [],
|
|
||||||
'openHandles' => [],
|
|
||||||
];
|
|
||||||
|
|
||||||
if (!isset($runningResponses[0])) {
|
if (!isset($runningResponses[0])) {
|
||||||
$runningResponses[0] = [$multi, []];
|
$runningResponses[0] = [$multi, []];
|
||||||
@ -145,7 +143,7 @@ class MockResponse implements ResponseInterface
|
|||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
protected static function perform(\stdClass $multi, array &$responses): void
|
protected static function perform(ClientState $multi, array &$responses): void
|
||||||
{
|
{
|
||||||
foreach ($responses as $response) {
|
foreach ($responses as $response) {
|
||||||
$id = $response->id;
|
$id = $response->id;
|
||||||
@ -185,7 +183,7 @@ class MockResponse implements ResponseInterface
|
|||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
protected static function select(\stdClass $multi, float $timeout): int
|
protected static function select(ClientState $multi, float $timeout): int
|
||||||
{
|
{
|
||||||
return 42;
|
return 42;
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ namespace Symfony\Component\HttpClient\Response;
|
|||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Symfony\Component\HttpClient\Chunk\FirstChunk;
|
use Symfony\Component\HttpClient\Chunk\FirstChunk;
|
||||||
use Symfony\Component\HttpClient\Exception\TransportException;
|
use Symfony\Component\HttpClient\Exception\TransportException;
|
||||||
|
use Symfony\Component\HttpClient\Internal\NativeClientState;
|
||||||
use Symfony\Contracts\HttpClient\ResponseInterface;
|
use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -32,11 +33,12 @@ final class NativeResponse implements ResponseInterface
|
|||||||
private $remaining;
|
private $remaining;
|
||||||
private $buffer;
|
private $buffer;
|
||||||
private $inflate;
|
private $inflate;
|
||||||
|
private $multi;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
public function __construct(\stdClass $multi, $context, string $url, $options, bool $gzipEnabled, array &$info, callable $resolveRedirect, ?callable $onProgress, ?LoggerInterface $logger)
|
public function __construct(NativeClientState $multi, $context, string $url, $options, bool $gzipEnabled, array &$info, callable $resolveRedirect, ?callable $onProgress, ?LoggerInterface $logger)
|
||||||
{
|
{
|
||||||
$this->multi = $multi;
|
$this->multi = $multi;
|
||||||
$this->id = (int) $context;
|
$this->id = (int) $context;
|
||||||
@ -193,7 +195,7 @@ final class NativeResponse implements ResponseInterface
|
|||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
private static function perform(\stdClass $multi, array &$responses = null): void
|
private static function perform(NativeClientState $multi, array &$responses = null): void
|
||||||
{
|
{
|
||||||
// List of native handles for stream_select()
|
// List of native handles for stream_select()
|
||||||
if (null !== $responses) {
|
if (null !== $responses) {
|
||||||
@ -283,6 +285,7 @@ final class NativeResponse implements ResponseInterface
|
|||||||
|
|
||||||
if ($multi->pendingResponses && \count($multi->handles) < $multi->maxHostConnections) {
|
if ($multi->pendingResponses && \count($multi->handles) < $multi->maxHostConnections) {
|
||||||
// Open the next pending request - this is a blocking operation so we do only one of them
|
// Open the next pending request - this is a blocking operation so we do only one of them
|
||||||
|
/** @var self $response */
|
||||||
$response = array_shift($multi->pendingResponses);
|
$response = array_shift($multi->pendingResponses);
|
||||||
$response->open();
|
$response->open();
|
||||||
$responses[$response->id] = $response;
|
$responses[$response->id] = $response;
|
||||||
@ -305,7 +308,7 @@ final class NativeResponse implements ResponseInterface
|
|||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
private static function select(\stdClass $multi, float $timeout): int
|
private static function select(NativeClientState $multi, float $timeout): int
|
||||||
{
|
{
|
||||||
$_ = [];
|
$_ = [];
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ use Symfony\Component\HttpClient\Exception\JsonException;
|
|||||||
use Symfony\Component\HttpClient\Exception\RedirectionException;
|
use Symfony\Component\HttpClient\Exception\RedirectionException;
|
||||||
use Symfony\Component\HttpClient\Exception\ServerException;
|
use Symfony\Component\HttpClient\Exception\ServerException;
|
||||||
use Symfony\Component\HttpClient\Exception\TransportException;
|
use Symfony\Component\HttpClient\Exception\TransportException;
|
||||||
|
use Symfony\Component\HttpClient\Internal\ClientState;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements the common logic for response classes.
|
* Implements the common logic for response classes.
|
||||||
@ -49,7 +50,7 @@ trait ResponseTrait
|
|||||||
'error' => null,
|
'error' => null,
|
||||||
];
|
];
|
||||||
|
|
||||||
private $multi;
|
/** @var resource */
|
||||||
private $handle;
|
private $handle;
|
||||||
private $id;
|
private $id;
|
||||||
private $timeout;
|
private $timeout;
|
||||||
@ -181,12 +182,12 @@ trait ResponseTrait
|
|||||||
/**
|
/**
|
||||||
* Performs all pending non-blocking operations.
|
* Performs all pending non-blocking operations.
|
||||||
*/
|
*/
|
||||||
abstract protected static function perform(\stdClass $multi, array &$responses): void;
|
abstract protected static function perform(ClientState $multi, array &$responses): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Waits for network activity.
|
* Waits for network activity.
|
||||||
*/
|
*/
|
||||||
abstract protected static function select(\stdClass $multi, float $timeout): int;
|
abstract protected static function select(ClientState $multi, float $timeout): int;
|
||||||
|
|
||||||
private static function addResponseHeaders(array $responseHeaders, array &$info, array &$headers): void
|
private static function addResponseHeaders(array $responseHeaders, array &$info, array &$headers): void
|
||||||
{
|
{
|
||||||
@ -254,6 +255,7 @@ trait ResponseTrait
|
|||||||
$timeoutMax = 0;
|
$timeoutMax = 0;
|
||||||
$timeoutMin = $timeout ?? INF;
|
$timeoutMin = $timeout ?? INF;
|
||||||
|
|
||||||
|
/** @var ClientState $multi */
|
||||||
foreach ($runningResponses as $i => [$multi]) {
|
foreach ($runningResponses as $i => [$multi]) {
|
||||||
$responses = &$runningResponses[$i][1];
|
$responses = &$runningResponses[$i][1];
|
||||||
self::perform($multi, $responses);
|
self::perform($multi, $responses);
|
||||||
|
Reference in New Issue
Block a user