friendship ended with social-app. php is my new best friend
1---
2title: Retry Handling
3description: Learn how to configure automatic retry behavior for failed HTTP requests
4---
5
6# Retry Handling
7
8The Fetch PHP package includes built-in retry functionality to handle transient failures gracefully. This guide explains how to configure and use the retry mechanism.
9
10## Basic Retry Configuration
11
12You can configure retries using the `retry()` method on the ClientHandler:
13
14```php
15use Fetch\Http\ClientHandler;
16
17$response = ClientHandler::create()
18 ->retry(3, 100) // Retry up to 3 times with initial delay of 100ms
19 ->get('https://api.example.com/unstable-endpoint');
20```
21
22Using helper functions:
23
24```php
25$response = fetch('https://api.example.com/unstable-endpoint', [
26 'retries' => 3, // Retry up to 3 times
27 'retry_delay' => 100 // Initial delay of 100ms
28]);
29```
30
31## How Retry Works
32
33When a request fails due to a retryable error (network issues or certain HTTP status codes), the package will:
34
351. Wait for a specified delay
362. Apply exponential backoff with jitter (randomness)
373. Retry the request
384. Repeat until success or the maximum retry count is reached
39
40The delay increases exponentially with each retry attempt:
41
42- First retry: Initial delay (e.g., 100ms)
43- Second retry: ~2x initial delay + jitter
44- Third retry: ~4x initial delay + jitter
45- And so on...
46
47The jitter (random variation) helps prevent multiple clients from retrying simultaneously, which can worsen outages.
48
49## Using Type-Safe Enums with Retries
50
51You can use the `Status` enum for more type-safe retry configuration:
52
53```php
54use Fetch\Http\ClientHandler;
55use Fetch\Enum\Status;
56
57$response = ClientHandler::create()
58 ->retry(3, 100)
59 ->retryStatusCodes([
60 Status::TOO_MANY_REQUESTS->value,
61 Status::SERVICE_UNAVAILABLE->value,
62 Status::GATEWAY_TIMEOUT->value
63 ])
64 ->get('https://api.example.com/unstable-endpoint');
65```
66
67## Customizing Retryable Status Codes
68
69By default, the client retries on these HTTP status codes:
70
71- 408 (Request Timeout)
72- 429 (Too Many Requests)
73- 500, 502, 503, 504 (Server Errors)
74- And several other common error codes
75
76You can customize which status codes should trigger retries:
77
78```php
79$client = ClientHandler::create()
80 ->retry(3, 100)
81 ->retryStatusCodes([429, 503, 504]); // Only retry on these status codes
82
83$response = $client->get('https://api.example.com/unstable-endpoint');
84```
85
86## Customizing Retryable Exceptions
87
88By default, the client retries on network-related exceptions like `ConnectException`. You can customize which exception types should trigger retries:
89
90```php
91use GuzzleHttp\Exception\ConnectException;
92use GuzzleHttp\Exception\RequestException;
93
94$client = ClientHandler::create()
95 ->retry(3, 100)
96 ->retryExceptions([
97 ConnectException::class,
98 RequestException::class
99 ]);
100
101$response = $client->get('https://api.example.com/unstable-endpoint');
102```
103
104## Checking Retry Configuration
105
106You can check the current retry configuration:
107
108```php
109$client = ClientHandler::create()->retry(3, 200);
110
111$maxRetries = $client->getMaxRetries(); // 3
112$retryDelay = $client->getRetryDelay(); // 200
113$statusCodes = $client->getRetryableStatusCodes(); // Array of status codes
114$exceptions = $client->getRetryableExceptions(); // Array of exception classes
115```
116
117## Global Retry Configuration
118
119You can set up global retry settings that apply to all requests:
120
121```php
122// Configure global retry settings
123fetch_client([
124 'retries' => 3,
125 'retry_delay' => 100
126]);
127
128// All requests will now use these retry settings
129$response = fetch('https://api.example.com/users');
130```
131
132## Logging Retries
133
134If you've set up a logger, retry attempts will be automatically logged:
135
136```php
137use Monolog\Logger;
138use Monolog\Handler\StreamHandler;
139use Fetch\Http\ClientHandler;
140
141// Create a logger
142$logger = new Logger('http');
143$logger->pushHandler(new StreamHandler('logs/http.log', Logger::INFO));
144
145// Create a client with logging and retries
146$client = ClientHandler::create();
147$client->setLogger($logger);
148$client->retry(3, 100);
149
150// Send a request that might require retries
151$response = $client->get('https://api.example.com/unstable-endpoint');
152
153// Retry attempts will be logged to logs/http.log
154```
155
156A typical retry log entry looks like:
157
158```
159[2023-09-15 14:30:12] http.INFO: Retrying request {"attempt":1,"max_attempts":3,"uri":"https://api.example.com/unstable-endpoint","method":"GET","error":"Connection timed out","code":28}
160```
161
162## Asynchronous Retries
163
164Retries also work with asynchronous requests:
165
166```php
167use function async;
168use function await;
169use function retry;
170
171// Retry asynchronous operations
172$result = await(retry(
173 function() {
174 return async(function() {
175 return fetch('https://api.example.com/unstable-endpoint');
176 });
177 },
178 3, // max attempts
179 function($attempt) {
180 // Exponential backoff strategy
181 return min(pow(2, $attempt) * 100, 1000);
182 }
183));
184
185// Process the result
186$data = $result->json();
187```
188
189## Real-World Examples
190
191### Handling Rate Limits
192
193APIs often implement rate limiting. You can configure your client to automatically retry when hitting rate limits:
194
195```php
196use Fetch\Enum\Status;
197
198$client = ClientHandler::create()
199 ->retry(3, 1000) // Longer initial delay for rate limits
200 ->retryStatusCodes([Status::TOO_MANY_REQUESTS->value]) // Only retry on Too Many Requests
201 ->get('https://api.example.com/rate-limited-endpoint');
202```
203
204### Handling Network Instability
205
206For unreliable network connections:
207
208```php
209$client = ClientHandler::create()
210 ->retry(5, 200) // More retries with moderate delay
211 // Using default retryable status codes and exceptions
212 ->get('https://api.example.com/endpoint');
213```
214
215### Handling Server Maintenance
216
217For APIs that might be temporarily down for maintenance:
218
219```php
220use Fetch\Enum\Status;
221
222$client = ClientHandler::create()
223 ->retry(10, 5000) // Many retries with long delay (5 seconds)
224 ->retryStatusCodes([Status::SERVICE_UNAVAILABLE->value]) // Service Unavailable
225 ->get('https://api.example.com/endpoint');
226```
227
228## Combining Retry with Timeout
229
230You can combine retry logic with timeout settings:
231
232```php
233$client = ClientHandler::create()
234 ->timeout(5) // 5 second timeout for each attempt
235 ->retry(3, 100) // 3 retries with 100ms initial delay
236 ->get('https://api.example.com/endpoint');
237```
238
239## Implementing Advanced Retry Logic
240
241For more complex scenarios, you can implement custom retry logic:
242
243```php
244use Fetch\Http\ClientHandler;
245use Fetch\Http\Response;
246use GuzzleHttp\Exception\RequestException;
247use Fetch\Enum\Status;
248
249function makeRequestWithCustomRetry(string $url, int $maxAttempts = 3): Response {
250 $attempt = 0;
251
252 while (true) {
253 try {
254 $client = ClientHandler::create();
255 $response = $client->get($url);
256
257 // Check if we got a success response
258 if ($response->successful()) {
259 return $response;
260 }
261
262 // Handle specific status codes
263 if ($response->statusEnum() === Status::TOO_MANY_REQUESTS) {
264 // Get retry-after header if available
265 $retryAfter = $response->header('Retry-After');
266 $delay = $retryAfter ? (int) $retryAfter * 1000 : 1000;
267 } else {
268 // Otherwise use exponential backoff
269 $delay = 100 * (2 ** $attempt);
270 }
271
272 // Add some jitter (±20%)
273 $jitter = mt_rand(-20, 20) / 100;
274 $delay = (int) ($delay * (1 + $jitter));
275
276 $attempt++;
277
278 // Check if we've exceeded max attempts
279 if ($attempt >= $maxAttempts) {
280 return $response; // Return the last response
281 }
282
283 // Wait before retrying
284 usleep($delay * 1000); // Convert ms to μs
285
286 } catch (RequestException $e) {
287 $attempt++;
288
289 // Check if we've exceeded max attempts
290 if ($attempt >= $maxAttempts) {
291 throw $e; // Rethrow the last exception
292 }
293
294 // Wait before retrying
295 $delay = 100 * (2 ** $attempt);
296 usleep($delay * 1000);
297 }
298 }
299}
300
301// Use the custom retry function
302$response = makeRequestWithCustomRetry('https://api.example.com/users');
303```
304
305## Monitoring Retry Activity
306
307To monitor retry activity, you can combine logging with a custom callback:
308
309```php
310use Monolog\Logger;
311use Monolog\Handler\StreamHandler;
312use Fetch\Http\ClientHandler;
313
314// Create a logger
315$logger = new Logger('retry');
316$logger->pushHandler(new StreamHandler('logs/retry.log', Logger::INFO));
317
318// Create a client with the logger
319$client = ClientHandler::create();
320$client->setLogger($logger);
321$client->retry(3, 100);
322
323// Make the request
324$response = $client->get('https://api.example.com/unstable-endpoint');
325
326// After the request completes, you can get debug info
327$debugInfo = $client->debug();
328echo "Request required " . $debugInfo['retries'] . " retries\n";
329```
330
331## Best Practices
332
3331. **Use Type-Safe Enums**: Leverage the Status enum for clearer and safer code when configuring retryable status codes.
334
3352. **Start with Conservative Settings**: Begin with a small number of retries (2-3) and moderate delays (100-200ms) and adjust based on your needs.
336
3373. **Be Mindful of Server Load**: Excessive retries can amplify problems during outages. Be respectful of the services you're calling.
338
3394. **Use Appropriate Timeout Values**: Set reasonable timeouts in conjunction with retries to avoid long-running requests.
340
3415. **Limit Retryable Status Codes**: Only retry on status codes that indicate transient issues. Don't retry on client errors like 400, 401, or 404.
342
3436. **Monitor Retry Activity**: Log retry attempts to identify recurring issues with specific endpoints.
344
3457. **Consider Retry-After Headers**: For rate limiting (429), respect the Retry-After header if provided by the server.
346
3478. **Add Jitter**: The built-in retry mechanism includes jitter, which helps prevent "thundering herd" problems.
348
3499. **Combine with Logging**: Always add logging when using retries to track and debug retry patterns.
350
35110. **Use Async Retries for Parallel Operations**: When working with async code, use the retry function for better integration with the async/await pattern.
352
353## Next Steps
354
355- Learn about [Error Handling](/guide/error-handling) for comprehensive error management
356- Explore [Logging](/guide/logging) for monitoring request and retry activity
357- See [Authentication](/guide/authentication) for handling authentication errors and retries
358- Check out [Asynchronous Requests](/guide/async-requests) for integrating retries with async operations