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