Skip to content

Commit 9e054a2

Browse files
Add support for Guzzle 8
1 parent 9d3538b commit 9e054a2

17 files changed

Lines changed: 386 additions & 405 deletions

composer.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@
1717
},
1818
"require": {
1919
"php": ">=8.1",
20-
"guzzlehttp/guzzle": "^7.4.5",
21-
"guzzlehttp/psr7": "^2.4.5",
22-
"guzzlehttp/promises": "^2.0",
20+
"guzzlehttp/guzzle": "^7.4.5 || ^8.0",
21+
"guzzlehttp/psr7": "^2.4.5 || ^3.0",
22+
"guzzlehttp/promises": "^2.0 || ^3.0",
2323
"mtdowling/jmespath.php": "^2.8.0",
2424
"ext-pcre": "*",
2525
"ext-json": "*",

src/Credentials/EcsCredentialProvider.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
use Aws\Arn\Arn;
55
use Aws\Exception\CredentialsException;
6-
use GuzzleHttp\Exception\ConnectException;
6+
use Aws\Handler\HttpHandlerError;
77
use GuzzleHttp\Exception\GuzzleException;
88
use GuzzleHttp\Psr7\Request;
99
use GuzzleHttp\Promise;
@@ -105,13 +105,14 @@ public function __invoke()
105105
CredentialSources::ECS
106106
);
107107
})->otherwise(function ($reason) {
108-
$reason = is_array($reason) ? $reason['exception'] : $reason;
108+
$connectionError = is_array($reason) && !empty($reason['connection_error']);
109+
$exception = is_array($reason) ? ($reason['exception'] ?? null) : $reason;
110+
$isRetryable = $connectionError || ($exception instanceof \Throwable && HttpHandlerError::isConnectionError($exception));
109111

110-
$isRetryable = $reason instanceof ConnectException;
111112
if ($isRetryable && ($this->attempts < $this->retries)) {
112113
sleep((int)pow(1.2, $this->attempts));
113114
} else {
114-
$msg = $reason->getMessage();
115+
$msg = $exception instanceof \Throwable ? $exception->getMessage() : \Aws\describe_type($reason);
115116
throw new CredentialsException(
116117
sprintf('Error retrieving credentials from container metadata after attempt %d/%d (%s)', $this->attempts, $this->retries, $msg)
117118
);

src/Handler/Guzzle/GuzzleHandler.php

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
<?php
22
namespace Aws\Handler\Guzzle;
33

4-
use Exception;
5-
use GuzzleHttp\Exception\ConnectException;
6-
use GuzzleHttp\Exception\RequestException;
4+
use Aws\Handler\HttpHandlerError;
75
use GuzzleHttp\Utils;
86
use GuzzleHttp\Promise;
97
use GuzzleHttp\Client;
@@ -31,7 +29,7 @@ public function __construct(?ClientInterface $client = null)
3129
* @param Psr7Request $request
3230
* @param array $options
3331
*
34-
* @return Promise\Promise
32+
* @return Promise\PromiseInterface
3533
*/
3634
public function __invoke(Psr7Request $request, array $options = [])
3735
{
@@ -43,21 +41,12 @@ public function __invoke(Psr7Request $request, array $options = [])
4341

4442
return $this->client->sendAsync($request, $this->parseOptions($options))
4543
->otherwise(
46-
static function ($e) {
47-
$error = [
44+
static function (\Throwable $e) {
45+
return new Promise\RejectedPromise([
4846
'exception' => $e,
49-
'connection_error' => $e instanceof ConnectException,
50-
'response' => null,
51-
];
52-
53-
if (
54-
($e instanceof RequestException)
55-
&& $e->getResponse()
56-
) {
57-
$error['response'] = $e->getResponse();
58-
}
59-
60-
return new Promise\RejectedPromise($error);
47+
'connection_error' => HttpHandlerError::isConnectionError($e),
48+
'response' => HttpHandlerError::getResponse($e),
49+
]);
6150
}
6251
);
6352
}

src/Handler/HttpHandlerError.php

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
namespace Aws\Handler;
3+
4+
use GuzzleHttp\Exception\ConnectException;
5+
use GuzzleHttp\Exception\NetworkException;
6+
use GuzzleHttp\Exception\RequestException;
7+
use GuzzleHttp\Exception\ResponseException;
8+
use GuzzleHttp\Exception\ResponseTransferException;
9+
use Psr\Http\Message\ResponseInterface;
10+
11+
/**
12+
* @internal
13+
*/
14+
final class HttpHandlerError
15+
{
16+
private const CURLE_RECV_ERROR = 56;
17+
18+
public static function isConnectionError(\Throwable $exception): bool
19+
{
20+
// Guzzle 8: transfer failures have dedicated exception classes.
21+
if ($exception instanceof NetworkException || $exception instanceof ResponseTransferException) {
22+
return true;
23+
}
24+
25+
// Guzzle 7: connection establishment failures use ConnectException.
26+
if ($exception instanceof ConnectException) {
27+
return true;
28+
}
29+
30+
// Guzzle 7: mid-response receive failures identifiable by cURL handler context.
31+
if ($exception instanceof RequestException && is_callable([$exception, 'getHandlerContext'])
32+
) {
33+
$context = $exception->getHandlerContext();
34+
35+
return !empty($context['errno']) && $context['errno'] === self::CURLE_RECV_ERROR;
36+
}
37+
38+
return false;
39+
}
40+
41+
public static function getResponse(\Throwable $exception): ?ResponseInterface
42+
{
43+
// Guzzle 8: response-aware failures expose the response through ResponseException.
44+
if ($exception instanceof ResponseException) {
45+
return $exception->getResponse();
46+
}
47+
48+
// Guzzle 7: RequestException directly carried an optional response.
49+
if ($exception instanceof RequestException && is_callable([$exception, 'getResponse'])
50+
) {
51+
return $exception->getResponse();
52+
}
53+
54+
return null;
55+
}
56+
}

src/Retry/V3/RetryMiddleware.php

Lines changed: 4 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
use Aws\Retry\ConfigurationInterface;
99
use Aws\Retry\RateLimiter;
1010
use Aws\Retry\RetryHelperTrait;
11-
use GuzzleHttp\Exception\RequestException;
1211
use GuzzleHttp\Promise;
1312
use GuzzleHttp\Promise\PromiseInterface;
1413
use Psr\Http\Message\RequestInterface;
@@ -72,7 +71,6 @@ class RetryMiddleware
7271
private array $options;
7372
private QuotaManager $quotaManager;
7473
private ?RateLimiter $rateLimiter = null;
75-
private array $retryCurlErrors;
7674
private ?string $service;
7775

7876
public static function wrap(ConfigurationInterface $config, array $options): \Closure
@@ -84,23 +82,17 @@ public static function wrap(ConfigurationInterface $config, array $options): \Cl
8482

8583
/**
8684
* Returns a closure that decides retryability for a given result based
87-
* on the standard error codes, status codes, and curl errors. Quota and
88-
* max-attempts decisions are handled by the middleware itself, not by
89-
* this closure.
85+
* on the standard error codes and status codes. Quota and max-attempts
86+
* decisions are handled by the middleware itself, not by this closure.
9087
*/
9188
public static function createDefaultDecider(array $options = []): \Closure
9289
{
93-
$retryCurlErrors = [];
94-
if (extension_loaded('curl')) {
95-
$retryCurlErrors[CURLE_RECV_ERROR] = true;
96-
}
97-
9890
return function (
9991
int $attempts,
10092
CommandInterface $command,
10193
mixed $result
102-
) use ($options, $retryCurlErrors): bool {
103-
return self::isRetryable($result, $retryCurlErrors, $options);
94+
) use ($options): bool {
95+
return self::isRetryable($result, $options);
10496
};
10597
}
10698

@@ -128,16 +120,6 @@ public function __construct(
128120
? ($options['delayer'])(...)
129121
: null;
130122

131-
$this->retryCurlErrors = [];
132-
if (extension_loaded('curl')) {
133-
$this->retryCurlErrors[CURLE_RECV_ERROR] = true;
134-
}
135-
if (!empty($options['curl_errors']) && is_array($options['curl_errors'])) {
136-
foreach ($options['curl_errors'] as $code) {
137-
$this->retryCurlErrors[$code] = true;
138-
}
139-
}
140-
141123
if ($this->mode === 'adaptive') {
142124
$this->rateLimiter = $options['rate_limiter'] ?? new RateLimiter();
143125
}
@@ -198,7 +180,6 @@ public function __invoke(CommandInterface $cmd, RequestInterface $req): PromiseI
198180

199181
$isRetryable = self::isRetryable(
200182
$value,
201-
$this->retryCurlErrors,
202183
$this->options
203184
);
204185

@@ -342,7 +323,6 @@ private function computeRetryDelay(int $attemptIndex, bool $isThrottling, mixed
342323

343324
private static function isRetryable(
344325
mixed $result,
345-
array $retryCurlErrors,
346326
array $options = []
347327
): bool
348328
{
@@ -371,14 +351,6 @@ private static function isRetryable(
371351
}
372352
}
373353

374-
if (!empty($options['curl_errors'])
375-
&& is_array($options['curl_errors'])
376-
) {
377-
foreach ($options['curl_errors'] as $code) {
378-
$retryCurlErrors[$code] = true;
379-
}
380-
}
381-
382354
$isError = $result instanceof \Throwable;
383355

384356
if (!$isError) {
@@ -406,24 +378,6 @@ private static function isRetryable(
406378
return true;
407379
}
408380

409-
if (count($retryCurlErrors)
410-
&& ($previous = $result->getPrevious())
411-
&& $previous instanceof RequestException
412-
) {
413-
if (method_exists($previous, 'getHandlerContext')) {
414-
$context = $previous->getHandlerContext();
415-
return !empty($context['errno'])
416-
&& isset($retryCurlErrors[$context['errno']]);
417-
}
418-
419-
$message = $previous->getMessage();
420-
foreach (array_keys($retryCurlErrors) as $curlError) {
421-
if (str_starts_with($message, 'cURL error ' . $curlError . ':')) {
422-
return true;
423-
}
424-
}
425-
}
426-
427381
if (!empty($errorShape = $result->getAwsErrorShape())) {
428382
$definition = $errorShape->toArray();
429383
if (!empty($definition['retryable'])) {

src/RetryMiddleware.php

Lines changed: 1 addition & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
use Aws\Exception\AwsException;
55
use Aws\Retry\RetryHelperTrait;
6-
use GuzzleHttp\Exception\RequestException;
76
use Psr\Http\Message\RequestInterface;
87
use GuzzleHttp\Promise\PromiseInterface;
98
use GuzzleHttp\Promise;
@@ -67,9 +66,6 @@ public function __construct(
6766
* Optional.
6867
* - statusCodes: (int[]) An indexed array of HTTP status codes to retry.
6968
* Optional.
70-
* - curlErrors: (int[]) An indexed array of Curl error codes to retry. Note
71-
* these should be valid Curl constants. Optional.
72-
*
7369
* @param int $maxRetries
7470
* @param array $extraConfig
7571
* @return callable
@@ -78,18 +74,13 @@ public static function createDefaultDecider(
7874
$maxRetries = 3,
7975
$extraConfig = []
8076
) {
81-
$retryCurlErrors = [];
82-
if (extension_loaded('curl')) {
83-
$retryCurlErrors[CURLE_RECV_ERROR] = true;
84-
}
85-
8677
return function (
8778
$retries,
8879
CommandInterface $command,
8980
RequestInterface $request,
9081
?ResultInterface $result = null,
9182
$error = null
92-
) use ($maxRetries, $retryCurlErrors, $extraConfig) {
83+
) use ($maxRetries, $extraConfig) {
9384
// Allow command-level options to override this value
9485
$maxRetries = null !== $command['@retries'] ?
9586
$command['@retries']
@@ -98,7 +89,6 @@ public static function createDefaultDecider(
9889
$isRetryable = self::isRetryable(
9990
$result,
10091
$error,
101-
$retryCurlErrors,
10292
$extraConfig
10393
);
10494

@@ -119,7 +109,6 @@ public static function createDefaultDecider(
119109
private static function isRetryable(
120110
$result,
121111
$error,
122-
$retryCurlErrors,
123112
$extraConfig = []
124113
) {
125114
$errorCodes = self::$retryCodes;
@@ -140,14 +129,6 @@ private static function isRetryable(
140129
}
141130
}
142131

143-
if (!empty($extraConfig['curl_errors'])
144-
&& is_array($extraConfig['curl_errors'])
145-
) {
146-
foreach($extraConfig['curl_errors'] as $code) {
147-
$retryCurlErrors[$code] = true;
148-
}
149-
}
150-
151132
if (!$error) {
152133
if (!isset($result['@metadata']['statusCode'])) {
153134
return false;
@@ -173,24 +154,6 @@ private static function isRetryable(
173154
return true;
174155
}
175156

176-
if (count($retryCurlErrors)
177-
&& ($previous = $error->getPrevious())
178-
&& $previous instanceof RequestException
179-
) {
180-
if (method_exists($previous, 'getHandlerContext')) {
181-
$context = $previous->getHandlerContext();
182-
return !empty($context['errno'])
183-
&& isset($retryCurlErrors[$context['errno']]);
184-
}
185-
186-
$message = $previous->getMessage();
187-
foreach (array_keys($retryCurlErrors) as $curlError) {
188-
if (strpos($message, 'cURL error ' . $curlError . ':') === 0) {
189-
return true;
190-
}
191-
}
192-
}
193-
194157
return false;
195158
}
196159

0 commit comments

Comments
 (0)