99use AsyncAws \Core \Credentials \CacheProvider ;
1010use AsyncAws \Core \Credentials \ChainProvider ;
1111use AsyncAws \Core \Credentials \CredentialProvider ;
12+ use AsyncAws \Core \EndpointDiscovery \EndpointCache ;
1213use AsyncAws \Core \Exception \InvalidArgument ;
1314use AsyncAws \Core \Exception \LogicException ;
15+ use AsyncAws \Core \Exception \RuntimeException ;
1416use AsyncAws \Core \HttpClient \AwsRetryStrategy ;
1517use AsyncAws \Core \Signer \Signer ;
1618use AsyncAws \Core \Signer \SignerV4 ;
@@ -59,6 +61,11 @@ abstract class AbstractApi
5961 */
6062 private $ awsErrorFactory ;
6163
64+ /**
65+ * @var EndpointCache
66+ */
67+ private $ endpointCache ;
68+
6269 /**
6370 * @param Configuration|array $configuration
6471 */
@@ -72,6 +79,7 @@ public function __construct($configuration = [], ?CredentialProvider $credential
7279
7380 $ this ->logger = $ logger ?? new NullLogger ();
7481 $ this ->awsErrorFactory = $ this ->getAwsErrorFactory ();
82+ $ this ->endpointCache = new EndpointCache ();
7583 if (!isset ($ httpClient )) {
7684 $ httpClient = HttpClient::create ();
7785 if (class_exists (RetryableHttpClient::class)) {
@@ -132,7 +140,7 @@ protected function getSignatureScopeName(): string
132140
133141 final protected function getResponse (Request $ request , ?RequestContext $ context = null ): Response
134142 {
135- $ request ->setEndpoint ($ this ->getEndpoint ($ request ->getUri (), $ request ->getQuery (), $ context ? $ context ->getRegion () : null ));
143+ $ request ->setEndpoint ($ this ->getDiscoveredEndpoint ($ request ->getUri (), $ request ->getQuery (), $ context ? $ context ->getRegion () : null , $ context ? $ context -> usesEndpointDiscovery () : false , $ context ? $ context -> requiresEndpointDiscovery () : false ));
136144
137145 if (null !== $ credentials = $ this ->credentialProvider ->getCredentials ($ this ->configuration )) {
138146 $ this ->getSigner ($ context ? $ context ->getRegion () : null )->sign ($ request , $ credentials , $ context ?? new RequestContext ());
@@ -166,7 +174,7 @@ final protected function getResponse(Request $request, ?RequestContext $context
166174 ]);
167175 }
168176
169- return new Response ($ response , $ this ->httpClient , $ this ->logger , $ this ->awsErrorFactory , $ debug , $ context ? $ context ->getExceptionMapping () : []);
177+ return new Response ($ response , $ this ->httpClient , $ this ->logger , $ this ->awsErrorFactory , $ this -> endpointCache , $ request , $ debug , $ context ? $ context ->getExceptionMapping () : []);
170178 }
171179
172180 /**
@@ -255,6 +263,49 @@ protected function getEndpoint(string $uri, array $query, ?string $region): stri
255263 return $ endpoint . (false === strpos ($ endpoint , '? ' ) ? '? ' : '& ' ) . http_build_query ($ query );
256264 }
257265
266+ protected function discoverEndpoints (?string $ region ): array
267+ {
268+ throw new LogicException (sprintf ('The Client "%s" must implement the "%s" method. ' , \get_class ($ this ), 'discoverEndpoints ' ));
269+ }
270+
271+ private function getDiscoveredEndpoint (string $ uri , array $ query , ?string $ region , bool $ usesEndpointDiscovery , bool $ requiresEndpointDiscovery )
272+ {
273+ if (!$ this ->configuration ->isDefault ('endpoint ' )) {
274+ return $ this ->getEndpoint ($ uri , $ query , $ region );
275+ }
276+
277+ $ usesEndpointDiscovery = $ requiresEndpointDiscovery || ($ usesEndpointDiscovery && filter_var ($ this ->configuration ->get (Configuration::OPTION_ENDPOINT_DISCOVERY_ENABLED ), \FILTER_VALIDATE_BOOLEAN ));
278+ if (!$ usesEndpointDiscovery ) {
279+ return $ this ->getEndpoint ($ uri , $ query , $ region );
280+ }
281+
282+ // 1. use an active endpoints
283+ if (null === $ endpoint = $ this ->endpointCache ->getActiveEndpoint ($ region )) {
284+ $ previous = null ;
285+
286+ try {
287+ // 2. call API to fetch new endpoints
288+ $ endpoints = $ this ->discoverEndpoints ($ region );
289+ $ this ->endpointCache ->addEndpoints ($ region , $ endpoints );
290+
291+ // 3. use active endpoints that has just been injected
292+ $ endpoint = $ this ->endpointCache ->getActiveEndpoint ($ region );
293+ } catch (\Exception $ previous ) {
294+ }
295+
296+ // 4. if endpoint is still null, fallback to expired endpoint
297+ if (null === $ endpoint && null === $ endpoint = $ this ->endpointCache ->getExpiredEndpoint ($ region )) {
298+ if ($ requiresEndpointDiscovery ) {
299+ throw new RuntimeException (sprintf ('The Client "%s" failed to fetch the endpoint. ' , \get_class ($ this )), 0 , $ previous );
300+ }
301+
302+ return $ this ->getEndpoint ($ uri , $ query , $ region );
303+ }
304+ }
305+
306+ return $ endpoint ;
307+ }
308+
258309 /**
259310 * @param ?string $region region provided by the user in the `@region` parameter of the Input
260311 */
0 commit comments