| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423 | 
							- <?php
 
- namespace Aws\EndpointDiscovery;
 
- use Aws\AwsClient;
 
- use Aws\CacheInterface;
 
- use Aws\CommandInterface;
 
- use Aws\Credentials\CredentialsInterface;
 
- use Aws\Exception\AwsException;
 
- use Aws\Exception\UnresolvedEndpointException;
 
- use Aws\LruArrayCache;
 
- use Aws\Middleware;
 
- use Psr\Http\Message\RequestInterface;
 
- use Psr\Http\Message\UriInterface;
 
- class EndpointDiscoveryMiddleware
 
- {
 
-     /**
 
-      * @var CacheInterface
 
-      */
 
-     private static $cache;
 
-     private static $discoveryCooldown = 60;
 
-     private $args;
 
-     private $client;
 
-     private $config;
 
-     private $discoveryTimes = [];
 
-     private $nextHandler;
 
-     private $service;
 
-     public static function wrap(
 
-         $client,
 
-         $args,
 
-         $config
 
-     ) {
 
-         return function (callable $handler) use (
 
-             $client,
 
-             $args,
 
-             $config
 
-         ) {
 
-             return new static(
 
-                 $handler,
 
-                 $client,
 
-                 $args,
 
-                 $config
 
-             );
 
-         };
 
-     }
 
-     public function __construct(
 
-         callable $handler,
 
-         AwsClient $client,
 
-         array $args,
 
-         $config
 
-     ) {
 
-         $this->nextHandler = $handler;
 
-         $this->client = $client;
 
-         $this->args = $args;
 
-         $this->service = $client->getApi();
 
-         $this->config = $config;
 
-     }
 
-     public function __invoke(CommandInterface $cmd, RequestInterface $request)
 
-     {
 
-         $nextHandler = $this->nextHandler;
 
-         $op = $this->service->getOperation($cmd->getName())->toArray();
 
-         // Continue only if endpointdiscovery trait is set
 
-         if (isset($op['endpointdiscovery'])) {
 
-             $config = ConfigurationProvider::unwrap($this->config);
 
-             $isRequired = !empty($op['endpointdiscovery']['required']);
 
-             if ($isRequired && !($config->isEnabled())) {
 
-                 throw new UnresolvedEndpointException('This operation '
 
-                     . 'requires the use of endpoint discovery, but this has '
 
-                     . 'been disabled in the configuration. Enable endpoint '
 
-                     . 'discovery or use a different operation.');
 
-             }
 
-             // Continue only if enabled by config
 
-             if ($config->isEnabled()) {
 
-                 if (isset($op['endpointoperation'])) {
 
-                     throw new UnresolvedEndpointException('This operation is '
 
-                         . 'contradictorily marked both as using endpoint discovery '
 
-                         . 'and being the endpoint discovery operation. Please '
 
-                         . 'verify the accuracy of your model files.');
 
-                 }
 
-                 // Original endpoint may be used if discovery optional
 
-                 $originalUri = $request->getUri();
 
-                 $identifiers = $this->getIdentifiers($op);
 
-                 $cacheKey = $this->getCacheKey(
 
-                     $this->client->getCredentials()->wait(),
 
-                     $cmd,
 
-                     $identifiers
 
-                 );
 
-                 // Check/create cache
 
-                 if (!isset(self::$cache)) {
 
-                     self::$cache = new LruArrayCache($config->getCacheLimit());
 
-                 }
 
-                 if (empty($endpointList = self::$cache->get($cacheKey))) {
 
-                     $endpointList = new EndpointList([]);
 
-                 }
 
-                 $endpoint = $endpointList->getActive();
 
-                 // Retrieve endpoints if there is no active endpoint
 
-                 if (empty($endpoint)) {
 
-                     try {
 
-                         $endpoint = $this->discoverEndpoint(
 
-                             $cacheKey,
 
-                             $cmd,
 
-                             $identifiers
 
-                         );
 
-                     } catch (\Exception $e) {
 
-                         // Use cached endpoint, expired or active, if any remain
 
-                         $endpoint = $endpointList->getEndpoint();
 
-                         if (empty($endpoint)) {
 
-                             return $this->handleDiscoveryException(
 
-                                 $isRequired,
 
-                                 $originalUri,
 
-                                 $e,
 
-                                 $cmd,
 
-                                 $request
 
-                             );
 
-                         }
 
-                     }
 
-                 }
 
-                 $request = $this->modifyRequest($request, $endpoint);
 
-                 $g = function ($value) use (
 
-                     $cacheKey,
 
-                     $cmd,
 
-                     $identifiers,
 
-                     $isRequired,
 
-                     $originalUri,
 
-                     $request,
 
-                     &$endpoint,
 
-                     &$g
 
-                 ) {
 
-                     if ($value instanceof AwsException
 
-                         && (
 
-                             $value->getAwsErrorCode() == 'InvalidEndpointException'
 
-                             || $value->getStatusCode() == 421
 
-                         )
 
-                     ) {
 
-                         return $this->handleInvalidEndpoint(
 
-                             $cacheKey,
 
-                             $cmd,
 
-                             $identifiers,
 
-                             $isRequired,
 
-                             $originalUri,
 
-                             $request,
 
-                             $value,
 
-                             $endpoint,
 
-                             $g
 
-                         );
 
-                     }
 
-                     
 
-                     return $value;
 
-                 };
 
-                 return $nextHandler($cmd, $request)->otherwise($g);
 
-             }
 
-         }
 
-         return $nextHandler($cmd, $request);
 
-     }
 
-     private function discoverEndpoint(
 
-         $cacheKey,
 
-         CommandInterface $cmd,
 
-         array $identifiers
 
-     ) {
 
-         $discCmd = $this->getDiscoveryCommand($cmd, $identifiers);
 
-         $this->discoveryTimes[$cacheKey] = time();
 
-         $result = $this->client->execute($discCmd);
 
-         if (isset($result['Endpoints'])) {
 
-             $endpointData = [];
 
-             foreach ($result['Endpoints'] as $datum) {
 
-                 $endpointData[$datum['Address']] = time()
 
-                     + ($datum['CachePeriodInMinutes'] * 60);
 
-             }
 
-             $endpointList = new EndpointList($endpointData);
 
-             self::$cache->set($cacheKey, $endpointList);
 
-             return $endpointList->getEndpoint();
 
-         }
 
-         throw new UnresolvedEndpointException('The endpoint discovery operation '
 
-             . 'yielded a response that did not contain properly formatted '
 
-             . 'endpoint data.');
 
-     }
 
-     private function getCacheKey(
 
-         CredentialsInterface $creds,
 
-         CommandInterface $cmd,
 
-         array $identifiers
 
-     ) {
 
-         $key = $this->service->getServiceName() . '_' . $creds->getAccessKeyId();
 
-         if (!empty($identifiers)) {
 
-             $key .= '_' . $cmd->getName();
 
-             foreach ($identifiers as $identifier) {
 
-                 $key .= "_{$cmd[$identifier]}";
 
-             }
 
-         }
 
-         return $key;
 
-     }
 
-     private function getDiscoveryCommand(
 
-         CommandInterface $cmd,
 
-         array $identifiers
 
-     ) {
 
-         foreach ($this->service->getOperations() as $op) {
 
-             if (isset($op['endpointoperation'])) {
 
-                 $endpointOperation = $op->toArray()['name'];
 
-                 break;
 
-             }
 
-         }
 
-         if (!isset($endpointOperation)) {
 
-             throw new UnresolvedEndpointException('This command is set to use '
 
-                 . 'endpoint discovery, but no endpoint discovery operation was '
 
-                 . 'found. Please verify the accuracy of your model files.');
 
-         }
 
-         $params = [];
 
-         if (!empty($identifiers)) {
 
-             $params['Operation'] = $cmd->getName();
 
-             $params['Identifiers'] = [];
 
-             foreach ($identifiers as $identifier) {
 
-                 $params['Identifiers'][$identifier] = $cmd[$identifier];
 
-             }
 
-         }
 
-         $command = $this->client->getCommand($endpointOperation, $params);
 
-         $command->getHandlerList()->appendBuild(
 
-             Middleware::mapRequest(function (RequestInterface $r) {
 
-                 return $r->withHeader(
 
-                     'x-amz-api-version',
 
-                     $this->service->getApiVersion()
 
-                 );
 
-             }),
 
-             'x-amz-api-version-header'
 
-         );
 
-         return $command;
 
-     }
 
-     private function getIdentifiers(array $operation)
 
-     {
 
-         $inputShape = $this->service->getShapeMap()
 
-             ->resolve($operation['input'])
 
-             ->toArray();
 
-         $identifiers = [];
 
-         foreach ($inputShape['members'] as $key => $member) {
 
-             if (!empty($member['endpointdiscoveryid'])) {
 
-                 $identifiers[] = $key;
 
-             }
 
-         }
 
-         return $identifiers;
 
-     }
 
-     private function handleDiscoveryException(
 
-         $isRequired,
 
-         $originalUri,
 
-         \Exception $e,
 
-         CommandInterface $cmd,
 
-         RequestInterface $request
 
-     ) {
 
-         // If no cached endpoints and discovery required,
 
-         // throw exception
 
-         if ($isRequired) {
 
-             $message = 'The endpoint required for this service is currently '
 
-                 . 'unable to be retrieved, and your request can not be fulfilled '
 
-                 . 'unless you manually specify an endpoint.';
 
-             throw new AwsException(
 
-                 $message,
 
-                 $cmd,
 
-                 [
 
-                     'code' => 'EndpointDiscoveryException',
 
-                     'message' => $message
 
-                 ],
 
-                 $e
 
-             );
 
-         }
 
-         // If discovery isn't required, use original endpoint
 
-         return $this->useOriginalUri(
 
-             $originalUri,
 
-             $cmd,
 
-             $request
 
-         );
 
-     }
 
-     private function handleInvalidEndpoint(
 
-         $cacheKey,
 
-         $cmd,
 
-         $identifiers,
 
-         $isRequired,
 
-         $originalUri,
 
-         $request,
 
-         $value,
 
-         &$endpoint,
 
-         &$g
 
-     ) {
 
-         $nextHandler = $this->nextHandler;
 
-         $endpointList = self::$cache->get($cacheKey);
 
-         if ($endpointList instanceof EndpointList) {
 
-             // Remove invalid endpoint from cached list
 
-             $endpointList->remove($endpoint);
 
-             // If possible, get another cached endpoint
 
-             $newEndpoint = $endpointList->getEndpoint();
 
-         }
 
-         if (empty($newEndpoint)) {
 
-             // If no more cached endpoints, make discovery call
 
-             // if none made within cooldown for given key
 
-             if (time() - $this->discoveryTimes[$cacheKey]
 
-                 < self::$discoveryCooldown
 
-             ) {
 
-                 // If no more cached endpoints and it's required,
 
-                 // fail with original exception
 
-                 if ($isRequired) {
 
-                     return $value;
 
-                 }
 
-                 // Use original endpoint if not required
 
-                 return $this->useOriginalUri(
 
-                     $originalUri,
 
-                     $cmd,
 
-                     $request
 
-                 );
 
-             }
 
-             $newEndpoint = $this->discoverEndpoint(
 
-                 $cacheKey,
 
-                 $cmd,
 
-                 $identifiers
 
-             );
 
-         }
 
-         $endpoint = $newEndpoint;
 
-         $request = $this->modifyRequest($request, $endpoint);
 
-         return $nextHandler($cmd, $request)->otherwise($g);
 
-     }
 
-     private function modifyRequest(RequestInterface $request, $endpoint)
 
-     {
 
-         $parsed = $this->parseEndpoint($endpoint);
 
-         if (!empty($request->getHeader('User-Agent'))) {
 
-             $userAgent = $request->getHeader('User-Agent')[0];
 
-             if (strpos($userAgent, 'endpoint-discovery') === false) {
 
-                 $userAgent = $userAgent . ' endpoint-discovery';
 
-             }
 
-         } else {
 
-             $userAgent = 'endpoint-discovery';
 
-         }
 
-         return $request
 
-             ->withUri(
 
-                 $request->getUri()
 
-                     ->withHost($parsed['host'])
 
-                     ->withPath($parsed['path'])
 
-             )
 
-             ->withHeader('User-Agent', $userAgent);
 
-     }
 
-     /**
 
-      * Parses an endpoint returned from the discovery API into an array with
 
-      * 'host' and 'path' keys.
 
-      *
 
-      * @param $endpoint
 
-      * @return array
 
-      */
 
-     private function parseEndpoint($endpoint)
 
-     {
 
-         $parsed = parse_url($endpoint);
 
-         // parse_url() will correctly parse full URIs with schemes
 
-         if (isset($parsed['host'])) {
 
-             return $parsed;
 
-         }
 
-         // parse_url() will put host & path in 'path' if scheme is not provided
 
-         if (isset($parsed['path'])) {
 
-             $split = explode('/', $parsed['path'], 2);
 
-             $parsed['host'] = $split[0];
 
-             if (isset($split[1])) {
 
-                 if (substr($split[1], 0 , 1) !== '/') {
 
-                     $split[1] = '/' . $split[1];
 
-                 }
 
-                 $parsed['path'] = $split[1];
 
-             } else {
 
-                 $parsed['path'] = '';
 
-             }
 
-             return $parsed;
 
-         }
 
-         throw new UnresolvedEndpointException("The supplied endpoint '"
 
-             . "{$endpoint}' is invalid.");
 
-     }
 
-     private function useOriginalUri(
 
-         UriInterface $uri,
 
-         CommandInterface $cmd,
 
-         RequestInterface $request
 
-     ) {
 
-         $nextHandler = $this->nextHandler;
 
-         $endpoint = $uri->getHost() . $uri->getPath();
 
-         $request = $this->modifyRequest(
 
-             $request,
 
-             $endpoint
 
-         );
 
-         return $nextHandler($cmd, $request);
 
-     }
 
- }
 
 
  |