Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Guzzle Retry Middleware Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. 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"
    }
    
  2. 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');
    
  3. 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.)
    

Implementation Patterns

Common Workflows

  1. Custom Retry Logic Override defaults (e.g., retry on 5xx only):

    $middleware = new RetryMiddleware([
        'retry_on' => [500, 502, 503, 504],
        'max_retries' => 3,
    ]);
    
  2. Conditional Retries Use shouldRetry callback:

    $middleware = new RetryMiddleware([
        'should_retry' => function ($retries, $response) {
            return $retries < 3 && $response->getStatusCode() === 429;
        },
    ]);
    
  3. 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(...));
    
  4. Async Requests Works seamlessly with GuzzleHttp\Promise\PromiseInterface:

    $promise = $client->getAsync('https://api.example.com/async-endpoint');
    $promise->then(...)->wait();
    
  5. 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
    ]);
    

Advanced Patterns

  • Rate-Limited APIs: Pair with GuzzleHttp\Middleware::retryIf for custom conditions.
  • Circuit Breaker: Combine with php-circuitbreaker to fail fast after retries.
  • Metrics: Log retry attempts with GuzzleHttp\Middleware::tap:
    $stack->push(\GuzzleHttp\Middleware::tap(
        function ($request, $response) {
            if ($response->getStatusCode() >= 400) {
                logger()->warning("Retry attempt for {$request->getUri()}");
            }
        }
    ));
    

Gotchas and Tips

Pitfalls

  1. Infinite Retries

    • Default max_retries is null (unlimited). Always set a cap:
      new RetryMiddleware(['max_retries' => 5])
      
  2. Blocking Requests (Pre-v2.13.0)

    • Older versions used usleep, which could block parallel requests. v2.13.0+ uses Guzzle's delay option for non-blocking behavior.
  3. Non-Idempotent Requests

    • Retrying POST/PUT/DELETE may cause side effects. Use retry_on to exclude non-safe methods:
      new RetryMiddleware(['retry_on' => [429, 503], 'retry_methods' => ['GET']])
      
  4. Race Conditions

    • Concurrent retries (e.g., in queues) may hit rate limits faster. Use jitter to randomize delays:
      new RetryMiddleware(['jitter' => true]) // Adds ±20% randomness to delays
      

Debugging

  • Log Retries: Enable Guzzle’s debug handler to inspect retry attempts:
    $stack->push(new \GuzzleHttp\Handler\CurlHandler(), 'curl');
    $stack->push(\GuzzleHttp\Middleware::tap(...)); // Log retries
    
  • Check Headers: Some APIs return Retry-After headers. The middleware respects these but logs them:
    $middleware = new RetryMiddleware(['log_retries' => true]);
    

Extension Points

  1. 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
        },
    ]);
    
  2. 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;
        },
    ]);
    
  3. 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);
            }
        },
    ]);
    

Config Quirks

  • Guzzle 6 vs. 7: The package supports both, but Guzzle 7 uses Psr\Http\Message interfaces. Ensure your middleware stack is compatible.
  • Case Sensitivity: retry_on accepts status codes as strings or integers (e.g., "429" or 429).
  • Priority in Stack: Place RetryMiddleware early in the stack to catch failures before other middleware processes them.
  • Delay Option (v2.13.0+)
    • The delay option now aligns with Guzzle's native delay option (in milliseconds).
    • For dynamic delays, pass a callable that returns the delay in milliseconds.
    • Example: ['delay' => 100] or ['delay' => fn($retries) => 100 * (2 ** $retries)].
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
besmartand-pro/php-quality-config
sentix/ai-chatbot
terminal42/code-quality-tools
codifyo/ts-generator-bundle
testo/fiber
mintobit/jobqueue
a4sex/maintenance-bundle
a4sex/entity-date-update
a4sex/client-identifier
a4sex/base-utilites
a4sex/key-value-storage
a4sex/micro-status
chilldev/dependency-injection-extra
datinglibre/datinglibre-app-api
biberltd/corebundle
bricre/symfony-bundle-test
biberltd/logbundle
dominium/http-adapter-bundle
dominium/google-analytics
a4sex/auto-clean-entity