caseyamcl / guzzle_retry_middleware

Middleware for Guzzle v6/7+ that automatically retries HTTP requests on 429, 503 responses.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Retry on any ConnectException (CURLE_COULDNT_CONNECT et. al.)

LeoniePhiline opened this issue · comments

Thank you for creating this very useful Middleware! ✨

Detailed description

GuzzleRetryMiddleware handles situations very nicely where requests connect successfully but the server is too busy to remember responding to you (e.g. https://httpstat.us/200?sleep=500000).

However, when requesting e.g. https://example.com:81 (as in https://stackoverflow.com/questions/100841/artificially-create-a-connection-timeout-error), cURL fails with an errno of 7 (CURLE_COULDNT_CONNECT) after the connect_timeout has passed.

GuzzleRetryMiddleware only retries in cases of errno being 28 (CURLE_OPERATION_TIMEOUTED) and ignores errno 7 and other similar connection failures.

In fact, \GuzzleHttp\Handler\CurlFactory::createRejection() checks for five error codes when deciding if to throw a \GuzzleHttp\Exception\ConnectException() (otherwise throwing a \GuzzleHttp\Exception\RequestException):

static $connectionErrors = [
    CURLE_OPERATION_TIMEOUTED  => true,
    CURLE_COULDNT_RESOLVE_HOST => true,
    CURLE_COULDNT_CONNECT      => true,
    CURLE_SSL_CONNECT_ERROR    => true,
    CURLE_GOT_NOTHING          => true,
];

Context

The behavior of GuzzleRetryMiddleware is unexpected and quite unintuitive. :)
It does not seem obvious why one would only retry on CURLE_OPERATION_TIMEOUTED if at least CURLE_COULDNT_CONNECT is definitely a timeout error as well.

Furthermore, CURLE_COULDNT_RESOLVE_HOST, CURLE_SSL_CONNECT_ERROR and CURLE_GOT_NOTHING could also be of a temporary nature, and retrying in these cases would make for a more reliable client.

Possible implementation

Probably \GuzzleRetry\GuzzleRetryMiddleware::shouldRetryConnectException() should retry all and any \GuzzleHttp\Exception\ConnectException (as long as retry is enabled and the maximum number of attempts has not yet been made).
Thus, since we already know we are dealing with a ConnectException, the check for the cURL errno in this method is unnecessary.

Remove the following errno testing switch case:

// Test if this was a connection or response timeout exception
case isset($e->getHandlerContext()['errno']) && $e->getHandlerContext()['errno'] == 28

Return true by default:

// No exit conditions met, so return true to retry.
default:
    return true;