RetryMiddleware.php 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  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. /**
  19. * @param callable $decider Function that accepts the number of retries,
  20. * a request, [response], and [exception] and
  21. * returns true if the request is to be
  22. * retried.
  23. * @param callable $nextHandler Next handler to invoke.
  24. * @param callable $delay Function that accepts the number of retries
  25. * and [response] and returns the number of
  26. * milliseconds to delay.
  27. */
  28. public function __construct(
  29. callable $decider,
  30. callable $nextHandler,
  31. callable $delay = null
  32. ) {
  33. $this->decider = $decider;
  34. $this->nextHandler = $nextHandler;
  35. $this->delay = $delay ?: __CLASS__ . '::exponentialDelay';
  36. }
  37. /**
  38. * Default exponential backoff delay function.
  39. *
  40. * @param $retries
  41. *
  42. * @return int
  43. */
  44. public static function exponentialDelay($retries)
  45. {
  46. return (int) pow(2, $retries - 1);
  47. }
  48. /**
  49. * @param RequestInterface $request
  50. * @param array $options
  51. *
  52. * @return PromiseInterface
  53. */
  54. public function __invoke(RequestInterface $request, array $options)
  55. {
  56. if (!isset($options['retries'])) {
  57. $options['retries'] = 0;
  58. }
  59. $fn = $this->nextHandler;
  60. return $fn($request, $options)
  61. ->then(
  62. $this->onFulfilled($request, $options),
  63. $this->onRejected($request, $options)
  64. );
  65. }
  66. private function onFulfilled(RequestInterface $req, array $options)
  67. {
  68. return function ($value) use ($req, $options) {
  69. if (!call_user_func(
  70. $this->decider,
  71. $options['retries'],
  72. $req,
  73. $value,
  74. null
  75. )) {
  76. return $value;
  77. }
  78. return $this->doRetry($req, $options, $value);
  79. };
  80. }
  81. private function onRejected(RequestInterface $req, array $options)
  82. {
  83. return function ($reason) use ($req, $options) {
  84. if (!call_user_func(
  85. $this->decider,
  86. $options['retries'],
  87. $req,
  88. null,
  89. $reason
  90. )) {
  91. return \GuzzleHttp\Promise\rejection_for($reason);
  92. }
  93. return $this->doRetry($req, $options);
  94. };
  95. }
  96. private function doRetry(RequestInterface $request, array $options, ResponseInterface $response = null)
  97. {
  98. $options['delay'] = call_user_func($this->delay, ++$options['retries'], $response);
  99. return $this($request, $options);
  100. }
  101. }