Skip to content

Commit

Permalink
feature #49809 [HttpClient] Allow using multiple base_uri as array fo…
Browse files Browse the repository at this point in the history
…r retries (Tiriel)

This PR was squashed before being merged into the 6.3 branch.

Discussion
----------

[HttpClient] Allow using multiple base_uri as array for retries

| Q             | A
| ------------- | ---
| Branch?       | 6.3
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | no
| Tickets       | Fix #43327
| License       | MIT
| Doc PR        | TBD

Allowing array of urls as `base_uri` option in `RetryableHttpClient` to allow retries on different base uris each time.

Commits
-------

b63ad40 [HttpClient] Allow using multiple base_uri as array for retries
  • Loading branch information
nicolas-grekas committed Apr 19, 2023
2 parents 474239a + b63ad40 commit 33da4c8
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 2 deletions.
1 change: 1 addition & 0 deletions src/Symfony/Component/HttpClient/CHANGELOG.md
Expand Up @@ -6,6 +6,7 @@ CHANGELOG

* Add `UriTemplateHttpClient` to use URI templates as specified in the RFC 6570
* Add `ServerSentEvent::getArrayData()` to get the Server-Sent Event's data decoded as an array when it's a JSON payload
* Allow array of urls as `base_uri` option value in `RetryableHttpClient` to retry on a new url each time

6.2
---
Expand Down
36 changes: 34 additions & 2 deletions src/Symfony/Component/HttpClient/RetryableHttpClient.php
Expand Up @@ -35,6 +35,7 @@ class RetryableHttpClient implements HttpClientInterface, ResetInterface
private RetryStrategyInterface $strategy;
private int $maxRetries;
private LoggerInterface $logger;
private array $baseUris = [];

/**
* @param int $maxRetries The maximum number of times to retry
Expand All @@ -47,13 +48,34 @@ public function __construct(HttpClientInterface $client, RetryStrategyInterface
$this->logger = $logger ?? new NullLogger();
}

public function withOptions(array $options): static
{
if (\array_key_exists('base_uri', $options)) {
if (\is_array($options['base_uri'])) {
$this->baseUris = $options['base_uri'];
unset($options['base_uri']);
} else {
$this->baseUris = [];
}
}

$clone = clone $this;
$clone->client = $this->client->withOptions($options);

return $clone;
}

public function request(string $method, string $url, array $options = []): ResponseInterface
{
$baseUris = \array_key_exists('base_uri', $options) ? $options['base_uri'] : $this->baseUris;
$baseUris = \is_array($baseUris) ? $baseUris : [];
$options = self::shiftBaseUri($options, $baseUris);

if ($this->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) {
return new AsyncResponse($this->client, $method, $url, $options, function (ChunkInterface $chunk, AsyncContext $context) use ($method, $url, $options, &$baseUris) {
static $retryCount = 0;
static $content = '';
static $firstChunk;
Expand Down Expand Up @@ -127,7 +149,7 @@ public function request(string $method, string $url, array $options = []): Respo
]);

$context->setInfo('retry_count', $retryCount);
$context->replaceRequest($method, $url, $options);
$context->replaceRequest($method, $url, self::shiftBaseUri($options, $baseUris));
$context->pause($delay / 1000);

if ($retryCount >= $this->maxRetries) {
Expand Down Expand Up @@ -168,4 +190,14 @@ private function passthru(AsyncContext $context, ?ChunkInterface $firstChunk, st

yield $lastChunk;
}

private static function shiftBaseUri(array $options, array &$baseUris): array
{
if ($baseUris) {
$baseUri = 1 < \count($baseUris) ? array_shift($baseUris) : current($baseUris);
$options['base_uri'] = \is_array($baseUri) ? $baseUri[array_rand($baseUri)] : $baseUri;
}

return $options;
}
}
100 changes: 100 additions & 0 deletions src/Symfony/Component/HttpClient/Tests/RetryableHttpClientTest.php
Expand Up @@ -244,4 +244,104 @@ public function testRetryOnErrorAssertContent()
self::assertSame('Test out content', $response->getContent());
self::assertSame('Test out content', $response->getContent(), 'Content should be buffered');
}

public function testRetryWithMultipleBaseUris()
{
$client = new RetryableHttpClient(
new MockHttpClient([
new MockResponse('', ['http_code' => 500]),
new MockResponse('Hit on second uri', ['http_code' => 200]),
]),
new GenericRetryStrategy([500], 0),
1
);

$response = $client->request('GET', 'foo-bar', [
'base_uri' => [
'http://example.com/a/',
'http://example.com/b/',
],
]);

self::assertSame(200, $response->getStatusCode());
self::assertSame('http://example.com/b/foo-bar', $response->getInfo('url'));
}

public function testMultipleBaseUrisAsOptions()
{
$client = new RetryableHttpClient(
new MockHttpClient([
new MockResponse('', ['http_code' => 500]),
new MockResponse('Hit on second uri', ['http_code' => 200]),
]),
new GenericRetryStrategy([500], 0),
1
);

$client = $client->withOptions([
'base_uri' => [
'http://example.com/a/',
'http://example.com/b/',
],
]);

$response = $client->request('GET', 'foo-bar');

self::assertSame(200, $response->getStatusCode());
self::assertSame('http://example.com/b/foo-bar', $response->getInfo('url'));
}

public function testRetryWithMultipleBaseUrisShufflesNestedArray()
{
$client = new RetryableHttpClient(
new MockHttpClient([
new MockResponse('', ['http_code' => 500]),
new MockResponse('Hit on second uri', ['http_code' => 200]),
]),
new GenericRetryStrategy([500], 0),
1
);

$response = $client->request('GET', 'foo-bar', [
'base_uri' => [
'http://example.com/a/',
[
'http://example.com/b/',
'http://example.com/c/',
],
'http://example.com/d/',
],
]);

self::assertSame(200, $response->getStatusCode());
self::assertMatchesRegularExpression('#^http://example.com/(b|c)/foo-bar$#', $response->getInfo('url'));
}

public function testRetryWithMultipleBaseUrisPreservesNonNestedOrder()
{
$client = new RetryableHttpClient(
new MockHttpClient([
new MockResponse('', ['http_code' => 500]),
new MockResponse('', ['http_code' => 500]),
new MockResponse('', ['http_code' => 500]),
new MockResponse('Hit on second uri', ['http_code' => 200]),
]),
new GenericRetryStrategy([500], 0),
3
);

$response = $client->request('GET', 'foo-bar', [
'base_uri' => [
'http://example.com/a/',
[
'http://example.com/b/',
'http://example.com/c/',
],
'http://example.com/d/',
],
]);

self::assertSame(200, $response->getStatusCode());
self::assertSame('http://example.com/d/foo-bar', $response->getInfo('url'));
}
}

0 comments on commit 33da4c8

Please sign in to comment.