[HttpClient] Add HttpClientInterface::withOptions()

This commit is contained in:
Nicolas Grekas 2021-02-25 16:41:26 +01:00
parent e872db4a1c
commit 439742ff33
22 changed files with 161 additions and 41 deletions

View File

@ -188,7 +188,7 @@
"url": "src/Symfony/Contracts", "url": "src/Symfony/Contracts",
"options": { "options": {
"versions": { "versions": {
"symfony/contracts": "2.3.x-dev" "symfony/contracts": "2.4.x-dev"
} }
} }
} }

View File

@ -51,4 +51,15 @@ trait AsyncDecoratorTrait
return new ResponseStream(AsyncResponse::stream($responses, $timeout, static::class)); return new ResponseStream(AsyncResponse::stream($responses, $timeout, static::class));
} }
/**
* {@inheritdoc}
*/
public function withOptions(array $options): self
{
$clone = clone $this;
$clone->client = $this->client->withOptions($options);
return $clone;
}
} }

View File

@ -1,6 +1,11 @@
CHANGELOG CHANGELOG
========= =========
5.3
---
* Implement `HttpClientInterface::withOptions()` from `symfony/contracts` v2.4
5.2.0 5.2.0
----- -----

View File

@ -341,30 +341,8 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface,
public function reset() public function reset()
{ {
if ($this->logger) { $this->multi->logger = $this->logger;
foreach ($this->multi->pushedResponses as $url => $response) { $this->multi->reset();
$this->logger->debug(sprintf('Unused pushed response: "%s"', $url));
}
}
$this->multi->pushedResponses = [];
$this->multi->dnsCache->evictions = $this->multi->dnsCache->evictions ?: $this->multi->dnsCache->removals;
$this->multi->dnsCache->removals = $this->multi->dnsCache->hostnames = [];
if (\is_resource($this->multi->handle) || $this->multi->handle instanceof \CurlMultiHandle) {
if (\defined('CURLMOPT_PUSHFUNCTION')) {
curl_multi_setopt($this->multi->handle, \CURLMOPT_PUSHFUNCTION, null);
}
$active = 0;
while (\CURLM_CALL_MULTI_PERFORM === curl_multi_exec($this->multi->handle, $active));
}
foreach ($this->multi->openHandles as [$ch]) {
if (\is_resource($ch) || $ch instanceof \CurlHandle) {
curl_setopt($ch, \CURLOPT_VERBOSE, false);
}
}
} }
public function __sleep() public function __sleep()
@ -379,7 +357,7 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface,
public function __destruct() public function __destruct()
{ {
$this->reset(); $this->multi->logger = $this->logger;
} }
private function handlePush($parent, $pushed, array $requestHeaders, int $maxPendingPushes): int private function handlePush($parent, $pushed, array $requestHeaders, int $maxPendingPushes): int

View File

@ -26,8 +26,9 @@ use Symfony\Contracts\HttpClient\ResponseInterface;
*/ */
final class EventSourceHttpClient implements HttpClientInterface final class EventSourceHttpClient implements HttpClientInterface
{ {
use AsyncDecoratorTrait; use AsyncDecoratorTrait, HttpClientTrait {
use HttpClientTrait; AsyncDecoratorTrait::withOptions insteadof HttpClientTrait;
}
private $reconnectionTime; private $reconnectionTime;

View File

@ -17,7 +17,7 @@ use Symfony\Component\HttpClient\Exception\TransportException;
/** /**
* Provides the common logic from writing HttpClientInterface implementations. * Provides the common logic from writing HttpClientInterface implementations.
* *
* All methods are static to prevent implementers from creating memory leaks via circular references. * All private methods are static to prevent implementers from creating memory leaks via circular references.
* *
* @author Nicolas Grekas <p@tchwork.com> * @author Nicolas Grekas <p@tchwork.com>
*/ */
@ -25,6 +25,17 @@ trait HttpClientTrait
{ {
private static $CHUNK_SIZE = 16372; private static $CHUNK_SIZE = 16372;
/**
* {@inheritdoc}
*/
public function withOptions(array $options): self
{
$clone = clone $this;
$clone->defaultOptions = self::mergeDefaultOptions($options, $this->defaultOptions);
return $clone;
}
/** /**
* Validates and normalizes method, URL and options, and merges them with defaults. * Validates and normalizes method, URL and options, and merges them with defaults.
* *

View File

@ -11,6 +11,8 @@
namespace Symfony\Component\HttpClient\Internal; namespace Symfony\Component\HttpClient\Internal;
use Psr\Log\LoggerInterface;
/** /**
* Internal representation of the cURL client's state. * Internal representation of the cURL client's state.
* *
@ -29,10 +31,55 @@ final class CurlClientState extends ClientState
/** @var float[] */ /** @var float[] */
public $pauseExpiries = []; public $pauseExpiries = [];
public $execCounter = \PHP_INT_MIN; public $execCounter = \PHP_INT_MIN;
/** @var LoggerInterface|null */
public $logger;
public function __construct() public function __construct()
{ {
$this->handle = curl_multi_init(); $this->handle = curl_multi_init();
$this->dnsCache = new DnsCache(); $this->dnsCache = new DnsCache();
} }
public function reset()
{
if ($this->logger) {
foreach ($this->pushedResponses as $url => $response) {
$this->logger->debug(sprintf('Unused pushed response: "%s"', $url));
}
}
$this->pushedResponses = [];
$this->dnsCache->evictions = $this->dnsCache->evictions ?: $this->dnsCache->removals;
$this->dnsCache->removals = $this->dnsCache->hostnames = [];
if (\is_resource($this->handle) || $this->handle instanceof \CurlMultiHandle) {
if (\defined('CURLMOPT_PUSHFUNCTION')) {
curl_multi_setopt($this->handle, \CURLMOPT_PUSHFUNCTION, null);
}
$active = 0;
while (\CURLM_CALL_MULTI_PERFORM === curl_multi_exec($this->handle, $active));
}
foreach ($this->openHandles as [$ch]) {
if (\is_resource($ch) || $ch instanceof \CurlHandle) {
curl_setopt($ch, \CURLOPT_VERBOSE, false);
}
}
}
public function __sleep()
{
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
}
public function __wakeup()
{
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
public function __destruct()
{
$this->reset();
}
} }

View File

@ -28,8 +28,8 @@ class MockHttpClient implements HttpClientInterface
use HttpClientTrait; use HttpClientTrait;
private $responseFactory; private $responseFactory;
private $baseUri;
private $requestsCount = 0; private $requestsCount = 0;
private $defaultOptions = [];
/** /**
* @param callable|callable[]|ResponseInterface|ResponseInterface[]|iterable|null $responseFactory * @param callable|callable[]|ResponseInterface|ResponseInterface[]|iterable|null $responseFactory
@ -47,7 +47,7 @@ class MockHttpClient implements HttpClientInterface
} }
$this->responseFactory = $responseFactory; $this->responseFactory = $responseFactory;
$this->baseUri = $baseUri; $this->defaultOptions['base_uri'] = $baseUri;
} }
/** /**
@ -55,7 +55,7 @@ class MockHttpClient implements HttpClientInterface
*/ */
public function request(string $method, string $url, array $options = []): ResponseInterface public function request(string $method, string $url, array $options = []): ResponseInterface
{ {
[$url, $options] = $this->prepareRequest($method, $url, $options, ['base_uri' => $this->baseUri], true); [$url, $options] = $this->prepareRequest($method, $url, $options, $this->defaultOptions, true);
$url = implode('', $url); $url = implode('', $url);
if (null === $this->responseFactory) { if (null === $this->responseFactory) {
@ -96,4 +96,15 @@ class MockHttpClient implements HttpClientInterface
{ {
return $this->requestsCount; return $this->requestsCount;
} }
/**
* {@inheritdoc}
*/
public function withOptions(array $options): self
{
$clone = clone $this;
$clone->defaultOptions = self::mergeDefaultOptions($options, $this->defaultOptions, true);
return $clone;
}
} }

View File

@ -110,4 +110,15 @@ final class NoPrivateNetworkHttpClient implements HttpClientInterface, LoggerAwa
$this->client->setLogger($logger); $this->client->setLogger($logger);
} }
} }
/**
* {@inheritdoc}
*/
public function withOptions(array $options): self
{
$clone = clone $this;
$clone->client = $this->client->withOptions($options);
return $clone;
}
} }

View File

@ -110,4 +110,15 @@ class ScopingHttpClient implements HttpClientInterface, ResetInterface, LoggerAw
$this->client->setLogger($logger); $this->client->setLogger($logger);
} }
} }
/**
* {@inheritdoc}
*/
public function withOptions(array $options): self
{
$clone = clone $this;
$clone->client = $this->client->withOptions($options);
return $clone;
}
} }

View File

@ -105,4 +105,15 @@ final class TraceableHttpClient implements HttpClientInterface, ResetInterface,
$this->client->setLogger($logger); $this->client->setLogger($logger);
} }
} }
/**
* {@inheritdoc}
*/
public function withOptions(array $options): self
{
$clone = clone $this;
$clone->client = $this->client->withOptions($options);
return $clone;
}
} }

View File

@ -18,12 +18,12 @@
"php-http/async-client-implementation": "*", "php-http/async-client-implementation": "*",
"php-http/client-implementation": "*", "php-http/client-implementation": "*",
"psr/http-client-implementation": "1.0", "psr/http-client-implementation": "1.0",
"symfony/http-client-implementation": "2.2" "symfony/http-client-implementation": "2.4"
}, },
"require": { "require": {
"php": ">=7.2.5", "php": ">=7.2.5",
"psr/log": "^1.0", "psr/log": "^1.0",
"symfony/http-client-contracts": "^2.2", "symfony/http-client-contracts": "^2.4",
"symfony/polyfill-php73": "^1.11", "symfony/polyfill-php73": "^1.11",
"symfony/polyfill-php80": "^1.15", "symfony/polyfill-php80": "^1.15",
"symfony/service-contracts": "^1.0|^2" "symfony/service-contracts": "^1.0|^2"

View File

@ -1,6 +1,11 @@
CHANGELOG CHANGELOG
========= =========
2.4
---
* Add `HttpClientInterface::withOptions()`
2.3.0 2.3.0
----- -----

View File

@ -28,7 +28,7 @@
"minimum-stability": "dev", "minimum-stability": "dev",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "2.3-dev" "dev-main": "2.4-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/contracts", "name": "symfony/contracts",

View File

@ -25,7 +25,7 @@
"minimum-stability": "dev", "minimum-stability": "dev",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "2.3-dev" "dev-main": "2.4-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/contracts", "name": "symfony/contracts",

View File

@ -28,7 +28,7 @@
"minimum-stability": "dev", "minimum-stability": "dev",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "2.3-dev" "dev-main": "2.4-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/contracts", "name": "symfony/contracts",

View File

@ -19,6 +19,8 @@ use Symfony\Contracts\HttpClient\Test\HttpClientTestCase;
* *
* @see HttpClientTestCase for a reference test suite * @see HttpClientTestCase for a reference test suite
* *
* @method static withOptions(array $options) Returns a new instance of the client with new default options
*
* @author Nicolas Grekas <p@tchwork.com> * @author Nicolas Grekas <p@tchwork.com>
*/ */
interface HttpClientInterface interface HttpClientInterface

View File

@ -1038,4 +1038,20 @@ abstract class HttpClientTestCase extends TestCase
$this->assertLessThan(10, $duration); $this->assertLessThan(10, $duration);
} }
public function testWithOptions()
{
$client = $this->getHttpClient(__FUNCTION__);
if (!method_exists($client, 'withOptions')) {
$this->markTestSkipped(sprintf('Not implementing "%s::withOptions()" is deprecated.', get_debug_type($client)));
}
$client2 = $client->withOptions(['base_uri' => 'http://localhost:8057/']);
$this->assertNotSame($client, $client2);
$this->assertSame(\get_class($client), \get_class($client2));
$response = $client2->request('GET', '/');
$this->assertSame(200, $response->getStatusCode());
}
} }

View File

@ -27,7 +27,7 @@
"minimum-stability": "dev", "minimum-stability": "dev",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "2.3-dev" "dev-main": "2.4-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/contracts", "name": "symfony/contracts",

View File

@ -28,7 +28,7 @@
"minimum-stability": "dev", "minimum-stability": "dev",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "2.3-dev" "dev-main": "2.4-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/contracts", "name": "symfony/contracts",

View File

@ -27,7 +27,7 @@
"minimum-stability": "dev", "minimum-stability": "dev",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "2.3-dev" "dev-main": "2.4-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/contracts", "name": "symfony/contracts",

View File

@ -49,7 +49,7 @@
"minimum-stability": "dev", "minimum-stability": "dev",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "2.3-dev" "dev-main": "2.4-dev"
} }
} }
} }