[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",
"options": {
"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));
}
/**
* {@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
=========
5.3
---
* Implement `HttpClientInterface::withOptions()` from `symfony/contracts` v2.4
5.2.0
-----

View File

@ -341,30 +341,8 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface,
public function reset()
{
if ($this->logger) {
foreach ($this->multi->pushedResponses as $url => $response) {
$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);
}
}
$this->multi->logger = $this->logger;
$this->multi->reset();
}
public function __sleep()
@ -379,7 +357,7 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface,
public function __destruct()
{
$this->reset();
$this->multi->logger = $this->logger;
}
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
{
use AsyncDecoratorTrait;
use HttpClientTrait;
use AsyncDecoratorTrait, HttpClientTrait {
AsyncDecoratorTrait::withOptions insteadof HttpClientTrait;
}
private $reconnectionTime;

View File

@ -17,7 +17,7 @@ use Symfony\Component\HttpClient\Exception\TransportException;
/**
* 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>
*/
@ -25,6 +25,17 @@ trait HttpClientTrait
{
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.
*

View File

@ -11,6 +11,8 @@
namespace Symfony\Component\HttpClient\Internal;
use Psr\Log\LoggerInterface;
/**
* Internal representation of the cURL client's state.
*
@ -29,10 +31,55 @@ final class CurlClientState extends ClientState
/** @var float[] */
public $pauseExpiries = [];
public $execCounter = \PHP_INT_MIN;
/** @var LoggerInterface|null */
public $logger;
public function __construct()
{
$this->handle = curl_multi_init();
$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;
private $responseFactory;
private $baseUri;
private $requestsCount = 0;
private $defaultOptions = [];
/**
* @param callable|callable[]|ResponseInterface|ResponseInterface[]|iterable|null $responseFactory
@ -47,7 +47,7 @@ class MockHttpClient implements HttpClientInterface
}
$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
{
[$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);
if (null === $this->responseFactory) {
@ -96,4 +96,15 @@ class MockHttpClient implements HttpClientInterface
{
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);
}
}
/**
* {@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);
}
}
/**
* {@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);
}
}
/**
* {@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/client-implementation": "*",
"psr/http-client-implementation": "1.0",
"symfony/http-client-implementation": "2.2"
"symfony/http-client-implementation": "2.4"
},
"require": {
"php": ">=7.2.5",
"psr/log": "^1.0",
"symfony/http-client-contracts": "^2.2",
"symfony/http-client-contracts": "^2.4",
"symfony/polyfill-php73": "^1.11",
"symfony/polyfill-php80": "^1.15",
"symfony/service-contracts": "^1.0|^2"

View File

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

View File

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

View File

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

View File

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

View File

@ -19,6 +19,8 @@ use Symfony\Contracts\HttpClient\Test\HttpClientTestCase;
*
* @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>
*/
interface HttpClientInterface

View File

@ -1038,4 +1038,20 @@ abstract class HttpClientTestCase extends TestCase
$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",
"extra": {
"branch-alias": {
"dev-main": "2.3-dev"
"dev-main": "2.4-dev"
},
"thanks": {
"name": "symfony/contracts",

View File

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

View File

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

View File

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