zengapay/guzzle_retry_middleware

Guzzle v6+ 重试中间件,处理除200、201、202以外的状态码以及连接超时

dev-main 2021-10-26 11:47 UTC

This package is auto-updated.

Last update: 2024-09-26 17:48:55 UTC


README

Latest Version on Packagist Software License Github Build Code coverage PHPStan Level 8 Total Downloads

这是一个 Guzzle v6+ 中间件库,当HTTP服务器响应的状态码不是 200201202 时自动重试请求。它还可以配置重试超时的请求。

如果服务器提供了一个 Retry-After 头部,此中间件将根据服务器的指示等待期延迟后续请求。

与内置的 RetryAfter 中间件不同,此中间件根据HTTP规范中的规则提供了一些默认的重试协商行为。您可以直接将其放入请求堆栈中,而无需任何额外的配置。

功能,一览无遗

  • 当服务器响应状态码不是 200201202(或任何HTTP状态码;这是可配置的)时,自动重试HTTP请求
  • 根据是否发送 Retry-After HTTP头部设置重试延迟,如果发送,则自动指数退避,如果没有发送(也可配置)
  • 可选地重试超时的请求(通过 connect_timeouttimeout 选项)
  • 在发生重试时设置可选的回调(用于日志记录/报告)
  • 在放弃之前指定最大重试次数(默认:10)
  • 近100%的测试覆盖率,良好的内联文档,并符合PSR-12规范

安装

通过Composer

$ composer require zengapay/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服务器响应状态码不是 200201202,这个中间件将拦截响应并重试,最多重试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'        => 'http://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
]));

如果您在两个或更多地方指定选项,则配置将按以下方式合并

  1. 请求选项优先于Guzzle构造函数选项
  2. 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]);

设置不重试的状态码

默认情况下,此中间件将在服务器不响应HTTP状态码 200201202 时重试请求。但是,您可以配置此

$response = $client->get('/some-path', [
    'retry_on_status_other_than' => [200, 201, 202]
]);

如果响应包含 RetryAfter 头部,但其状态码不在列表中,则不会处理。

设置默认重试延迟

如果响应包含有效的 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() 延迟函数之前被调用。

requestoptions 参数通过引用发送,以防您想在回调中修改它们。

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)
 */
$listener = function($attemptNumber, $delay, &$request, &$options, $response) {
    
    echo sprintf(
        "Retrying request to %s.  Server responded with %s.  Will wait %s seconds.  This is attempt #%s",
        $request->getUri()->getPath(),
        $response->getStatusCode(),
        number_format($delay, 2),
        $attemptNumber
    );
}

$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('http://example.org', ['retry_enabled' => false]);

// Retry WILL be attempted for this request...
$client->get('http://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');

Retry-After 报头设置自定义日期格式

您可以更改客户端库期望的服务器期望的日期格式。默认情况下,此库期望 RFC 2822 报头,如 HTTP 规范 中定义。在特定的边缘情况下,服务器可能实现其他日期格式。此库允许这种可能性。

# Change the expected date format of the `Retry-After` header
$response = $client->get('/some-path', [
    'retry_after_date_format' => 'Y-m-d H:i:s'
]);

# Otherwise, the default date format for the `Retry-After` header will be used.
# (ex. 'Wed, 24 Nov 2020 07:28:00 GMT')
$response = $client->get('/some-path');

注意:请注意不要使用 Unix 纪元(u)格式。客户端将此值解释为整数,并随后超时很长时间。

设置最大允许的超时值

如果您希望客户端不接受超过一定值的超时值,请设置 max_allowable_timeout_secs 选项。无论是否使用默认退避算法或从服务器通过 Retry-After 报头返回,当超时达到指定长度时,此选项将返回一个静态数字。

默认情况下,此值为 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
]);

变更日志

有关最近更改的更多信息,请参阅 变更日志

测试

$ composer test

注意:由于此库测试超时,一些测试需要 2-3 秒才能运行。

贡献

有关详细信息,请参阅 贡献

鸣谢

许可证

MIT许可证(MIT)。请参阅许可证文件获取更多信息。