PluginProbe ʕ •ᴥ•ʔ
Transferito: WP Migration / trunk
Transferito: WP Migration vtrunk
trunk 11.4.0 12.0.0 13.1.0 14.0.0 14.0.11 14.0.7 14.1.0 14.1.1 14.1.2 14.1.3 14.1.4
transferito / vendor / aws / aws-sdk-php / src / EndpointDiscovery / EndpointDiscoveryMiddleware.php
transferito / vendor / aws / aws-sdk-php / src / EndpointDiscovery Last commit date
Exception 11 months ago Configuration.php 11 months ago ConfigurationInterface.php 11 months ago ConfigurationProvider.php 11 months ago EndpointDiscoveryMiddleware.php 11 months ago EndpointList.php 11 months ago
EndpointDiscoveryMiddleware.php
424 lines
1 <?php
2 namespace Aws\EndpointDiscovery;
3
4 use Aws\AwsClient;
5 use Aws\CacheInterface;
6 use Aws\CommandInterface;
7 use Aws\Credentials\CredentialsInterface;
8 use Aws\Exception\AwsException;
9 use Aws\Exception\UnresolvedEndpointException;
10 use Aws\LruArrayCache;
11 use Aws\Middleware;
12 use Psr\Http\Message\RequestInterface;
13 use Psr\Http\Message\UriInterface;
14
15 class EndpointDiscoveryMiddleware
16 {
17 /**
18 * @var CacheInterface
19 */
20 private static $cache;
21 private static $discoveryCooldown = 60;
22
23 private $args;
24 private $client;
25 private $config;
26 private $discoveryTimes = [];
27 private $nextHandler;
28 private $service;
29
30 public static function wrap(
31 $client,
32 $args,
33 $config
34 ) {
35 return function (callable $handler) use (
36 $client,
37 $args,
38 $config
39 ) {
40 return new static(
41 $handler,
42 $client,
43 $args,
44 $config
45 );
46 };
47 }
48
49 public function __construct(
50 callable $handler,
51 AwsClient $client,
52 array $args,
53 $config
54 ) {
55 $this->nextHandler = $handler;
56 $this->client = $client;
57 $this->args = $args;
58 $this->service = $client->getApi();
59 $this->config = $config;
60 }
61
62 public function __invoke(CommandInterface $cmd, RequestInterface $request)
63 {
64 $nextHandler = $this->nextHandler;
65 $op = $this->service->getOperation($cmd->getName())->toArray();
66
67 // Continue only if endpointdiscovery trait is set
68 if (isset($op['endpointdiscovery'])) {
69 $config = ConfigurationProvider::unwrap($this->config);
70 $isRequired = !empty($op['endpointdiscovery']['required']);
71
72 if ($isRequired && !($config->isEnabled())) {
73 throw new UnresolvedEndpointException('This operation '
74 . 'requires the use of endpoint discovery, but this has '
75 . 'been disabled in the configuration. Enable endpoint '
76 . 'discovery or use a different operation.');
77 }
78
79 // Continue only if enabled by config
80 if ($config->isEnabled()) {
81 if (isset($op['endpointoperation'])) {
82 throw new UnresolvedEndpointException('This operation is '
83 . 'contradictorily marked both as using endpoint discovery '
84 . 'and being the endpoint discovery operation. Please '
85 . 'verify the accuracy of your model files.');
86 }
87
88 // Original endpoint may be used if discovery optional
89 $originalUri = $request->getUri();
90
91 $identifiers = $this->getIdentifiers($op);
92
93 $cacheKey = $this->getCacheKey(
94 $this->client->getCredentials()->wait(),
95 $cmd,
96 $identifiers
97 );
98
99 // Check/create cache
100 if (!isset(self::$cache)) {
101 self::$cache = new LruArrayCache($config->getCacheLimit());
102 }
103
104 if (empty($endpointList = self::$cache->get($cacheKey))) {
105 $endpointList = new EndpointList([]);
106 }
107 $endpoint = $endpointList->getActive();
108
109 // Retrieve endpoints if there is no active endpoint
110 if (empty($endpoint)) {
111 try {
112 $endpoint = $this->discoverEndpoint(
113 $cacheKey,
114 $cmd,
115 $identifiers
116 );
117 } catch (\Exception $e) {
118 // Use cached endpoint, expired or active, if any remain
119 $endpoint = $endpointList->getEndpoint();
120
121 if (empty($endpoint)) {
122 return $this->handleDiscoveryException(
123 $isRequired,
124 $originalUri,
125 $e,
126 $cmd,
127 $request
128 );
129 }
130 }
131 }
132
133 $request = $this->modifyRequest($request, $endpoint);
134
135 $g = function ($value) use (
136 $cacheKey,
137 $cmd,
138 $identifiers,
139 $isRequired,
140 $originalUri,
141 $request,
142 &$endpoint,
143 &$g
144 ) {
145 if ($value instanceof AwsException
146 && (
147 $value->getAwsErrorCode() == 'InvalidEndpointException'
148 || $value->getStatusCode() == 421
149 )
150 ) {
151 return $this->handleInvalidEndpoint(
152 $cacheKey,
153 $cmd,
154 $identifiers,
155 $isRequired,
156 $originalUri,
157 $request,
158 $value,
159 $endpoint,
160 $g
161 );
162 }
163
164 return $value;
165 };
166
167 return $nextHandler($cmd, $request)->otherwise($g);
168 }
169 }
170
171 return $nextHandler($cmd, $request);
172 }
173
174 private function discoverEndpoint(
175 $cacheKey,
176 CommandInterface $cmd,
177 array $identifiers
178 ) {
179 $discCmd = $this->getDiscoveryCommand($cmd, $identifiers);
180 $this->discoveryTimes[$cacheKey] = time();
181 $result = $this->client->execute($discCmd);
182
183 if (isset($result['Endpoints'])) {
184 $endpointData = [];
185 foreach ($result['Endpoints'] as $datum) {
186 $endpointData[$datum['Address']] = time()
187 + ($datum['CachePeriodInMinutes'] * 60);
188 }
189 $endpointList = new EndpointList($endpointData);
190 self::$cache->set($cacheKey, $endpointList);
191 return $endpointList->getEndpoint();
192 }
193
194 throw new UnresolvedEndpointException('The endpoint discovery operation '
195 . 'yielded a response that did not contain properly formatted '
196 . 'endpoint data.');
197 }
198
199 private function getCacheKey(
200 CredentialsInterface $creds,
201 CommandInterface $cmd,
202 array $identifiers
203 ) {
204 $key = $this->service->getServiceName() . '_' . $creds->getAccessKeyId();
205 if (!empty($identifiers)) {
206 $key .= '_' . $cmd->getName();
207 foreach ($identifiers as $identifier) {
208 $key .= "_{$cmd[$identifier]}";
209 }
210 }
211
212 return $key;
213 }
214
215 private function getDiscoveryCommand(
216 CommandInterface $cmd,
217 array $identifiers
218 ) {
219 foreach ($this->service->getOperations() as $op) {
220 if (isset($op['endpointoperation'])) {
221 $endpointOperation = $op->toArray()['name'];
222 break;
223 }
224 }
225
226 if (!isset($endpointOperation)) {
227 throw new UnresolvedEndpointException('This command is set to use '
228 . 'endpoint discovery, but no endpoint discovery operation was '
229 . 'found. Please verify the accuracy of your model files.');
230 }
231
232 $params = [];
233 if (!empty($identifiers)) {
234 $params['Operation'] = $cmd->getName();
235 $params['Identifiers'] = [];
236 foreach ($identifiers as $identifier) {
237 $params['Identifiers'][$identifier] = $cmd[$identifier];
238 }
239 }
240 $command = $this->client->getCommand($endpointOperation, $params);
241 $command->getHandlerList()->appendBuild(
242 Middleware::mapRequest(function (RequestInterface $r) {
243 return $r->withHeader(
244 'x-amz-api-version',
245 $this->service->getApiVersion()
246 );
247 }),
248 'x-amz-api-version-header'
249 );
250
251 return $command;
252 }
253
254 private function getIdentifiers(array $operation)
255 {
256 $inputShape = $this->service->getShapeMap()
257 ->resolve($operation['input'])
258 ->toArray();
259 $identifiers = [];
260 foreach ($inputShape['members'] as $key => $member) {
261 if (!empty($member['endpointdiscoveryid'])) {
262 $identifiers[] = $key;
263 }
264 }
265 return $identifiers;
266 }
267
268 private function handleDiscoveryException(
269 $isRequired,
270 $originalUri,
271 \Exception $e,
272 CommandInterface $cmd,
273 RequestInterface $request
274 ) {
275 // If no cached endpoints and discovery required,
276 // throw exception
277 if ($isRequired) {
278 $message = 'The endpoint required for this service is currently '
279 . 'unable to be retrieved, and your request can not be fulfilled '
280 . 'unless you manually specify an endpoint.';
281 throw new AwsException(
282 $message,
283 $cmd,
284 [
285 'code' => 'EndpointDiscoveryException',
286 'message' => $message
287 ],
288 $e
289 );
290 }
291
292 // If discovery isn't required, use original endpoint
293 return $this->useOriginalUri(
294 $originalUri,
295 $cmd,
296 $request
297 );
298 }
299
300 private function handleInvalidEndpoint(
301 $cacheKey,
302 $cmd,
303 $identifiers,
304 $isRequired,
305 $originalUri,
306 $request,
307 $value,
308 &$endpoint,
309 &$g
310 ) {
311 $nextHandler = $this->nextHandler;
312 $endpointList = self::$cache->get($cacheKey);
313 if ($endpointList instanceof EndpointList) {
314
315 // Remove invalid endpoint from cached list
316 $endpointList->remove($endpoint);
317
318 // If possible, get another cached endpoint
319 $newEndpoint = $endpointList->getEndpoint();
320 }
321 if (empty($newEndpoint)) {
322
323 // If no more cached endpoints, make discovery call
324 // if none made within cooldown for given key
325 if (time() - $this->discoveryTimes[$cacheKey]
326 < self::$discoveryCooldown
327 ) {
328
329 // If no more cached endpoints and it's required,
330 // fail with original exception
331 if ($isRequired) {
332 return $value;
333 }
334
335 // Use original endpoint if not required
336 return $this->useOriginalUri(
337 $originalUri,
338 $cmd,
339 $request
340 );
341 }
342
343 $newEndpoint = $this->discoverEndpoint(
344 $cacheKey,
345 $cmd,
346 $identifiers
347 );
348 }
349 $endpoint = $newEndpoint;
350 $request = $this->modifyRequest($request, $endpoint);
351 return $nextHandler($cmd, $request)->otherwise($g);
352 }
353
354 private function modifyRequest(RequestInterface $request, $endpoint)
355 {
356 $parsed = $this->parseEndpoint($endpoint);
357 if (!empty($request->getHeader('User-Agent'))) {
358 $userAgent = $request->getHeader('User-Agent')[0];
359 if (strpos($userAgent, 'endpoint-discovery') === false) {
360 $userAgent = $userAgent . ' endpoint-discovery';
361 }
362 } else {
363 $userAgent = 'endpoint-discovery';
364 }
365
366 return $request
367 ->withUri(
368 $request->getUri()
369 ->withHost($parsed['host'])
370 ->withPath($parsed['path'])
371 )
372 ->withHeader('User-Agent', $userAgent);
373 }
374
375 /**
376 * Parses an endpoint returned from the discovery API into an array with
377 * 'host' and 'path' keys.
378 *
379 * @param $endpoint
380 * @return array
381 */
382 private function parseEndpoint($endpoint)
383 {
384 $parsed = parse_url($endpoint);
385
386 // parse_url() will correctly parse full URIs with schemes
387 if (isset($parsed['host'])) {
388 return $parsed;
389 }
390
391 // parse_url() will put host & path in 'path' if scheme is not provided
392 if (isset($parsed['path'])) {
393 $split = explode('/', $parsed['path'], 2);
394 $parsed['host'] = $split[0];
395 if (isset($split[1])) {
396 if (substr($split[1], 0 , 1) !== '/') {
397 $split[1] = '/' . $split[1];
398 }
399 $parsed['path'] = $split[1];
400 } else {
401 $parsed['path'] = '';
402 }
403 return $parsed;
404 }
405
406 throw new UnresolvedEndpointException("The supplied endpoint '"
407 . "{$endpoint}' is invalid.");
408 }
409
410 private function useOriginalUri(
411 UriInterface $uri,
412 CommandInterface $cmd,
413 RequestInterface $request
414 ) {
415 $nextHandler = $this->nextHandler;
416 $endpoint = $uri->getHost() . $uri->getPath();
417 $request = $this->modifyRequest(
418 $request,
419 $endpoint
420 );
421 return $nextHandler($cmd, $request);
422 }
423 }
424