diff --git a/src/Symfony/Component/HttpClient/CHANGELOG.md b/src/Symfony/Component/HttpClient/CHANGELOG.md index 7cd69191c5411..0d483c645c9da 100644 --- a/src/Symfony/Component/HttpClient/CHANGELOG.md +++ b/src/Symfony/Component/HttpClient/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.4 +--- + + * Add `max_retries` option to `RetryableHttpClient` to adjust the retry logic on a per request level + 6.3 --- diff --git a/src/Symfony/Component/HttpClient/RetryableHttpClient.php b/src/Symfony/Component/HttpClient/RetryableHttpClient.php index 1a6ec7d35e63f..bb0cab1b3ca6b 100644 --- a/src/Symfony/Component/HttpClient/RetryableHttpClient.php +++ b/src/Symfony/Component/HttpClient/RetryableHttpClient.php @@ -60,6 +60,9 @@ public function withOptions(array $options): static } $clone = clone $this; + $clone->maxRetries = (int) ($options['max_retries'] ?? $this->maxRetries); + unset($options['max_retries']); + $clone->client = $this->client->withOptions($options); return $clone; @@ -71,11 +74,14 @@ public function request(string $method, string $url, array $options = []): Respo $baseUris = \is_array($baseUris) ? $baseUris : []; $options = self::shiftBaseUri($options, $baseUris); - if ($this->maxRetries <= 0) { + $maxRetries = (int) ($options['max_retries'] ?? $this->maxRetries); + unset($options['max_retries']); + + if ($maxRetries <= 0) { return new AsyncResponse($this->client, $method, $url, $options); } - return new AsyncResponse($this->client, $method, $url, $options, function (ChunkInterface $chunk, AsyncContext $context) use ($method, $url, $options, &$baseUris) { + return new AsyncResponse($this->client, $method, $url, $options, function (ChunkInterface $chunk, AsyncContext $context) use ($method, $url, $options, $maxRetries, &$baseUris) { static $retryCount = 0; static $content = ''; static $firstChunk; @@ -152,7 +158,7 @@ public function request(string $method, string $url, array $options = []): Respo $context->replaceRequest($method, $url, self::shiftBaseUri($options, $baseUris)); $context->pause($delay / 1000); - if ($retryCount >= $this->maxRetries) { + if ($retryCount >= $maxRetries) { $context->passthru(); } }); diff --git a/src/Symfony/Component/HttpClient/Tests/RetryableHttpClientTest.php b/src/Symfony/Component/HttpClient/Tests/RetryableHttpClientTest.php index b32601aefc5a5..2ef52336c340c 100644 --- a/src/Symfony/Component/HttpClient/Tests/RetryableHttpClientTest.php +++ b/src/Symfony/Component/HttpClient/Tests/RetryableHttpClientTest.php @@ -344,4 +344,45 @@ public function testRetryWithMultipleBaseUrisPreservesNonNestedOrder() self::assertSame(200, $response->getStatusCode()); self::assertSame('http://example.com/d/foo-bar', $response->getInfo('url')); } + + public function testMaxRetriesOption() + { + $client = new RetryableHttpClient( + new MockHttpClient([ + new MockResponse('', ['http_code' => 500]), + new MockResponse('', ['http_code' => 502]), + new MockResponse('', ['http_code' => 200]), + ]), + new GenericRetryStrategy([500, 502], 0), + 3 + ); + + $response = $client->request('GET', 'http://example.com/foo-bar', [ + 'max_retries' => 1, + ]); + + self::assertSame(502, $response->getStatusCode()); + } + + public function testMaxRetriesWithOptions() + { + $client = new RetryableHttpClient( + new MockHttpClient([ + new MockResponse('', ['http_code' => 500]), + new MockResponse('', ['http_code' => 502]), + new MockResponse('', ['http_code' => 504]), + new MockResponse('', ['http_code' => 200]), + ]), + new GenericRetryStrategy([500, 502, 504], 0), + 3 + ); + + $client = $client->withOptions([ + 'max_retries' => 2, + ]); + + $response = $client->request('GET', 'http://example.com/foo-bar'); + + self::assertSame(504, $response->getStatusCode()); + } }