function (array &$args) { $availableRegions = array_keys($args['partition']['regions']); return end($availableRegions); }]; unset($args['region']); return $args + ['bucket_region_cache' => ['type' => 'config', 'valid' => [\UglyRobot\Infinite_Uploads\Aws\CacheInterface::class], 'doc' => 'Cache of regions in which given buckets are located.', 'default' => function () { return new \UglyRobot\Infinite_Uploads\Aws\LruArrayCache(); }], 'region' => $regionDef]; } public function __construct(array $args) { parent::__construct($args); $this->cache = $this->getConfig('bucket_region_cache'); $this->getHandlerList()->prependInit($this->determineRegionMiddleware(), 'determine_region'); } private function determineRegionMiddleware() { return function (callable $handler) { return function (\UglyRobot\Infinite_Uploads\Aws\CommandInterface $command) use($handler) { $cacheKey = $this->getCacheKey($command['Bucket']); if (empty($command['@region']) && ($region = $this->cache->get($cacheKey))) { $command['@region'] = $region; } return \UglyRobot\Infinite_Uploads\GuzzleHttp\Promise\coroutine(function () use($handler, $command, $cacheKey) { try { (yield $handler($command)); } catch (PermanentRedirectException $e) { if (empty($command['Bucket'])) { throw $e; } $result = $e->getResult(); $region = null; if (isset($result['@metadata']['headers']['x-amz-bucket-region'])) { $region = $result['@metadata']['headers']['x-amz-bucket-region']; $this->cache->set($cacheKey, $region); } else { $region = (yield $this->determineBucketRegionAsync($command['Bucket'])); } $command['@region'] = $region; (yield $handler($command)); } catch (AwsException $e) { if ($e->getAwsErrorCode() === 'AuthorizationHeaderMalformed') { $region = $this->determineBucketRegionFromExceptionBody($e->getResponse()); if (!empty($region)) { $this->cache->set($cacheKey, $region); $command['@region'] = $region; (yield $handler($command)); } else { throw $e; } } else { throw $e; } } }); }; }; } public function createPresignedRequest(\UglyRobot\Infinite_Uploads\Aws\CommandInterface $command, $expires, array $options = []) { if (empty($command['Bucket'])) { throw new \InvalidArgumentException('The S3\\MultiRegionClient' . ' cannot create presigned requests for commands without a' . ' specified bucket.'); } /** @var S3ClientInterface $client */ $client = $this->getClientFromPool($this->determineBucketRegion($command['Bucket'])); return $client->createPresignedRequest($client->getCommand($command->getName(), $command->toArray()), $expires); } public function getObjectUrl($bucket, $key) { /** @var S3Client $regionalClient */ $regionalClient = $this->getClientFromPool($this->determineBucketRegion($bucket)); return $regionalClient->getObjectUrl($bucket, $key); } public function determineBucketRegionAsync($bucketName) { $cacheKey = $this->getCacheKey($bucketName); if ($cached = $this->cache->get($cacheKey)) { return \UglyRobot\Infinite_Uploads\GuzzleHttp\Promise\promise_for($cached); } /** @var S3ClientInterface $regionalClient */ $regionalClient = $this->getClientFromPool(); return $regionalClient->determineBucketRegionAsync($bucketName)->then(function ($region) use($cacheKey) { $this->cache->set($cacheKey, $region); return $region; }); } private function getCacheKey($bucketName) { return "aws:s3:{$bucketName}:location"; } }