-
Notifications
You must be signed in to change notification settings - Fork 0
/
RetryableHttpClient.php
115 lines (87 loc) · 4.26 KB
/
RetryableHttpClient.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
<?php
declare(strict_types=1);
namespace Manyou\PromiseHttpClient;
use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Promise\RejectedPromise;
use Manyou\PromiseHttpClient\DelayStrategy\DelayStrategyChain;
use Manyou\PromiseHttpClient\RetryStrategy\DefaultRetryStrategy;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\LoggerInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\ResponseInterface;
final class RetryableHttpClient implements PromiseHttpClientInterface, LoggerAwareInterface
{
use LoggerAwareTrait;
private RetryStrategyInterface $retryStrategy;
private DelayStrategyInterface $delayStrategy;
public function __construct(
private PromiseHttpClientInterface $client,
?RetryStrategyInterface $retryStrategy = null,
?DelayStrategyInterface $delayStrategy = null,
private int $maxRetries = 3,
?LoggerInterface $logger = null,
) {
$this->retryStrategy = $retryStrategy ?? new DefaultRetryStrategy();
$this->delayStrategy = $delayStrategy ?? DelayStrategyChain::createDefault();
if (null !== $logger) {
$this->setLogger($logger);
}
}
public function request(string $method, string $url, array $options = []): PromiseInterface
{
return $this->doRequest($method, $url, $options, 0);
}
private function doRequest(string $method, string $url, array $options, int $count): PromiseInterface
{
$promise = $this->client->request($method, $url, $options);
if ($count < $this->maxRetries) {
return $promise->then(
$this->onFulfilled($method, $url, $options, $count),
$this->onRejected($method, $url, $options, $count),
);
}
$this->logger && $this->logger->debug(
'Reached max retries of {maxRetries}',
['method' => $method, 'url' => $url, 'count' => $count, 'maxRetries' => $this->maxRetries],
);
return $promise;
}
private function onFulfilled(string $method, string $url, array $options, int $count): callable
{
return function (ResponseInterface $response) use ($method, $url, $options, $count) {
$context = ['method' => $method, 'url' => $url, 'statusCode' => $response->getStatusCode(), 'count' => $count];
if ($this->retryStrategy->onResponse($response)) {
$this->logger && $this->logger->info('Retrying on response of status {statusCode}', $context);
return $this->doRetry($method, $url, $options, $count, $response);
}
$this->logger && $this->logger->debug('Returning response of status {statusCode}', $context);
return $response;
};
}
private function onRejected(string $method, string $url, array $options, int $count): callable
{
return function ($reason) use ($method, $url, $options, $count) {
if ($reason instanceof TransportExceptionInterface) {
$context = ['method' => $method, 'url' => $url, 'exception' => $reason, 'count' => $count];
if ($this->retryStrategy->onException($reason)) {
$this->logger && $this->logger->info('Retrying on exception', $context);
return $this->doRetry($method, $url, $options, $count, null);
}
$this->logger && $this->logger->debug('Rejecting with exception', $context);
}
return new RejectedPromise($reason);
};
}
private function doRetry(string $method, string $url, array $options, int $count, ?ResponseInterface $response): PromiseInterface
{
$delay = $this->delayStrategy->getDelay(++$count, $response);
$context = ['method' => $method, 'url' => $url, 'count' => $count, 'delay' => $delay];
if ($delay !== null && $delay > 0) {
$this->logger && $this->logger->info('Retrying #{count} with {delay} ms delay', $context);
return $this->doRequest($method, $url, ['delay' => $delay] + $options, $count);
}
$this->logger && $this->logger->info('Retrying #{count} without delay', $context);
return $this->doRequest($method, $url, $options, $count);
}
}