Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
}