RetryMiddleware.php 3.4 KB

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