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);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								}
							 |