1 | <?php |
||
34 | class GuzzleRetryMiddleware |
||
35 | { |
||
36 | // HTTP date format |
||
37 | const DATE_FORMAT = 'D, d M Y H:i:s T'; |
||
38 | |||
39 | // Default retry header (off by default; configurable) |
||
40 | const RETRY_HEADER = 'X-Retry-Counter'; |
||
41 | |||
42 | /** |
||
43 | * @var array |
||
44 | */ |
||
45 | private $defaultOptions = [ |
||
46 | |||
47 | // Retry enabled. Toggle retry on or off per request |
||
48 | 'retry_enabled' => true, |
||
49 | |||
50 | // If server doesn't provide a Retry-After header, then set a default back-off delay |
||
51 | // NOTE: This can either be a float, or it can be a callable that returns a (accepts count and response|null) |
||
52 | 'default_retry_multiplier' => 1.5, |
||
53 | |||
54 | // Set a maximum number of attempts per request |
||
55 | 'max_retry_attempts' => 10, |
||
56 | |||
57 | // Set this to TRUE to retry only if the HTTP Retry-After header is specified |
||
58 | 'retry_only_if_retry_after_header' => false, |
||
59 | |||
60 | // Only retry when status is equal to these response codes |
||
61 | 'retry_on_status' => ['429', '503'], |
||
62 | |||
63 | // Callback to trigger when delay occurs (accepts count, delay, request, response, options) |
||
64 | 'on_retry_callback' => null, |
||
65 | |||
66 | // Retry on connect timeout? |
||
67 | 'retry_on_timeout' => false, |
||
68 | |||
69 | // Add the number of retries to an X-Header |
||
70 | 'expose_retry_header' => false, |
||
71 | |||
72 | // The header key |
||
73 | 'retry_header' => self::RETRY_HEADER |
||
74 | ]; |
||
75 | |||
76 | /** |
||
77 | * @var callable |
||
78 | */ |
||
79 | private $nextHandler; |
||
80 | |||
81 | /** |
||
82 | * Provides a closure that can be pushed onto the handler stack |
||
83 | * |
||
84 | * Example: |
||
85 | * <code>$handlerStack->push(GuzzleRetryMiddleware::factory());</code> |
||
86 | * |
||
87 | * @param array $defaultOptions |
||
88 | * @return \Closure |
||
89 | */ |
||
90 | 66 | public static function factory(array $defaultOptions = []) |
|
96 | |||
97 | /** |
||
98 | * GuzzleRetryMiddleware constructor. |
||
99 | * |
||
100 | * @param callable $nextHandler |
||
101 | * @param array $defaultOptions |
||
102 | */ |
||
103 | 69 | public function __construct(callable $nextHandler, array $defaultOptions = []) |
|
108 | |||
109 | /** |
||
110 | * @param RequestInterface $request |
||
111 | * @param array $options |
||
112 | * @return Promise |
||
113 | */ |
||
114 | 66 | public function __invoke(RequestInterface $request, array $options) |
|
132 | |||
133 | /** |
||
134 | * No exceptions were thrown during processing |
||
135 | * |
||
136 | * Depending on where this middleware is in the stack, the response could still |
||
137 | * be unsuccessful (e.g. 429 or 503), so check to see if it should be retried |
||
138 | * |
||
139 | * @param RequestInterface $request |
||
140 | * @param array $options |
||
141 | * @return callable |
||
142 | */ |
||
143 | 66 | protected function onFulfilled(RequestInterface $request, array $options) |
|
151 | |||
152 | /** |
||
153 | * An exception or error was thrown during processing |
||
154 | * |
||
155 | * If the reason is a BadResponseException exception, check to see if |
||
156 | * the request can be retried. Otherwise, pass it on. |
||
157 | * |
||
158 | * @param RequestInterface $request |
||
159 | * @param array $options |
||
160 | * @return callable |
||
161 | */ |
||
162 | 22 | protected function onRejected(RequestInterface $request, array $options) |
|
182 | |||
183 | 12 | protected function shouldRetryConnectException(ConnectException $e, array $options) |
|
201 | |||
202 | /** |
||
203 | * Check to see if a request can be retried |
||
204 | * |
||
205 | * This checks two things: |
||
206 | * |
||
207 | * 1. The response status code against the status codes that should be retried |
||
208 | * 2. The number of attempts made thus far for this request |
||
209 | * |
||
210 | * @param array $options |
||
211 | * @param ResponseInterface|null $response |
||
212 | * @return bool TRUE if the response should be retried, FALSE if not |
||
213 | */ |
||
214 | 54 | protected function shouldRetryHttpResponse(array $options, ResponseInterface $response) |
|
234 | |||
235 | /** |
||
236 | * Count the number of retries remaining. Always returns 0 or greater. |
||
237 | * @param array $options |
||
238 | * @return int |
||
239 | */ |
||
240 | 63 | protected function countRemainingRetries(array $options) |
|
250 | |||
251 | /** |
||
252 | * Retry the request |
||
253 | * |
||
254 | * Increments the retry count, determines the delay (timeout), executes callbacks, sleeps, and re-send the request |
||
255 | * |
||
256 | * @param RequestInterface $request |
||
257 | * @param array $options |
||
258 | * @param ResponseInterface|null $response |
||
259 | * @return |
||
260 | */ |
||
261 | 48 | protected function doRetry(RequestInterface $request, array $options, ResponseInterface $response = null) |
|
287 | |||
288 | 54 | protected function returnResponse(array $options, ResponseInterface $response) |
|
298 | |||
299 | /** |
||
300 | * Determine the delay timeout |
||
301 | * |
||
302 | * Attempts to read and interpret the HTTP `Retry-After` header, or defaults |
||
303 | * to a built-in incremental back-off algorithm. |
||
304 | * |
||
305 | * @param ResponseInterface $response |
||
306 | * @param array $options |
||
307 | * @return float Delay timeout, in seconds |
||
308 | */ |
||
309 | 48 | protected function determineDelayTimeout(array $options, ResponseInterface $response = null) |
|
331 | |||
332 | /** |
||
333 | * Attempt to derive the timeout from the HTTP `Retry-After` header |
||
334 | * |
||
335 | * The spec allows the header value to either be a number of seconds or a datetime. |
||
336 | * |
||
337 | * @param string $headerValue |
||
338 | * @return float|null The number of seconds to wait, or NULL if unsuccessful (invalid header) |
||
339 | */ |
||
340 | 9 | protected function deriveTimeoutFromHeader($headerValue) |
|
352 | } |
||
353 |
Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code: