117 lines
		
	
	
		
			3.5 KiB
		
	
	
	
		
			PHP
		
	
	
		
		
			
		
	
	
			117 lines
		
	
	
		
			3.5 KiB
		
	
	
	
		
			PHP
		
	
	
|  | <?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 null|callable(int): int                             $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); | ||
|  | 
 | ||
|  |         return $this($request, $options); | ||
|  |     } | ||
|  | } |