RetryMiddleware.php
3.5 KB
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
116
<?php
namespace GuzzleHttp;
use GuzzleHttp\Promise as P;
use GuzzleHttp\Promise\PromiseInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
/**
* Middleware that retries requests based on the boolean result of
* invoking the provided "decider" function.
*
* @final
*/
class RetryMiddleware
{
/**
* @var callable(RequestInterface, array): PromiseInterface
*/
private $nextHandler;
/**
* @var callable
*/
private $decider;
/**
* @var callable(int)
*/
private $delay;
/**
* @param callable $decider Function that accepts the number of retries,
* a request, [response], and [exception] and
* returns true if the request is to be
* retried.
* @param callable(RequestInterface, array): PromiseInterface $nextHandler Next handler to invoke.
* @param (callable(int): int)|null $delay Function that accepts the number of retries
* and returns the number of
* milliseconds to delay.
*/
public function __construct(callable $decider, callable $nextHandler, callable $delay = null)
{
$this->decider = $decider;
$this->nextHandler = $nextHandler;
$this->delay = $delay ?: __CLASS__ . '::exponentialDelay';
}
/**
* Default exponential backoff delay function.
*
* @return int milliseconds.
*/
public static function exponentialDelay(int $retries): int
{
return (int) \pow(2, $retries - 1) * 1000;
}
public function __invoke(RequestInterface $request, array $options): PromiseInterface
{
if (!isset($options['retries'])) {
$options['retries'] = 0;
}
$fn = $this->nextHandler;
return $fn($request, $options)
->then(
$this->onFulfilled($request, $options),
$this->onRejected($request, $options)
);
}
/**
* Execute fulfilled closure
*/
private function onFulfilled(RequestInterface $request, array $options): callable
{
return function ($value) use ($request, $options) {
if (!($this->decider)(
$options['retries'],
$request,
$value,
null
)) {
return $value;
}
return $this->doRetry($request, $options, $value);
};
}
/**
* Execute rejected closure
*/
private function onRejected(RequestInterface $req, array $options): callable
{
return function ($reason) use ($req, $options) {
if (!($this->decider)(
$options['retries'],
$req,
null,
$reason
)) {
return P\Create::rejectionFor($reason);
}
return $this->doRetry($req, $options);
};
}
private function doRetry(RequestInterface $request, array $options, ResponseInterface $response = null): PromiseInterface
{
$options['delay'] = ($this->delay)(++$options['retries'], $response, $request);
return $this($request, $options);
}
}