[HttpClient] Add HttpClientInterface::withOptions()
This commit is contained in:
parent
e872db4a1c
commit
439742ff33
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
5.3
|
||||||
|
---
|
||||||
|
|
||||||
|
* Implement `HttpClientInterface::withOptions()` from `symfony/contracts` v2.4
|
||||||
|
|
||||||
5.2.0
|
5.2.0
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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.
|
||||||
*
|
*
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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"
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
2.4
|
||||||
|
---
|
||||||
|
|
||||||
|
* Add `HttpClientInterface::withOptions()`
|
||||||
|
|
||||||
2.3.0
|
2.3.0
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
@ -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",
|
||||||
|
@ -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",
|
||||||
|
@ -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",
|
||||||
|
@ -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
|
||||||
|
@ -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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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",
|
||||||
|
@ -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",
|
||||||
|
@ -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",
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user