<?php 
namespace Aws; 
 
use Aws\Exception\AwsException; 
use Psr\Http\Message\RequestInterface; 
use GuzzleHttp\Promise\PromiseInterface; 
use GuzzleHttp\Psr7; 
 
/** 
 * @internal Middleware that retries failures. 
 */ 
class RetryMiddleware 
{ 
    private static $retryStatusCodes = [ 
        500 => true, 
        503 => true 
    ]; 
 
    private static $retryCodes = [ 
        'RequestLimitExceeded'                   => true, 
        'Throttling'                             => true, 
        'ThrottlingException'                    => true, 
        'ProvisionedThroughputExceededException' => true, 
        'RequestThrottled'                       => true, 
    ]; 
 
    private $decider; 
    private $delay; 
    private $nextHandler; 
 
    public function __construct( 
        callable $decider, 
        callable $delay, 
        callable $nextHandler 
    ) { 
        $this->decider = $decider; 
        $this->delay = $delay; 
        $this->nextHandler = $nextHandler; 
    } 
 
    /** 
     * Creates a default AWS retry decider function. 
     * 
     * @param int $maxRetries 
     * 
     * @return callable 
     */ 
    public static function createDefaultDecider($maxRetries = 3) 
    { 
        return function ( 
            $retries, 
            CommandInterface $command, 
            RequestInterface $request, 
            ResultInterface $result = null, 
            $error = null 
        ) use ($maxRetries) { 
            // Allow command-level options to override this value 
            $maxRetries = null !== $command['@retries'] ? 
                $command['@retries'] 
                : $maxRetries; 
 
            if ($retries >= $maxRetries) { 
                return false; 
            } elseif (!$error) { 
                return isset(self::$retryStatusCodes[$result['@metadata']['statusCode']]); 
            } elseif (!($error instanceof AwsException)) { 
                return false; 
            } elseif ($error->isConnectionError()) { 
                return true; 
            } elseif (isset(self::$retryCodes[$error->getAwsErrorCode()])) { 
                return true; 
            } elseif (isset(self::$retryStatusCodes[$error->getStatusCode()])) { 
                return true; 
            } else { 
                return false; 
            } 
        }; 
    } 
 
    /** 
     * Delay function that calculates an exponential delay. 
     * 
     * @param $retries 
     * 
     * @return int 
     */ 
    public static function exponentialDelay($retries) 
    { 
        return mt_rand(0, (int) pow(2, $retries - 1) * 100); 
    } 
 
    /** 
     * @param CommandInterface $command 
     * @param RequestInterface $request 
     * 
     * @return PromiseInterface 
     */ 
    public function __invoke( 
        CommandInterface $command, 
        RequestInterface $request = null 
    ) { 
        $retries = 0; 
        $handler = $this->nextHandler; 
        $decider = $this->decider; 
        $delay = $this->delay; 
 
        $g = function ($value) use ($handler, $decider, $delay, $command, $request, &$retries, &$g) { 
            if ($value instanceof \Exception) { 
                if (!$decider($retries, $command, $request, null, $value)) { 
                    return \GuzzleHttp\Promise\rejection_for($value); 
                } 
            } elseif ($value instanceof ResultInterface 
                && !$decider($retries, $command, $request, $value, null) 
            ) { 
                return $value; 
            } 
 
            // Delay fn is called with 0, 1, ... so increment after the call. 
            $command['@http']['delay'] = $delay($retries++); 
            return $handler($command, $request)->then($g, $g); 
        }; 
 
        return $handler($command, $request)->then($g, $g); 
    } 
} 
 
 |