caseyamcl / guzzle_retry_middleware
Guzzle v6+ 重试中间件,处理 429/503 状态码和连接超时
Requires
- php: ^7.1|^8.0
- guzzlehttp/guzzle: ^6.3|^7.0
Requires (Dev)
- jaschilz/php-coverage-badger: ^2.0
- nesbot/carbon: ^2.0|^3.0
- phpstan/extension-installer: ^1.0
- phpstan/phpstan: ^1.0
- phpunit/phpunit: ^7.5|^8.0|^9.0
- squizlabs/php_codesniffer: ^3.5
- symfony/var-dumper: ^5.0|^6.0|^7.0
README
这是一个 Guzzle v6/7+ 中间件库,当 HTTP 服务器响应 503
或 429
状态码时,它将自动重试请求。它还可以配置重试超时的请求。
如果服务器提供了一个 Retry-After 头,这个中间件将根据服务器指示的等待时间延迟后续请求。
与内置的 RetryAfter
中间件不同,这个中间件提供了一些基于 HTTP 规范中规则的默认重试行为。您可以直接将其添加到请求堆栈中,而无需任何额外的配置。
功能,速览
- 当服务器响应 429 或 503 状态码(或任何 HTTP 状态码,这是可配置的)时,自动重试 HTTP 请求
- 根据
Retry-After
HTTP 头设置重试延迟,如果发送了,或者如果没有发送Retry-After
头,则自动指数退避(也可配置) - 可选地重试超时的请求(通过
connect_timeout
或timeout
选项) - 在发生重试时设置可选的回调(用于记录/报告)
- 指定在放弃之前最大重试次数(默认:10)
- 近 100% 的测试覆盖率,良好的内联文档,PSR-12 合规
安装
通过 Composer
$ composer require caseyamcl/guzzle_retry_middleware
用法
基本上
use GuzzleHttp\Client; use GuzzleHttp\HandlerStack; use GuzzleRetry\GuzzleRetryMiddleware; $stack = HandlerStack::create(); $stack->push(GuzzleRetryMiddleware::factory()); $client = new Client(['handler' => $stack]); // Make requests galore...
这是默认配置。如果 HTTP 服务器响应 429
或 503
状态码,此中间件将拦截响应并重试,最多重试 10 次,然后放弃并执行 Guzzle 默认行为(默认情况下,抛出 BadResponseException
)。
如果服务器提供了 RetryAfter
头,此中间件将在尝试请求之前等待指定的时间。如果没有,则每次请求之间的等待时间将越来越长,直到 10 次尝试后放弃。
选项
以下选项可用
以下将详细介绍每个选项。
配置选项
选项可以在三个位置之一设置
// Per request, in the same array as other Guzzle options $response = $client->get('/some-url', [ 'max_retry_attempts' => 5, 'on_retry_callback' => $notifier ]); // When you instantiate Guzzle, in the same array as other Guzzle options $client = new \GuzzleHttp\Client([ // Standard Guzzle options 'base_url' => 'https://example.org', 'connect_timeout' => 10.0, // Retry options 'max_retry_attempts' => 5, 'on_retry_callback' => $notifier ]); // When you instantiate the Retry middleware $stack = \GuzzleHttp\Stack::create(); $stack->push(GuzzleRetryMiddleware::factory([ 'max_retry_attempts' => 5, 'on_retry_callback' => $notifier ]));
如果您在两个或多个位置指定了选项,则配置将按以下方式合并
- 单个请求选项优先于 Guzzle 构造函数选项
- Guzzle 构造函数选项优先于中间件构造函数选项。
设置最大重试次数
此值应是一个大于等于 0 的整数。设置 0 或负数将有效地禁用此中间件。
将此值设置为 0 有用,当您想默认重试尝试,但为特定请求禁用重试时
// Set the default retry attempts to 5 $client = new \GuzzleHttp\Client(['max_retry_attempts' => 5]); // Do not retry this request $client->get('/some/url', ['max_retry_attempts' => 0]);
设置要重试的状态码
默认情况下,此中间件将在服务器响应 429
或 503
HTTP 状态码时重试请求。但是,您可以配置此
$response = $client->get('/some-path', [ 'retry_on_status' => [429, 503, 500] ]);
如果响应包括 RetryAfter
头,但其状态码不在列表中,则不会进行处理。
注意: 我还没有测试这个,但我真诚地相信如果你尝试使用这个中间件与3xx响应一起使用,你可能会看到一些古怪的行为。我不建议这么做。
设置默认重试延迟
如果响应中包含有效的RetryAfter
头,这个中间件将根据服务器在该头中指定的时长延迟下一次重试尝试。
如果响应中包含一个非有效的RetryAfter
或没有提供RetryAfter
头,那么这个中间件将使用默认的退避算法:multipler * number-of-attempts
带有RetryAfter
头的响应
Client Server
------ ------
GET /resource ->
<- 429 Response with `Retry-After: 120`
wait 120s
GET /resource ->
<- 200 OK
没有RetryAfter
,请求次数乘以乘数(默认:1.5
)
Client Server
------ ------
GET /resource ->
<- 429 Response (no Retry-After header)
wait 1.5 x 1s
GET /resource ->
<- 429 Response (no Retry-After header)
wait 1.5 x 2s
GET /resource ->
<- 429 Response (no Retry-After header)
wait 1.5 x 3s
GET /resource ->
<- 429 Response (no Retry-After header)
wait 1.5 x 4s
GET /resource ->
<- 200 OK
你可以设置自定义的默认乘数
$response = $client->get('/some-path', [ 'default_retry_multiplier' => 2.5 ]);
你也可以通过指定default_retry_multiplier
的调用对象来传递设置默认延迟超时的自定义算法
// Custom callback to determine default timeout. Note: $response may be NULL if a connect timeout occurred. $response = $client->get('/some-path', [ 'default_retry_multiplier' => function($numRequests, ?ResponseInterface $response): float { return (float) rand(1, 5); } ]);
重试超时请求
你可以配置这个中间件来重试超时请求。只需将retry_on_timeout
选项设置为true
// Retry this request if it times out: $response = $client->get('/some-path', [ 'retry_on_timeout' => true, // Set the retry middleware to retry when the connection or response times out 'connect_timeout' => 20, // This is a built-in Guzzle option 'timeout' => 50 // This is also a built-in Guzzle option ]); // You can also set these as defaults for every request: $guzzle = new \GuzzleHttp\Client(['retry_on_timeout' => true, 'connect_timeout' => 20]); $response = $guzzle->get('https://example.org');
重试回调
你可以提供一个回调方法,在每次请求重试之前将被调用。这对于记录、报告或其他任何你可以想到的事情都很有用。
如果你指定了回调,它将在中间件调用usleep()
延迟函数之前被调用。
request
和options
参数通过引用传递,以便你在回调中修改它们,在请求重新发送之前。
use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; /** * Listen for retry events * * @param int $attemptNumber How many attempts have been tried for this particular request * @param float $delay How long the client will wait before retrying the request * @param RequestInterface $request Request * @param array $options Guzzle request options * @param ResponseInterface|null $response Response (or NULL if response not sent; e.g. connect timeout) * @param Throwable|null $exception This value will be present if the retry was triggered by onRejected * (e.g. in the event of a connection timeout) */ $listener = function( int $attemptNumber, float $delay, RequestInterface &$request, array &$options, ?ResponseInterface $response, ?Throwable $exception ) { echo sprintf( "Retrying request to %s. Server responded with %s. Will wait %s seconds. This is attempt #%s. The error was %s", $request->getUri()->getPath(), $response->getStatusCode(), number_format($delay, 2), $attemptNumber, $exception->getMessage() ); } $client = new \GuzzleHttp\Client([ 'on_retry_callback' => $listener ]); $response = $client->get('/some/path');
按请求启用或禁用
假设你已经按照以下方式设置了默认重试选项
$stack = \GuzzleHttp\Stack::create(); $stack->push(GuzzleRetryMiddleware::factory(['max_retry_attempts' => 5])); $client = new \GuzzleHttp\Client(['handler' => $stack]);
你可以通过设置请求选项中的retry_enabled
参数来禁用单个请求的重试
// Retry will NOT be attempted for this request.. $client->get('https://example.org', ['retry_enabled' => false]); // Retry WILL be attempted for this request... $client->get('https://example.org');
向HTTP响应中添加自定义重试头
有时为了调试目的,知道在获取响应时请求重试了多少次是有用的。为此目的,这个库可以向响应添加自定义头;只需将expose_retry_header
选项设置为TRUE
。
注意:这修改了客户端的HTTP响应。如果你不想修改从服务器检索的响应,你也可以使用回调来获取请求计数。
示例
// Retry this request if it times out: $response = $client->get('/some-path', [ 'expose_retry_header' => true // This adds the 'X-Retry-Counter' if a request was retried ]); // If a request was retried, the response will include the 'X-Retry-Counter' $numRetries = (int) $response->getHeaderLine('X-Retry-Counter');
你也可以指定自定义头键
// Retry this request if it times out: $response = $client->get('/some-path', [ 'expose_retry_header' => true, 'retry_header' => 'X-Retry-Count' ]); // If a request was retried, the response will include the 'X-Retry-Counter' $numRetries = (int) $response->getHeaderLine('X-Retry-Count');
修改期望的头名称从Retry-After
你可以更改客户端期望服务器响应的头。默认情况下,客户端查找Retry-After
头,但在某些边缘情况下,服务器可能选择响应不同的头。
// Change the name of the expected retry after header to something else: $response = $client->get('/some-path', [ 'retry_after_header' => 'X-Custom-Retry-After-Seconds' ]); // Otherwise, the default `Retry-After` header will be used. $response = $client->get('/some-path');
为RetryAfter
头设置自定义日期格式
你可以更改客户端库期望的服务器期望的日期格式。默认情况下,此库期望RFC 2822头,如HTTP规范中定义。在某些边缘情况下,服务器可能实现其他日期格式。此库允许这种情况。
注意:请小心不要使用Unix纪元(u
)格式。客户端将此值解释为整数,并随后超时
非常、非常长时间。
设置最大允许的超时值
如果你想让客户端不接受超过特定值的超时值,请设置max_allowable_timeout_secs
选项。这将在超时达到指定长度时返回一个静态数字,无论它是使用默认退避算法计算的还是从服务器通过RetryAfter
头返回。
默认情况下,此值是 null
,表示没有限制。
// Set the maximum allowable timeout // If the calculated value exceeds 120 seconds, then just return 120 seconds $response = $client->get('/some-path', [ 'max_allowable_timeout_secs' => 120 ]);
为所有重试设置时间上限
如果您想为所有重试请求设置时间上限,请设置 give_up_after_secs
选项。如果设置了该选项,则会先检查时间上限,即使未达到重试次数限制,任何请求也会失败。
// This will fail when either the number of seconds is reached, or the number of retry attempts is reached, whichever // comes first $response = $client->get('/some-path', [ 'max_retry_attempts' => 10 'give_up_after_secs' => 10 ]);
设置特定HTTP方法进行重试
默认情况下,此库重试所有请求方法(GET
、POST
、PATCH
等)。如果您想限制此库将重试的HTTP方法,请使用方法数组指定 retry_on_methods
选项。
// $response = $client->get('/some-path', [ 'retry_on_methods' => ['GET', 'OPTIONS', 'HEAD'] ]);
自定义重试决策逻辑
有时,服务器在需要重试响应时可能会未能提供适当的HTTP错误代码。例如,考虑一个返回 200
状态代码,但消息体指示您等待的服务器。在这种情况下,您可以使用 should_retry_callback
选项来实现一个回调方法,该方法返回 true
(应该重试)或 false
(不应该重试)。
此回调的另一种用途可以基于请求本身。例如,您可能只想重试设置了特定头部的请求。
请注意,只有当以下所有情况都成立时,回调才会被调用
retry_enabled
选项为true
(默认:true
)- 重试次数未超过在
max_retry_attempts
中设置的值(默认:10
) - 总经过时间小于
give_up_after_secs
值(默认:禁用)
use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; $callback = function (array $options, ?ResponseInterface $response, RequestInterface $request): bool { // Only allow retries if x-header header is set if(! $request->hasHeader('x-header')) { return false; } // Response will be NULL in the event of a connection timeout, so your callback function // will need to be able to handle that case if (! $response) { return true; } // Get the HTTP body as a string $body = (string) $response->getBody(); return str_contains($body, 'error'); // NOTE: The str_contains function is available only in PHP 8+ }; $response = $client->get('/some-path', [ // ..other options.., 'should_retry_callback' => $callback ]);
变更日志
有关最近更改的更多信息,请参阅 CHANGELOG。
测试
$ composer test
注意: 由于此库测试超时,一些测试可能需要 2-3 秒才能运行。
贡献
有关详细信息,请参阅 CONTRIBUTING。
安全
如果您发现任何安全相关的问题,请通过电子邮件 [email protected] 而不是使用问题跟踪器。
鸣谢
许可协议
MIT 许可协议(MIT)。有关更多信息,请参阅 许可文件。