[e3d4e0a] | 1 | <?php
|
---|
| 2 |
|
---|
| 3 | namespace GuzzleHttp;
|
---|
| 4 |
|
---|
| 5 | use GuzzleHttp\Promise as P;
|
---|
| 6 | use GuzzleHttp\Promise\PromiseInterface;
|
---|
| 7 | use Psr\Http\Message\RequestInterface;
|
---|
| 8 | use Psr\Http\Message\ResponseInterface;
|
---|
| 9 |
|
---|
| 10 | /**
|
---|
| 11 | * Middleware that retries requests based on the boolean result of
|
---|
| 12 | * invoking the provided "decider" function.
|
---|
| 13 | *
|
---|
| 14 | * @final
|
---|
| 15 | */
|
---|
| 16 | class RetryMiddleware
|
---|
| 17 | {
|
---|
| 18 | /**
|
---|
| 19 | * @var callable(RequestInterface, array): PromiseInterface
|
---|
| 20 | */
|
---|
| 21 | private $nextHandler;
|
---|
| 22 |
|
---|
| 23 | /**
|
---|
| 24 | * @var callable
|
---|
| 25 | */
|
---|
| 26 | private $decider;
|
---|
| 27 |
|
---|
| 28 | /**
|
---|
| 29 | * @var callable(int)
|
---|
| 30 | */
|
---|
| 31 | private $delay;
|
---|
| 32 |
|
---|
| 33 | /**
|
---|
| 34 | * @param callable $decider Function that accepts the number of retries,
|
---|
| 35 | * a request, [response], and [exception] and
|
---|
| 36 | * returns true if the request is to be
|
---|
| 37 | * retried.
|
---|
| 38 | * @param callable(RequestInterface, array): PromiseInterface $nextHandler Next handler to invoke.
|
---|
| 39 | * @param (callable(int): int)|null $delay Function that accepts the number of retries
|
---|
| 40 | * and returns the number of
|
---|
| 41 | * milliseconds to delay.
|
---|
| 42 | */
|
---|
| 43 | public function __construct(callable $decider, callable $nextHandler, ?callable $delay = null)
|
---|
| 44 | {
|
---|
| 45 | $this->decider = $decider;
|
---|
| 46 | $this->nextHandler = $nextHandler;
|
---|
| 47 | $this->delay = $delay ?: __CLASS__.'::exponentialDelay';
|
---|
| 48 | }
|
---|
| 49 |
|
---|
| 50 | /**
|
---|
| 51 | * Default exponential backoff delay function.
|
---|
| 52 | *
|
---|
| 53 | * @return int milliseconds.
|
---|
| 54 | */
|
---|
| 55 | public static function exponentialDelay(int $retries): int
|
---|
| 56 | {
|
---|
| 57 | return (int) 2 ** ($retries - 1) * 1000;
|
---|
| 58 | }
|
---|
| 59 |
|
---|
| 60 | public function __invoke(RequestInterface $request, array $options): PromiseInterface
|
---|
| 61 | {
|
---|
| 62 | if (!isset($options['retries'])) {
|
---|
| 63 | $options['retries'] = 0;
|
---|
| 64 | }
|
---|
| 65 |
|
---|
| 66 | $fn = $this->nextHandler;
|
---|
| 67 |
|
---|
| 68 | return $fn($request, $options)
|
---|
| 69 | ->then(
|
---|
| 70 | $this->onFulfilled($request, $options),
|
---|
| 71 | $this->onRejected($request, $options)
|
---|
| 72 | );
|
---|
| 73 | }
|
---|
| 74 |
|
---|
| 75 | /**
|
---|
| 76 | * Execute fulfilled closure
|
---|
| 77 | */
|
---|
| 78 | private function onFulfilled(RequestInterface $request, array $options): callable
|
---|
| 79 | {
|
---|
| 80 | return function ($value) use ($request, $options) {
|
---|
| 81 | if (!($this->decider)(
|
---|
| 82 | $options['retries'],
|
---|
| 83 | $request,
|
---|
| 84 | $value,
|
---|
| 85 | null
|
---|
| 86 | )) {
|
---|
| 87 | return $value;
|
---|
| 88 | }
|
---|
| 89 |
|
---|
| 90 | return $this->doRetry($request, $options, $value);
|
---|
| 91 | };
|
---|
| 92 | }
|
---|
| 93 |
|
---|
| 94 | /**
|
---|
| 95 | * Execute rejected closure
|
---|
| 96 | */
|
---|
| 97 | private function onRejected(RequestInterface $req, array $options): callable
|
---|
| 98 | {
|
---|
| 99 | return function ($reason) use ($req, $options) {
|
---|
| 100 | if (!($this->decider)(
|
---|
| 101 | $options['retries'],
|
---|
| 102 | $req,
|
---|
| 103 | null,
|
---|
| 104 | $reason
|
---|
| 105 | )) {
|
---|
| 106 | return P\Create::rejectionFor($reason);
|
---|
| 107 | }
|
---|
| 108 |
|
---|
| 109 | return $this->doRetry($req, $options);
|
---|
| 110 | };
|
---|
| 111 | }
|
---|
| 112 |
|
---|
| 113 | private function doRetry(RequestInterface $request, array $options, ?ResponseInterface $response = null): PromiseInterface
|
---|
| 114 | {
|
---|
| 115 | $options['delay'] = ($this->delay)(++$options['retries'], $response, $request);
|
---|
| 116 |
|
---|
| 117 | return $this($request, $options);
|
---|
| 118 | }
|
---|
| 119 | }
|
---|