Skip to content

Commit 2e4ecdb

Browse files
Tirielnicolas-grekas
authored andcommitted
[HttpClient] Allow using multiple base_uri as array for retries
1 parent 95cf988 commit 2e4ecdb

File tree

3 files changed

+135
-2
lines changed

3 files changed

+135
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ CHANGELOG
66

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

1011
6.2
1112
---

RetryableHttpClient.php

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class RetryableHttpClient implements HttpClientInterface, ResetInterface
3535
private RetryStrategyInterface $strategy;
3636
private int $maxRetries;
3737
private LoggerInterface $logger;
38+
private array $baseUris = [];
3839

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

51+
public function withOptions(array $options): static
52+
{
53+
if (\array_key_exists('base_uri', $options)) {
54+
if (\is_array($options['base_uri'])) {
55+
$this->baseUris = $options['base_uri'];
56+
unset($options['base_uri']);
57+
} else {
58+
$this->baseUris = [];
59+
}
60+
}
61+
62+
$clone = clone $this;
63+
$clone->client = $this->client->withOptions($options);
64+
65+
return $clone;
66+
}
67+
5068
public function request(string $method, string $url, array $options = []): ResponseInterface
5169
{
70+
$baseUris = \array_key_exists('base_uri', $options) ? $options['base_uri'] : $this->baseUris;
71+
$baseUris = \is_array($baseUris) ? $baseUris : [];
72+
$options = self::shiftBaseUri($options, $baseUris);
73+
5274
if ($this->maxRetries <= 0) {
5375
return new AsyncResponse($this->client, $method, $url, $options);
5476
}
5577

56-
return new AsyncResponse($this->client, $method, $url, $options, function (ChunkInterface $chunk, AsyncContext $context) use ($method, $url, $options) {
78+
return new AsyncResponse($this->client, $method, $url, $options, function (ChunkInterface $chunk, AsyncContext $context) use ($method, $url, $options, &$baseUris) {
5779
static $retryCount = 0;
5880
static $content = '';
5981
static $firstChunk;
@@ -127,7 +149,7 @@ public function request(string $method, string $url, array $options = []): Respo
127149
]);
128150

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

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

169191
yield $lastChunk;
170192
}
193+
194+
private static function shiftBaseUri(array $options, array &$baseUris): array
195+
{
196+
if ($baseUris) {
197+
$baseUri = 1 < \count($baseUris) ? array_shift($baseUris) : current($baseUris);
198+
$options['base_uri'] = \is_array($baseUri) ? $baseUri[array_rand($baseUri)] : $baseUri;
199+
}
200+
201+
return $options;
202+
}
171203
}

Tests/RetryableHttpClientTest.php

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,4 +244,104 @@ public function testRetryOnErrorAssertContent()
244244
self::assertSame('Test out content', $response->getContent());
245245
self::assertSame('Test out content', $response->getContent(), 'Content should be buffered');
246246
}
247+
248+
public function testRetryWithMultipleBaseUris()
249+
{
250+
$client = new RetryableHttpClient(
251+
new MockHttpClient([
252+
new MockResponse('', ['http_code' => 500]),
253+
new MockResponse('Hit on second uri', ['http_code' => 200]),
254+
]),
255+
new GenericRetryStrategy([500], 0),
256+
1
257+
);
258+
259+
$response = $client->request('GET', 'foo-bar', [
260+
'base_uri' => [
261+
'http://example.com/a/',
262+
'http://example.com/b/',
263+
],
264+
]);
265+
266+
self::assertSame(200, $response->getStatusCode());
267+
self::assertSame('http://example.com/b/foo-bar', $response->getInfo('url'));
268+
}
269+
270+
public function testMultipleBaseUrisAsOptions()
271+
{
272+
$client = new RetryableHttpClient(
273+
new MockHttpClient([
274+
new MockResponse('', ['http_code' => 500]),
275+
new MockResponse('Hit on second uri', ['http_code' => 200]),
276+
]),
277+
new GenericRetryStrategy([500], 0),
278+
1
279+
);
280+
281+
$client = $client->withOptions([
282+
'base_uri' => [
283+
'http://example.com/a/',
284+
'http://example.com/b/',
285+
],
286+
]);
287+
288+
$response = $client->request('GET', 'foo-bar');
289+
290+
self::assertSame(200, $response->getStatusCode());
291+
self::assertSame('http://example.com/b/foo-bar', $response->getInfo('url'));
292+
}
293+
294+
public function testRetryWithMultipleBaseUrisShufflesNestedArray()
295+
{
296+
$client = new RetryableHttpClient(
297+
new MockHttpClient([
298+
new MockResponse('', ['http_code' => 500]),
299+
new MockResponse('Hit on second uri', ['http_code' => 200]),
300+
]),
301+
new GenericRetryStrategy([500], 0),
302+
1
303+
);
304+
305+
$response = $client->request('GET', 'foo-bar', [
306+
'base_uri' => [
307+
'http://example.com/a/',
308+
[
309+
'http://example.com/b/',
310+
'http://example.com/c/',
311+
],
312+
'http://example.com/d/',
313+
],
314+
]);
315+
316+
self::assertSame(200, $response->getStatusCode());
317+
self::assertMatchesRegularExpression('#^http://example.com/(b|c)/foo-bar$#', $response->getInfo('url'));
318+
}
319+
320+
public function testRetryWithMultipleBaseUrisPreservesNonNestedOrder()
321+
{
322+
$client = new RetryableHttpClient(
323+
new MockHttpClient([
324+
new MockResponse('', ['http_code' => 500]),
325+
new MockResponse('', ['http_code' => 500]),
326+
new MockResponse('', ['http_code' => 500]),
327+
new MockResponse('Hit on second uri', ['http_code' => 200]),
328+
]),
329+
new GenericRetryStrategy([500], 0),
330+
3
331+
);
332+
333+
$response = $client->request('GET', 'foo-bar', [
334+
'base_uri' => [
335+
'http://example.com/a/',
336+
[
337+
'http://example.com/b/',
338+
'http://example.com/c/',
339+
],
340+
'http://example.com/d/',
341+
],
342+
]);
343+
344+
self::assertSame(200, $response->getStatusCode());
345+
self::assertSame('http://example.com/d/foo-bar', $response->getInfo('url'));
346+
}
247347
}

0 commit comments

Comments
 (0)