diff --git a/README.md b/README.md index 99bd5bc1..ec4b3478 100644 --- a/README.md +++ b/README.md @@ -56,28 +56,36 @@ $config->setDeveloperApiKey('*'); $hubspot = \HubSpot\Factory::create(null, $config); ``` -#### API Client comes with Middleware for implementation of Rate and Concurrent Limiting +#### Retry Middleware -It provides an ability to turn on retry for failed requests with statuses 429 or 500. Please note that Apps using OAuth are only subject to a limit of 100 requests every 10 seconds. +The API client provides retry middleware for three failure scenarios: rate limiting (429), internal server errors (500–503, 520–599), and connection errors. All middleware retry up to `RetryMiddlewareFactory::DEFAULT_MAX_RETRIES` (5) times by default. + +If no delay function is provided, Guzzle applies its built-in exponential backoff. Available delay helpers: + +- `Delay::getConstantDelayFunction(int $secondsDelay = 10)` — fixed delay between retries +- `Delay::getLinearDelayFunction()` — delay grows linearly with retry count + +Please note that Apps using OAuth are only subject to a limit of 100 requests every 10 seconds. ```php $handlerStack = \GuzzleHttp\HandlerStack::create(); + +// Retry on 429 with a constant 10-second delay $handlerStack->push( \HubSpot\RetryMiddlewareFactory::createRateLimitMiddleware( \HubSpot\Delay::getConstantDelayFunction() ) ); +// Retry on 5xx errors (exponential backoff by default) $handlerStack->push( - \HubSpot\RetryMiddlewareFactory::createInternalErrorsMiddleware( - \HubSpot\Delay::getExponentialDelayFunction(2) - ) + \HubSpot\RetryMiddlewareFactory::createInternalErrorsMiddleware() ); +// Retry on connection errors for cURL codes 52, 55, 56 (exponential backoff by default) +// Pass an empty array to retry all ConnectExceptions regardless of cURL error code $handlerStack->push( - \HubSpot\RetryMiddlewareFactory::createConnectionErrorsMiddleware( - \HubSpot\Delay::getExponentialDelayFunction(2) - ) + \HubSpot\RetryMiddlewareFactory::createConnectionErrorsMiddleware() ); $client = new \GuzzleHttp\Client(['handler' => $handlerStack]); diff --git a/lib/Delay.php b/lib/Delay.php index 24262799..25e1a2ed 100644 --- a/lib/Delay.php +++ b/lib/Delay.php @@ -18,6 +18,9 @@ public static function getLinearDelayFunction() }; } + /** + * @deprecated pass null as the delay function instead — Guzzle will apply its built-in exponential delay via \GuzzleHttp\RetryMiddleware::exponentialDelay + */ public static function getExponentialDelayFunction(int $base) { return function ($retries) use ($base) { diff --git a/lib/RetryMiddlewareFactory.php b/lib/RetryMiddlewareFactory.php index e78bf72f..45ccc0e4 100644 --- a/lib/RetryMiddlewareFactory.php +++ b/lib/RetryMiddlewareFactory.php @@ -22,7 +22,7 @@ public static function createConnectionErrorsMiddleware( array $curlErrorCodes = self::TRANSIENT_CURL_ERROR_CODES ): callable { return Middleware::retry( - static::getRetryFunctionByConnectionErrors($maxRetries, $curlErrorCodes), + static::getRetryFunctionByConnectionErrors($curlErrorCodes, $maxRetries), $delayFunction ); } @@ -146,8 +146,8 @@ public static function getRetryFunction( } public static function getRetryFunctionByConnectionErrors( - int $maxRetries = self::DEFAULT_MAX_RETRIES, - array $curlErrorCodes = self::TRANSIENT_CURL_ERROR_CODES + array $curlErrorCodes = self::TRANSIENT_CURL_ERROR_CODES, + int $maxRetries = self::DEFAULT_MAX_RETRIES ): callable { return function ( $retries, @@ -163,6 +163,10 @@ public static function getRetryFunctionByConnectionErrors( return false; } + if (empty($curlErrorCodes)) { + return true; + } + $handlerContext = $exception->getHandlerContext(); $errno = $handlerContext['errno'] ?? null; @@ -170,7 +174,7 @@ public static function getRetryFunctionByConnectionErrors( return true; } - if (preg_match('/cURL error\s+(\d+):/i', $exception->getMessage(), $matches) === 1) { + if (1 === preg_match('/cURL error\s+(\d+):/i', $exception->getMessage(), $matches)) { return in_array((int) $matches[1], $curlErrorCodes, true); } diff --git a/tests/Unit/RetryMiddlewareFactoryTest.php b/tests/Unit/RetryMiddlewareFactoryTest.php index a4d249dc..92b13779 100644 --- a/tests/Unit/RetryMiddlewareFactoryTest.php +++ b/tests/Unit/RetryMiddlewareFactoryTest.php @@ -17,7 +17,7 @@ class RetryMiddlewareFactoryTest extends TestCase /** @test */ public function itRetriesRetriableConnectionErrorsByErrno(): void { - $retry = RetryMiddlewareFactory::getRetryFunctionByConnectionErrors(3); + $retry = RetryMiddlewareFactory::getRetryFunctionByConnectionErrors(RetryMiddlewareFactory::TRANSIENT_CURL_ERROR_CODES, 3); $request = new Request('GET', 'https://api.hubapi.com/test'); $exception = new ConnectException( 'cURL error 56: OpenSSL SSL_read unexpected eof while reading', @@ -32,7 +32,7 @@ public function itRetriesRetriableConnectionErrorsByErrno(): void /** @test */ public function itRetriesRetriableConnectionErrorsByMessageWhenErrnoMissing(): void { - $retry = RetryMiddlewareFactory::getRetryFunctionByConnectionErrors(3); + $retry = RetryMiddlewareFactory::getRetryFunctionByConnectionErrors(RetryMiddlewareFactory::TRANSIENT_CURL_ERROR_CODES, 3); $request = new Request('GET', 'https://api.hubapi.com/test'); $exception = new ConnectException( 'cURL error 55: Send failure: Broken pipe', @@ -45,7 +45,7 @@ public function itRetriesRetriableConnectionErrorsByMessageWhenErrnoMissing(): v /** @test */ public function itDoesNotRetryNonRetriableConnectionErrors(): void { - $retry = RetryMiddlewareFactory::getRetryFunctionByConnectionErrors(3); + $retry = RetryMiddlewareFactory::getRetryFunctionByConnectionErrors(RetryMiddlewareFactory::TRANSIENT_CURL_ERROR_CODES, 3); $request = new Request('GET', 'https://api.hubapi.com/test'); $exception = new ConnectException( 'cURL error 60: SSL certificate problem', @@ -60,7 +60,7 @@ public function itDoesNotRetryNonRetriableConnectionErrors(): void /** @test */ public function itStopsRetryingWhenMaxRetriesReached(): void { - $retry = RetryMiddlewareFactory::getRetryFunctionByConnectionErrors(1); + $retry = RetryMiddlewareFactory::getRetryFunctionByConnectionErrors(RetryMiddlewareFactory::TRANSIENT_CURL_ERROR_CODES, 1); $request = new Request('GET', 'https://api.hubapi.com/test'); $exception = new ConnectException( 'cURL error 56: OpenSSL SSL_read unexpected eof while reading',