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) \pow(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 return $fn($request, $options) 68 ->then( 69 $this->onFulfilled($request, $options), 70 $this->onRejected($request, $options) 71 ); 72 } 73 74 /** 75 * Execute fulfilled closure 76 */ 77 private function onFulfilled(RequestInterface $request, array $options): callable 78 { 79 return function ($value) use ($request, $options) { 80 if (!($this->decider)( 81 $options['retries'], 82 $request, 83 $value, 84 null 85 )) { 86 return $value; 87 } 88 return $this->doRetry($request, $options, $value); 89 }; 90 } 91 92 /** 93 * Execute rejected closure 94 */ 95 private function onRejected(RequestInterface $req, array $options): callable 96 { 97 return function ($reason) use ($req, $options) { 98 if (!($this->decider)( 99 $options['retries'], 100 $req, 101 null, 102 $reason 103 )) { 104 return P\Create::rejectionFor($reason); 105 } 106 return $this->doRetry($req, $options); 107 }; 108 } 109 110 private function doRetry(RequestInterface $request, array $options, ResponseInterface $response = null): PromiseInterface 111 { 112 $options['delay'] = ($this->delay)(++$options['retries'], $response, $request); 113 114 return $this($request, $options); 115 } 116 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body