PluginProbe
Media Cloud Sync / 1.2.2
Media Cloud Sync v1.2.2
1.4.1 1.4.0 1.3.12 1.3.11 1.3.10 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.2.0 1.2.10 1.2.11 1.2.12 1.2.13 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 All 35 releases
media-cloud-sync / includes / sdk / s3 / Aws / EndpointDiscovery / EndpointDiscoveryMiddleware.php

EndpointDiscoveryMiddleware.php in Media Cloud Sync 1.2.2, at includes/sdk/s3/Aws/EndpointDiscovery/EndpointDiscoveryMiddleware.php

247 lines 11.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Dudlewebs\WPMCS\s3\Aws\EndpointDiscovery;
4
5 use Dudlewebs\WPMCS\s3\Aws\AwsClient;
6 use Dudlewebs\WPMCS\s3\Aws\CacheInterface;
7 use Dudlewebs\WPMCS\s3\Aws\CommandInterface;
8 use Dudlewebs\WPMCS\s3\Aws\Credentials\CredentialsInterface;
9 use Dudlewebs\WPMCS\s3\Aws\Exception\AwsException;
10 use Dudlewebs\WPMCS\s3\Aws\Exception\UnresolvedEndpointException;
11 use Dudlewebs\WPMCS\s3\Aws\LruArrayCache;
12 use Dudlewebs\WPMCS\s3\Aws\Middleware;
13 use Dudlewebs\WPMCS\s3\Psr\Http\Message\RequestInterface;
14 use Dudlewebs\WPMCS\s3\Psr\Http\Message\UriInterface;
15 class EndpointDiscoveryMiddleware
16 {
17 /**
18 * @var CacheInterface
19 */
20 private static $cache;
21 private static $discoveryCooldown = 60;
22 private $args;
23 private $client;
24 private $config;
25 private $discoveryTimes = [];
26 private $nextHandler;
27 private $service;
28 public static function wrap($client, $args, $config)
29 {
30 return function (callable $handler) use($client, $args, $config) {
31 return new static($handler, $client, $args, $config);
32 };
33 }
34 public function __construct(callable $handler, AwsClient $client, array $args, $config)
35 {
36 $this->nextHandler = $handler;
37 $this->client = $client;
38 $this->args = $args;
39 $this->service = $client->getApi();
40 $this->config = $config;
41 }
42 public function __invoke(CommandInterface $cmd, RequestInterface $request)
43 {
44 $nextHandler = $this->nextHandler;
45 $op = $this->service->getOperation($cmd->getName())->toArray();
46 // Continue only if endpointdiscovery trait is set
47 if (isset($op['endpointdiscovery'])) {
48 $config = ConfigurationProvider::unwrap($this->config);
49 $isRequired = !empty($op['endpointdiscovery']['required']);
50 if ($isRequired && !$config->isEnabled()) {
51 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.');
52 }
53 // Continue only if enabled by config
54 if ($config->isEnabled()) {
55 if (isset($op['endpointoperation'])) {
56 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.');
57 }
58 // Original endpoint may be used if discovery optional
59 $originalUri = $request->getUri();
60 $identifiers = $this->getIdentifiers($op);
61 $cacheKey = $this->getCacheKey($this->client->getCredentials()->wait(), $cmd, $identifiers);
62 // Check/create cache
63 if (!isset(self::$cache)) {
64 self::$cache = new LruArrayCache($config->getCacheLimit());
65 }
66 if (empty($endpointList = self::$cache->get($cacheKey))) {
67 $endpointList = new EndpointList([]);
68 }
69 $endpoint = $endpointList->getActive();
70 // Retrieve endpoints if there is no active endpoint
71 if (empty($endpoint)) {
72 try {
73 $endpoint = $this->discoverEndpoint($cacheKey, $cmd, $identifiers);
74 } catch (\Exception $e) {
75 // Use cached endpoint, expired or active, if any remain
76 $endpoint = $endpointList->getEndpoint();
77 if (empty($endpoint)) {
78 return $this->handleDiscoveryException($isRequired, $originalUri, $e, $cmd, $request);
79 }
80 }
81 }
82 $request = $this->modifyRequest($request, $endpoint);
83 $g = function ($value) use($cacheKey, $cmd, $identifiers, $isRequired, $originalUri, $request, &$endpoint, &$g) {
84 if ($value instanceof AwsException && ($value->getAwsErrorCode() == 'InvalidEndpointException' || $value->getStatusCode() == 421)) {
85 return $this->handleInvalidEndpoint($cacheKey, $cmd, $identifiers, $isRequired, $originalUri, $request, $value, $endpoint, $g);
86 }
87 return $value;
88 };
89 return $nextHandler($cmd, $request)->otherwise($g);
90 }
91 }
92 return $nextHandler($cmd, $request);
93 }
94 private function discoverEndpoint($cacheKey, CommandInterface $cmd, array $identifiers)
95 {
96 $discCmd = $this->getDiscoveryCommand($cmd, $identifiers);
97 $this->discoveryTimes[$cacheKey] = \time();
98 $result = $this->client->execute($discCmd);
99 if (isset($result['Endpoints'])) {
100 $endpointData = [];
101 foreach ($result['Endpoints'] as $datum) {
102 $endpointData[$datum['Address']] = \time() + $datum['CachePeriodInMinutes'] * 60;
103 }
104 $endpointList = new EndpointList($endpointData);
105 self::$cache->set($cacheKey, $endpointList);
106 return $endpointList->getEndpoint();
107 }
108 throw new UnresolvedEndpointException('The endpoint discovery operation ' . 'yielded a response that did not contain properly formatted ' . 'endpoint data.');
109 }
110 private function getCacheKey(CredentialsInterface $creds, CommandInterface $cmd, array $identifiers)
111 {
112 $key = $this->service->getServiceName() . '_' . $creds->getAccessKeyId();
113 if (!empty($identifiers)) {
114 $key .= '_' . $cmd->getName();
115 foreach ($identifiers as $identifier) {
116 $key .= "_{$cmd[$identifier]}";
117 }
118 }
119 return $key;
120 }
121 private function getDiscoveryCommand(CommandInterface $cmd, array $identifiers)
122 {
123 foreach ($this->service->getOperations() as $op) {
124 if (isset($op['endpointoperation'])) {
125 $endpointOperation = $op->toArray()['name'];
126 break;
127 }
128 }
129 if (!isset($endpointOperation)) {
130 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.');
131 }
132 $params = [];
133 if (!empty($identifiers)) {
134 $params['Operation'] = $cmd->getName();
135 $params['Identifiers'] = [];
136 foreach ($identifiers as $identifier) {
137 $params['Identifiers'][$identifier] = $cmd[$identifier];
138 }
139 }
140 $command = $this->client->getCommand($endpointOperation, $params);
141 $command->getHandlerList()->appendBuild(Middleware::mapRequest(function (RequestInterface $r) {
142 return $r->withHeader('x-amz-api-version', $this->service->getApiVersion());
143 }), 'x-amz-api-version-header');
144 return $command;
145 }
146 private function getIdentifiers(array $operation)
147 {
148 $inputShape = $this->service->getShapeMap()->resolve($operation['input'])->toArray();
149 $identifiers = [];
150 foreach ($inputShape['members'] as $key => $member) {
151 if (!empty($member['endpointdiscoveryid'])) {
152 $identifiers[] = $key;
153 }
154 }
155 return $identifiers;
156 }
157 private function handleDiscoveryException($isRequired, $originalUri, \Exception $e, CommandInterface $cmd, RequestInterface $request)
158 {
159 // If no cached endpoints and discovery required,
160 // throw exception
161 if ($isRequired) {
162 $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.';
163 throw new AwsException($message, $cmd, ['code' => 'EndpointDiscoveryException', 'message' => $message], $e);
164 }
165 // If discovery isn't required, use original endpoint
166 return $this->useOriginalUri($originalUri, $cmd, $request);
167 }
168 private function handleInvalidEndpoint($cacheKey, $cmd, $identifiers, $isRequired, $originalUri, $request, $value, &$endpoint, &$g)
169 {
170 $nextHandler = $this->nextHandler;
171 $endpointList = self::$cache->get($cacheKey);
172 if ($endpointList instanceof EndpointList) {
173 // Remove invalid endpoint from cached list
174 $endpointList->remove($endpoint);
175 // If possible, get another cached endpoint
176 $newEndpoint = $endpointList->getEndpoint();
177 }
178 if (empty($newEndpoint)) {
179 // If no more cached endpoints, make discovery call
180 // if none made within cooldown for given key
181 if (\time() - $this->discoveryTimes[$cacheKey] < self::$discoveryCooldown) {
182 // If no more cached endpoints and it's required,
183 // fail with original exception
184 if ($isRequired) {
185 return $value;
186 }
187 // Use original endpoint if not required
188 return $this->useOriginalUri($originalUri, $cmd, $request);
189 }
190 $newEndpoint = $this->discoverEndpoint($cacheKey, $cmd, $identifiers);
191 }
192 $endpoint = $newEndpoint;
193 $request = $this->modifyRequest($request, $endpoint);
194 return $nextHandler($cmd, $request)->otherwise($g);
195 }
196 private function modifyRequest(RequestInterface $request, $endpoint)
197 {
198 $parsed = $this->parseEndpoint($endpoint);
199 if (!empty($request->getHeader('User-Agent'))) {
200 $userAgent = $request->getHeader('User-Agent')[0];
201 if (\strpos($userAgent, 'endpoint-discovery') === \false) {
202 $userAgent = $userAgent . ' endpoint-discovery';
203 }
204 } else {
205 $userAgent = 'endpoint-discovery';
206 }
207 return $request->withUri($request->getUri()->withHost($parsed['host'])->withPath($parsed['path']))->withHeader('User-Agent', $userAgent);
208 }
209 /**
210 * Parses an endpoint returned from the discovery API into an array with
211 * 'host' and 'path' keys.
212 *
213 * @param $endpoint
214 * @return array
215 */
216 private function parseEndpoint($endpoint)
217 {
218 $parsed = \parse_url($endpoint);
219 // parse_url() will correctly parse full URIs with schemes
220 if (isset($parsed['host'])) {
221 return $parsed;
222 }
223 // parse_url() will put host & path in 'path' if scheme is not provided
224 if (isset($parsed['path'])) {
225 $split = \explode('/', $parsed['path'], 2);
226 $parsed['host'] = $split[0];
227 if (isset($split[1])) {
228 if (\substr($split[1], 0, 1) !== '/') {
229 $split[1] = '/' . $split[1];
230 }
231 $parsed['path'] = $split[1];
232 } else {
233 $parsed['path'] = '';
234 }
235 return $parsed;
236 }
237 throw new UnresolvedEndpointException("The supplied endpoint '" . "{$endpoint}' is invalid.");
238 }
239 private function useOriginalUri(UriInterface $uri, CommandInterface $cmd, RequestInterface $request)
240 {
241 $nextHandler = $this->nextHandler;
242 $endpoint = $uri->getHost() . $uri->getPath();
243 $request = $this->modifyRequest($request, $endpoint);
244 return $nextHandler($cmd, $request);
245 }
246 }
247