caseyamcl/guzzle_retry_middleware
Guzzle middleware that automatically retries failed HTTP requests with configurable delays and retry conditions. Helps handle transient network errors, 5xx responses, and rate limiting with backoff strategies, improving resilience without changing client code.
Installation
composer require caseyamcl/guzzle_retry_middleware
Add to composer.json if using Guzzle 6:
"require": {
"guzzlehttp/guzzle": "^6.0|^7.0",
"caseyamcl/guzzle_retry_middleware": "^2.0"
}
Basic Usage
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use Caseyamcl\GuzzleRetryMiddleware\RetryMiddleware;
$client = new Client([
'handler' => HandlerStack::create([
new RetryMiddleware(), // Add middleware
// Other middleware (e.g., logging, auth)
]),
]);
$response = $client->get('https://api.example.com/rate-limited-endpoint');
First Use Case Retry failed requests (e.g., 429 Too Many Requests) with exponential backoff:
$client->get('https://api.example.com/limited-resource');
// Automatically retries on 429/503 with delays (default: 100ms, 200ms, 400ms, etc.)
Custom Retry Logic Override defaults (e.g., retry on 5xx only):
$middleware = new RetryMiddleware([
'retry_on' => [500, 502, 503, 504],
'max_retries' => 3,
]);
Conditional Retries
Use shouldRetry callback:
$middleware = new RetryMiddleware([
'should_retry' => function ($retries, $response) {
return $retries < 3 && $response->getStatusCode() === 429;
},
]);
Integration with Existing Stack Add to Guzzle’s middleware stack after logging/auth but before handlers:
$stack = HandlerStack::create();
$stack->push(new RetryMiddleware());
$stack->push(\GuzzleHttp\Middleware::tap(...));
Async Requests
Works seamlessly with GuzzleHttp\Promise\PromiseInterface:
$promise = $client->getAsync('https://api.example.com/async-endpoint');
$promise->then(...)->wait();
Using Delay Option (v2.13.0+)
Leverage Guzzle's native delay option for non-blocking retries:
$client = new Client([
'handler' => HandlerStack::create([
new RetryMiddleware(['delay' => 100]), // Delay in milliseconds
]),
'delay' => 100, // Optional: Set default delay for all requests
]);
GuzzleHttp\Middleware::retryIf for custom conditions.php-circuitbreaker to fail fast after retries.GuzzleHttp\Middleware::tap:
$stack->push(\GuzzleHttp\Middleware::tap(
function ($request, $response) {
if ($response->getStatusCode() >= 400) {
logger()->warning("Retry attempt for {$request->getUri()}");
}
}
));
Infinite Retries
max_retries is null (unlimited). Always set a cap:
new RetryMiddleware(['max_retries' => 5])
Blocking Requests (Pre-v2.13.0)
usleep, which could block parallel requests. v2.13.0+ uses Guzzle's delay option for non-blocking behavior.Non-Idempotent Requests
POST/PUT/DELETE may cause side effects. Use retry_on to exclude non-safe methods:
new RetryMiddleware(['retry_on' => [429, 503], 'retry_methods' => ['GET']])
Race Conditions
jitter to randomize delays:
new RetryMiddleware(['jitter' => true]) // Adds ±20% randomness to delays
$stack->push(new \GuzzleHttp\Handler\CurlHandler(), 'curl');
$stack->push(\GuzzleHttp\Middleware::tap(...)); // Log retries
Retry-After headers. The middleware respects these but logs them:
$middleware = new RetryMiddleware(['log_retries' => true]);
Custom Delay Strategy (v2.13.0+)
Replace the default exponential backoff using Guzzle's delay option:
$middleware = new RetryMiddleware([
'delay' => function ($retries) {
return 1000 * (2 ** $retries); // Custom delay logic
},
]);
Pre-Retry Hooks Modify requests before retry (e.g., add headers):
$middleware = new RetryMiddleware([
'on_retry' => function ($request, $retries) {
$request = $request->withHeader('X-Retry-Attempt', $retries);
return $request;
},
]);
Post-Retry Actions Execute logic after retries (e.g., notify on failure):
$middleware = new RetryMiddleware([
'on_failure' => function ($request, $exception, $retries) {
if ($retries >= 3) {
notifyAdmin($exception);
}
},
]);
Psr\Http\Message interfaces. Ensure your middleware stack is compatible.retry_on accepts status codes as strings or integers (e.g., "429" or 429).RetryMiddleware early in the stack to catch failures before other middleware processes them.delay option now aligns with Guzzle's native delay option (in milliseconds).['delay' => 100] or ['delay' => fn($retries) => 100 * (2 ** $retries)].How can I help you explore Laravel packages today?