mingalevme / retryable-psr-http-client
The package provides a familiar PSR HTTP Client interface with automatic retries and exponential backoff (and others strategies). It is a thin wrapper over the standard PSR HTTP Client and exposes same public API. This makes the decorator very easy to drop into existing programs.
2.0.0
2024-07-05 10:42 UTC
Requires
- php: ^8.0
- psr/clock: ^1.0
- psr/http-client: ^1.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.13
- guzzlehttp/psr7: ^2.4
- mingalevme/psr-http-client-stubs: ^2.0
- phpstan/phpstan: ^1.9
- phpunit/phpunit: ^9.5
- squizlabs/php_codesniffer: ^3.7
- vimeo/psalm: ^5.1
Provides
This package is auto-updated.
Last update: 2024-12-05 11:37:36 UTC
README
Simple Retryable Psr Http Client Decorator with Retry-After-header support* and 100% code coverage.
NOTE: Retry-After-header handling is disabled by default because relying on untrusted headers it risky and dangerous, turn it on only if you clearly understand the consequences.
Composer
composer require mingalevme/retryable-psr-http-client
Example 1 (Simple drop-in replacement)
- Max 3 attempts.
- Triggers on 5xx/429 response status codes and/or
Psr\Http\Client\ClientExceptionInterface
. - Exponential backoff: 2 power of attempt number (1, 2, 4, 8, ...).
use Mingalevme\RetryablePsrHttpClient\RetryablePsrHttpClient; use Psr\Http\Client\ClientInterface; $someDiContainer->decorate(ClientInterface::class, function (ClientInterface $client): RetryablePsrHttpClient { return new RetryablePsrHttpClient($client); });
Example 2 (Extended Usage)
- Max 5 attempts.
- Respect Retry-After-header.
- Liner backoff with 1s (initial value) + 2s (slope).
- Triggers on 4xx, 5xx and
Psr\Http\Client\ClientExceptionInterface
. - Log on any error (unacceptable response or
Psr\Http\Client\ClientExceptionInterface
-exception).
<?php use Mingalevme\RetryablePsrHttpClient\BackoffCalc\LinearBackoffCalc; use Mingalevme\RetryablePsrHttpClient\Config; use Mingalevme\RetryablePsrHttpClient\NullEventListener; use Mingalevme\RetryablePsrHttpClient\ResponseAnalyzer\ResponseAnalyzerInterface; use Mingalevme\RetryablePsrHttpClient\RetryablePsrHttpClient; use Psr\Http\Client\ClientExceptionInterface; use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; use Psr\Log\LoggerInterface; final class MyAppHttpClientErrLogEventListener extends AbstractEventListener { public function __construct( private LoggerInterface $logger, ) { } public function onError( int $attemptNumber, RequestInterface $request, ClientExceptionInterface|ResponseInterface $error, ): void { $this->logger->error("Error while sending request {$request->getUri()}, attempt #$attemptNumber"); } } final class MyAppHttpResponseAnalyzer implements ResponseAnalyzerInterface { public function isAcceptable(ResponseInterface $response): bool { return $response->getStatusCode() >= 400; } } $someDiContainer->decorate( ClientInterface::class, function ( ClientInterface $client, MyAppHttpClientErrLogEventListener $listener, MyAppHttpResponseAnalyzer $responseAnalyzer, ): RetryablePsrHttpClient { $config = Config::new() ->setRetryCount(5) ->setRespectRetryAfterHeader(true) ->setBackoffCalc(new LinearBackoffCalc(2, 1)) ->setResponseAnalyzer($responseAnalyzer) ->addEventListener($listener); return new RetryablePsrHttpClient($client, $config); }, );