Guzzleでリトライする方法

Published:

Guzzleにはリトライミドルウェアがあり、これを使うことにより簡単にリトライ処理が出来ます。

リトライ条件

まずはどのような条件でリトライするか決めます。

リトライミドルウェアでは、通信が終了するとDeciderの処理を行います。 引数は、リトライ回数GuzzleHttp\Psr7\RequestGuzzleHttp\Psr7\Response例外です。 リトライ回数は0(リトライをしていない、最初の通信)から始まります。

このDeciderが true を返した場合はリトライをする、 false を返した場合はリトライをしません。 リトライ上限や、200レスポンスの時はリトライしないなど、全て自分で定義する必要があります。

サンプルは以下のとおりです。

private function retryDecider(int $maxRetry): Closure
{
    return function ($retries, $request, $response, $exception) use ($maxRetry) {  
        if ($retries >= $maxRetry - 1) {  
            return false;  
        }  

        if ($exception instanceof ConnectException) {  
            return true;  
        }  

        if ($response === null) {  
            return true;  
        }  

        if ($response->getStatusCode() === 200) {  
            return false;  
        }  

        return true;  
    };  
}

リトライ時の待機時間

次にリトライ時の待ち時間をdelayクロージャーで定義します。

引数はリトライ回数です。 戻り値はリトライ時の待ち時間のミリ秒です。

サンプルは以下のとおりです。

private function retryDelay(int $delayMillisecond): Closure  
{
    return function ($retries) use ($delayMillisecond) {  
        return $delayMillisecond;  
    };  
}

サンプルでは固定値ですが、リトライ回数が引数にあるので、リトライが増える毎に待ち時間を加算するなども可能です。

リトライミドルウェアを使う

後は以下のように、ハンドラーを設定するとリトライしてくれるようになります。

public function __construct()
{
    $handlerStack = HandlerStack::create();
  
    $handlerStack->push(Middleware::retry($this->retryDecider(3), $this->retryDelay(1000)));  

    $this->guzzle = new Client([
        'handler' => $handlerStack,  
    ]);
}

POSTのリトライ処理

上記のコードでは、リトライでのPOST時にBodyの中身が空になります。 これはGuzzleがデータをストリームで管理していることにより起こります。 最初のPOSTでストリームが進んでしまうので、リトライ時には位置をリセットする必要があります。

まず、以下のようなリセットハンドラーを用意し、シークが進んでいたら rewind でリセットします。

private function preserveRequestBody(): Closure  
{  
    return function ($handler) {  
        return function ($request, $options) use ($handler) {  
            if ($request->getBody()->isSeekable()) {  
                $request->getBody()->rewind();  
            }  
  
            return $handler($request, $options);  
        };  
    };  
}

次にこのハンドラーを設定します。 このハンドラーはリセットハンドラーより後に定義する必要があります。

public function __construct()
{
    $handlerStack = HandlerStack::create();
  
    $handlerStack->push(Middleware::retry($this->retryDecider(3), $this->retryDelay(1000)));  
    $handlerStack->push($this->preserveRequestBody();  

    $this->guzzle = new Client([
        'handler' => $handlerStack,  
    ]);
}

これでリトライ時でもPOSTが正しく送信されるようになります。