Document the state object that is passed around by the HttpClient.
This commit is contained in:
parent
2243bf5bc1
commit
20f4eb3204
@ -15,6 +15,8 @@ use Psr\Log\LoggerAwareInterface;
|
||||
use Psr\Log\LoggerAwareTrait;
|
||||
use Psr\Log\LoggerInterface;
|
||||
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\ResponseStream;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
@ -37,6 +39,12 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface
|
||||
use LoggerAwareTrait;
|
||||
|
||||
private $defaultOptions = self::OPTIONS_DEFAULTS;
|
||||
|
||||
/**
|
||||
* An internal object to share state between the client and its responses.
|
||||
*
|
||||
* @var CurlClientState
|
||||
*/
|
||||
private $multi;
|
||||
|
||||
/**
|
||||
@ -56,22 +64,13 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface
|
||||
[, $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
|
||||
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);
|
||||
|
||||
// Use an internal stdClass object to share state between the client and its responses
|
||||
$this->multi = $multi = (object) [
|
||||
'openHandles' => [],
|
||||
'handlesActivity' => [],
|
||||
'handle' => $mh,
|
||||
'pushedResponses' => [],
|
||||
'dnsCache' => [[], [], []],
|
||||
];
|
||||
curl_multi_setopt($this->multi->handle, CURLMOPT_MAX_HOST_CONNECTIONS, 0 < $maxHostConnections ? $maxHostConnections : PHP_INT_MAX);
|
||||
|
||||
// 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)) {
|
||||
@ -85,7 +84,7 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface
|
||||
|
||||
$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);
|
||||
});
|
||||
}
|
||||
@ -103,7 +102,7 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface
|
||||
$host = parse_url($authority, PHP_URL_HOST);
|
||||
$url = implode('', $url);
|
||||
|
||||
if ([$pushedResponse, $pushedHeaders] = $this->multi->pushedResponses[$url] ?? null) {
|
||||
if ($pushedResponse = $this->multi->pushedResponses[$url] ?? null) {
|
||||
unset($this->multi->pushedResponses[$url]);
|
||||
// Accept pushed responses only if their headers related to authentication match the request
|
||||
$expectedHeaders = [
|
||||
@ -113,13 +112,13 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface
|
||||
$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));
|
||||
|
||||
// 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));
|
||||
@ -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
|
||||
if (isset($this->multi->dnsCache[0][$host])) {
|
||||
$options['resolve'] += [$host => $this->multi->dnsCache[0][$host]];
|
||||
if (isset($this->multi->dnsCache->hostnames[$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
|
||||
$resolve = $this->multi->dnsCache[2];
|
||||
$this->multi->dnsCache[2] = [];
|
||||
$resolve = $this->multi->dnsCache->evictions;
|
||||
$this->multi->dnsCache->evictions = [];
|
||||
$port = parse_url($authority, PHP_URL_PORT) ?: ('http:' === $scheme ? 80 : 443);
|
||||
|
||||
if ($resolve && 0x072a00 > curl_version()['version_number']) {
|
||||
@ -178,8 +177,8 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface
|
||||
|
||||
foreach ($options['resolve'] as $host => $ip) {
|
||||
$resolve[] = null === $ip ? "-$host:$port" : "$host:$port:$ip";
|
||||
$this->multi->dnsCache[0][$host] = $ip;
|
||||
$this->multi->dnsCache[1]["-$host:$port"] = "-$host:$port";
|
||||
$this->multi->dnsCache->hostnames[$host] = $ip;
|
||||
$this->multi->dnsCache->removals["-$host:$port"] = "-$host:$port";
|
||||
}
|
||||
|
||||
$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 = [];
|
||||
$origin = curl_getinfo($parent, CURLINFO_EFFECTIVE_URL);
|
||||
@ -336,15 +335,15 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface
|
||||
$url .= $headers[':path'];
|
||||
$logger && $logger->debug(sprintf('Queueing pushed response: "%s"', $url));
|
||||
|
||||
$multi->pushedResponses[$url] = [
|
||||
$multi->pushedResponses[$url] = new PushedResponse(
|
||||
new CurlResponse($multi, $pushed),
|
||||
[
|
||||
$headers['authorization'] ?? null,
|
||||
$headers['cookie'] ?? null,
|
||||
$headers['x-requested-with'] ?? null,
|
||||
null,
|
||||
],
|
||||
];
|
||||
]
|
||||
);
|
||||
|
||||
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\LoggerAwareTrait;
|
||||
use Symfony\Component\HttpClient\Exception\TransportException;
|
||||
use Symfony\Component\HttpClient\Internal\NativeClientState;
|
||||
use Symfony\Component\HttpClient\Response\NativeResponse;
|
||||
use Symfony\Component\HttpClient\Response\ResponseStream;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
@ -36,6 +37,8 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac
|
||||
use LoggerAwareTrait;
|
||||
|
||||
private $defaultOptions = self::OPTIONS_DEFAULTS;
|
||||
|
||||
/** @var NativeClientState */
|
||||
private $multi;
|
||||
|
||||
/**
|
||||
@ -50,18 +53,8 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac
|
||||
[, $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 = (object) [
|
||||
'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),
|
||||
];
|
||||
$this->multi = new NativeClientState();
|
||||
$this->multi->maxHostConnections = 0 < $maxHostConnections ? $maxHostConnections : PHP_INT_MAX;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -292,7 +285,7 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac
|
||||
/**
|
||||
* 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) ?: '') {
|
||||
$info['primary_port'] = $port;
|
||||
@ -343,7 +336,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']) {
|
||||
$info['redirect_url'] = null;
|
||||
|
||||
|
@ -14,6 +14,7 @@ namespace Symfony\Component\HttpClient\Response;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\HttpClient\Chunk\FirstChunk;
|
||||
use Symfony\Component\HttpClient\Exception\TransportException;
|
||||
use Symfony\Component\HttpClient\Internal\CurlClientState;
|
||||
use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
|
||||
/**
|
||||
@ -26,11 +27,12 @@ final class CurlResponse implements ResponseInterface
|
||||
use ResponseTrait;
|
||||
|
||||
private static $performing = false;
|
||||
private $multi;
|
||||
|
||||
/**
|
||||
* @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;
|
||||
|
||||
@ -186,8 +188,8 @@ final class CurlResponse implements ResponseInterface
|
||||
|
||||
$this->multi->pushedResponses = [];
|
||||
// Schedule DNS cache eviction for the next request
|
||||
$this->multi->dnsCache[2] = $this->multi->dnsCache[2] ?: $this->multi->dnsCache[1];
|
||||
$this->multi->dnsCache[1] = $this->multi->dnsCache[0] = [];
|
||||
$this->multi->dnsCache->evictions = $this->multi->dnsCache->evictions ?: $this->multi->dnsCache->removals;
|
||||
$this->multi->dnsCache->removals = $this->multi->dnsCache->hostnames = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -195,7 +197,7 @@ final class CurlResponse implements ResponseInterface
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function close(): void
|
||||
private function close(): void
|
||||
{
|
||||
unset($this->multi->openHandles[$this->id], $this->multi->handlesActivity[$this->id]);
|
||||
curl_multi_remove_handle($this->multi->handle, $this->handle);
|
||||
@ -213,7 +215,7 @@ final class CurlResponse implements ResponseInterface
|
||||
/**
|
||||
* {@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])) {
|
||||
$runningResponses[$i][1][$response->id] = $response;
|
||||
@ -231,7 +233,7 @@ final class CurlResponse implements ResponseInterface
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static function perform(\stdClass $multi, array &$responses = null): void
|
||||
private static function perform(CurlClientState $multi, array &$responses = null): void
|
||||
{
|
||||
if (self::$performing) {
|
||||
return;
|
||||
@ -253,7 +255,7 @@ final class CurlResponse implements ResponseInterface
|
||||
/**
|
||||
* {@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);
|
||||
}
|
||||
@ -261,7 +263,7 @@ final class CurlResponse implements ResponseInterface
|
||||
/**
|
||||
* 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)) {
|
||||
return \strlen($data); // Ignore HTTP trailers
|
||||
@ -295,11 +297,11 @@ final class CurlResponse implements ResponseInterface
|
||||
$info['redirect_url'] = $resolveRedirect($ch, $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
|
||||
$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"]);
|
||||
$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\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\HttpClient\Exception\TransportException;
|
||||
use Symfony\Component\HttpClient\Internal\ClientState;
|
||||
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.');
|
||||
}
|
||||
|
||||
$multi = self::$mainMulti ?? self::$mainMulti = (object) [
|
||||
'handlesActivity' => [],
|
||||
'openHandles' => [],
|
||||
];
|
||||
$multi = self::$mainMulti ?? self::$mainMulti = new ClientState();
|
||||
|
||||
if (!isset($runningResponses[0])) {
|
||||
$runningResponses[0] = [$multi, []];
|
||||
@ -145,7 +143,7 @@ class MockResponse implements ResponseInterface
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static function perform(\stdClass $multi, array &$responses): void
|
||||
protected static function perform(ClientState $multi, array &$responses): void
|
||||
{
|
||||
foreach ($responses as $response) {
|
||||
$id = $response->id;
|
||||
@ -185,7 +183,7 @@ class MockResponse implements ResponseInterface
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static function select(\stdClass $multi, float $timeout): int
|
||||
protected static function select(ClientState $multi, float $timeout): int
|
||||
{
|
||||
return 42;
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ namespace Symfony\Component\HttpClient\Response;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\HttpClient\Chunk\FirstChunk;
|
||||
use Symfony\Component\HttpClient\Exception\TransportException;
|
||||
use Symfony\Component\HttpClient\Internal\NativeClientState;
|
||||
use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
|
||||
/**
|
||||
@ -32,11 +33,12 @@ final class NativeResponse implements ResponseInterface
|
||||
private $remaining;
|
||||
private $buffer;
|
||||
private $inflate;
|
||||
private $multi;
|
||||
|
||||
/**
|
||||
* @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->id = (int) $context;
|
||||
@ -193,7 +195,7 @@ final class NativeResponse implements ResponseInterface
|
||||
/**
|
||||
* {@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()
|
||||
if (null !== $responses) {
|
||||
@ -283,6 +285,7 @@ final class NativeResponse implements ResponseInterface
|
||||
|
||||
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
|
||||
/** @var self $response */
|
||||
$response = array_shift($multi->pendingResponses);
|
||||
$response->open();
|
||||
$responses[$response->id] = $response;
|
||||
@ -305,7 +308,7 @@ final class NativeResponse implements ResponseInterface
|
||||
/**
|
||||
* {@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\ServerException;
|
||||
use Symfony\Component\HttpClient\Exception\TransportException;
|
||||
use Symfony\Component\HttpClient\Internal\ClientState;
|
||||
|
||||
/**
|
||||
* Implements the common logic for response classes.
|
||||
@ -49,7 +50,7 @@ trait ResponseTrait
|
||||
'error' => null,
|
||||
];
|
||||
|
||||
private $multi;
|
||||
/** @var resource */
|
||||
private $handle;
|
||||
private $id;
|
||||
private $timeout;
|
||||
@ -181,12 +182,12 @@ trait ResponseTrait
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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
|
||||
{
|
||||
@ -254,6 +255,7 @@ trait ResponseTrait
|
||||
$timeoutMax = 0;
|
||||
$timeoutMin = $timeout ?? INF;
|
||||
|
||||
/** @var ClientState $multi */
|
||||
foreach ($runningResponses as $i => [$multi]) {
|
||||
$responses = &$runningResponses[$i][1];
|
||||
self::perform($multi, $responses);
|
||||
|
Reference in New Issue
Block a user