Guzzle 6 retry middleware

December 18, 2015 14 By addshore

Recently I switched from using Guzzle 5 to Guzzle 6 in my mediawiki-api-base PHP library. Everything went very smoothly except for there being no compatible version of the retry-subscriber that I had previously used. The subscriber has been replaced by retry middleware of which I was provided an extracted example. In this post I cover my implementation for the mediawiki-api-base library.

Guzzle 5

With Guzzle 5 you would create a retry subscriber with a filter and then attach it to the event emitter for the guzzle client. A full example can be seen below where RetrySubscriber::createStatusFilter can be seen here.

// Retry 500 and 503 responses
$retry = new RetrySubscriber([
    'filter' => RetrySubscriber::createStatusFilter(),
    'delay'  => function ($number, $event) { return 1; },
    'max' => 3,
]);

$client = new GuzzleHttp\Client();
$client->getEmitter()->attach($retry);

Guzzle 6

Guzzle 6 got rid of its event system and switched to the afore mentioned middleware system.

Middleware is added to a handler stack upon Client construction as seen below:

$handlerStack = HandlerStack::create( new CurlHandler() );
$handlerStack->push( Middleware::retry( retryDecider(), retryDelay() ) );
$client = new Client( array( 'handler' => $handlerStack ) );

The retry middleware takes two parameters, the first is the callable function that decides if a request / response should be retried and the last deciding how long to wait before retrying (similar to in Guzzle 6). Each can be seen below.

function retryDecider() {
   return function (
      $retries,
      Request $request,
      Response $response = null,
      RequestException $exception = null
   ) {
      // Limit the number of retries to 5
      if ( $retries >= 5 ) {
         return false;
      }

      // Retry connection exceptions
      if( $exception instanceof ConnectException ) {
         return true;
      }

      if( $response ) {
         // Retry on server errors
         if( $response->getStatusCode() >= 500 ) {
            return true;
         }
      }

      return false;
   };
}
function retryDelay() {
    return function( $numberOfRetries ) {
        return 1000 * $numberOfRetries;
    };
}

When implementing this for the mediawiki-api-base library I actually ended up creating a MiddlewareFactory which can be seen at on github here which is fully tested here.

This implementation includes a more complex retry decider including logging.

Enjoy!