1 | var AWS = require('./core');
|
2 | var SequentialExecutor = require('./sequential_executor');
|
3 | var DISCOVER_ENDPOINT = require('./discover_endpoint').discoverEndpoint;
|
4 |
|
5 |
|
6 |
|
7 |
|
8 | AWS.EventListeners = {
|
9 | |
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 | Core: {}
|
63 | };
|
64 |
|
65 |
|
66 |
|
67 |
|
68 | function getOperationAuthtype(req) {
|
69 | if (!req.service.api.operations) {
|
70 | return '';
|
71 | }
|
72 | var operation = req.service.api.operations[req.operation];
|
73 | return operation ? operation.authtype : '';
|
74 | }
|
75 |
|
76 | AWS.EventListeners = {
|
77 | Core: new SequentialExecutor().addNamedListeners(function(add, addAsync) {
|
78 | addAsync('VALIDATE_CREDENTIALS', 'validate',
|
79 | function VALIDATE_CREDENTIALS(req, done) {
|
80 | if (!req.service.api.signatureVersion && !req.service.config.signatureVersion) return done();
|
81 | req.service.config.getCredentials(function(err) {
|
82 | if (err) {
|
83 | req.response.error = AWS.util.error(err,
|
84 | {code: 'CredentialsError', message: 'Missing credentials in config'});
|
85 | }
|
86 | done();
|
87 | });
|
88 | });
|
89 |
|
90 | add('VALIDATE_REGION', 'validate', function VALIDATE_REGION(req) {
|
91 | if (!req.service.config.region && !req.service.isGlobalEndpoint) {
|
92 | req.response.error = AWS.util.error(new Error(),
|
93 | {code: 'ConfigError', message: 'Missing region in config'});
|
94 | }
|
95 | });
|
96 |
|
97 | add('BUILD_IDEMPOTENCY_TOKENS', 'validate', function BUILD_IDEMPOTENCY_TOKENS(req) {
|
98 | if (!req.service.api.operations) {
|
99 | return;
|
100 | }
|
101 | var operation = req.service.api.operations[req.operation];
|
102 | if (!operation) {
|
103 | return;
|
104 | }
|
105 | var idempotentMembers = operation.idempotentMembers;
|
106 | if (!idempotentMembers.length) {
|
107 | return;
|
108 | }
|
109 |
|
110 | var params = AWS.util.copy(req.params);
|
111 | for (var i = 0, iLen = idempotentMembers.length; i < iLen; i++) {
|
112 | if (!params[idempotentMembers[i]]) {
|
113 |
|
114 | params[idempotentMembers[i]] = AWS.util.uuid.v4();
|
115 | }
|
116 | }
|
117 | req.params = params;
|
118 | });
|
119 |
|
120 | add('VALIDATE_PARAMETERS', 'validate', function VALIDATE_PARAMETERS(req) {
|
121 | if (!req.service.api.operations) {
|
122 | return;
|
123 | }
|
124 | var rules = req.service.api.operations[req.operation].input;
|
125 | var validation = req.service.config.paramValidation;
|
126 | new AWS.ParamValidator(validation).validate(rules, req.params);
|
127 | });
|
128 |
|
129 | addAsync('COMPUTE_SHA256', 'afterBuild', function COMPUTE_SHA256(req, done) {
|
130 | req.haltHandlersOnError();
|
131 | if (!req.service.api.operations) {
|
132 | return;
|
133 | }
|
134 | var operation = req.service.api.operations[req.operation];
|
135 | var authtype = operation ? operation.authtype : '';
|
136 | if (!req.service.api.signatureVersion && !authtype && !req.service.config.signatureVersion) return done();
|
137 | if (req.service.getSignerClass(req) === AWS.Signers.V4) {
|
138 | var body = req.httpRequest.body || '';
|
139 | if (authtype.indexOf('unsigned-body') >= 0) {
|
140 | req.httpRequest.headers['X-Amz-Content-Sha256'] = 'UNSIGNED-PAYLOAD';
|
141 | return done();
|
142 | }
|
143 | AWS.util.computeSha256(body, function(err, sha) {
|
144 | if (err) {
|
145 | done(err);
|
146 | }
|
147 | else {
|
148 | req.httpRequest.headers['X-Amz-Content-Sha256'] = sha;
|
149 | done();
|
150 | }
|
151 | });
|
152 | } else {
|
153 | done();
|
154 | }
|
155 | });
|
156 |
|
157 | add('SET_CONTENT_LENGTH', 'afterBuild', function SET_CONTENT_LENGTH(req) {
|
158 | var authtype = getOperationAuthtype(req);
|
159 | var payloadMember = AWS.util.getRequestPayloadShape(req);
|
160 | if (req.httpRequest.headers['Content-Length'] === undefined) {
|
161 | try {
|
162 | var length = AWS.util.string.byteLength(req.httpRequest.body);
|
163 | req.httpRequest.headers['Content-Length'] = length;
|
164 | } catch (err) {
|
165 | if (payloadMember && payloadMember.isStreaming) {
|
166 | if (payloadMember.requiresLength) {
|
167 |
|
168 | throw err;
|
169 | } else if (authtype.indexOf('unsigned-body') >= 0) {
|
170 |
|
171 | req.httpRequest.headers['Transfer-Encoding'] = 'chunked';
|
172 | return;
|
173 | } else {
|
174 | throw err;
|
175 | }
|
176 | }
|
177 | throw err;
|
178 | }
|
179 | }
|
180 | });
|
181 |
|
182 | add('SET_HTTP_HOST', 'afterBuild', function SET_HTTP_HOST(req) {
|
183 | req.httpRequest.headers['Host'] = req.httpRequest.endpoint.host;
|
184 | });
|
185 |
|
186 | add('RESTART', 'restart', function RESTART() {
|
187 | var err = this.response.error;
|
188 | if (!err || !err.retryable) return;
|
189 |
|
190 | this.httpRequest = new AWS.HttpRequest(
|
191 | this.service.endpoint,
|
192 | this.service.region
|
193 | );
|
194 |
|
195 | if (this.response.retryCount < this.service.config.maxRetries) {
|
196 | this.response.retryCount++;
|
197 | } else {
|
198 | this.response.error = null;
|
199 | }
|
200 | });
|
201 |
|
202 | var addToHead = true;
|
203 | addAsync('DISCOVER_ENDPOINT', 'sign', DISCOVER_ENDPOINT, addToHead);
|
204 |
|
205 | addAsync('SIGN', 'sign', function SIGN(req, done) {
|
206 | var service = req.service;
|
207 | var operations = req.service.api.operations || {};
|
208 | var operation = operations[req.operation];
|
209 | var authtype = operation ? operation.authtype : '';
|
210 | if (!service.api.signatureVersion && !authtype && !service.config.signatureVersion) return done();
|
211 |
|
212 | service.config.getCredentials(function (err, credentials) {
|
213 | if (err) {
|
214 | req.response.error = err;
|
215 | return done();
|
216 | }
|
217 |
|
218 | try {
|
219 | var date = service.getSkewCorrectedDate();
|
220 | var SignerClass = service.getSignerClass(req);
|
221 | var signer = new SignerClass(req.httpRequest,
|
222 | service.api.signingName || service.api.endpointPrefix,
|
223 | {
|
224 | signatureCache: service.config.signatureCache,
|
225 | operation: operation,
|
226 | signatureVersion: service.api.signatureVersion
|
227 | });
|
228 | signer.setServiceClientId(service._clientId);
|
229 |
|
230 |
|
231 | delete req.httpRequest.headers['Authorization'];
|
232 | delete req.httpRequest.headers['Date'];
|
233 | delete req.httpRequest.headers['X-Amz-Date'];
|
234 |
|
235 |
|
236 | signer.addAuthorization(credentials, date);
|
237 | req.signedAt = date;
|
238 | } catch (e) {
|
239 | req.response.error = e;
|
240 | }
|
241 | done();
|
242 | });
|
243 | });
|
244 |
|
245 | add('VALIDATE_RESPONSE', 'validateResponse', function VALIDATE_RESPONSE(resp) {
|
246 | if (this.service.successfulResponse(resp, this)) {
|
247 | resp.data = {};
|
248 | resp.error = null;
|
249 | } else {
|
250 | resp.data = null;
|
251 | resp.error = AWS.util.error(new Error(),
|
252 | {code: 'UnknownError', message: 'An unknown error occurred.'});
|
253 | }
|
254 | });
|
255 |
|
256 | addAsync('SEND', 'send', function SEND(resp, done) {
|
257 | resp.httpResponse._abortCallback = done;
|
258 | resp.error = null;
|
259 | resp.data = null;
|
260 |
|
261 | function callback(httpResp) {
|
262 | resp.httpResponse.stream = httpResp;
|
263 | var stream = resp.request.httpRequest.stream;
|
264 | var service = resp.request.service;
|
265 | var api = service.api;
|
266 | var operationName = resp.request.operation;
|
267 | var operation = api.operations[operationName] || {};
|
268 |
|
269 | httpResp.on('headers', function onHeaders(statusCode, headers, statusMessage) {
|
270 | resp.request.emit(
|
271 | 'httpHeaders',
|
272 | [statusCode, headers, resp, statusMessage]
|
273 | );
|
274 |
|
275 | if (!resp.httpResponse.streaming) {
|
276 | if (AWS.HttpClient.streamsApiVersion === 2) {
|
277 |
|
278 |
|
279 | if (operation.hasEventOutput && service.successfulResponse(resp)) {
|
280 |
|
281 | resp.request.emit('httpDone');
|
282 | done();
|
283 | return;
|
284 | }
|
285 |
|
286 | httpResp.on('readable', function onReadable() {
|
287 | var data = httpResp.read();
|
288 | if (data !== null) {
|
289 | resp.request.emit('httpData', [data, resp]);
|
290 | }
|
291 | });
|
292 | } else {
|
293 | httpResp.on('data', function onData(data) {
|
294 | resp.request.emit('httpData', [data, resp]);
|
295 | });
|
296 | }
|
297 | }
|
298 | });
|
299 |
|
300 | httpResp.on('end', function onEnd() {
|
301 | if (!stream || !stream.didCallback) {
|
302 | if (AWS.HttpClient.streamsApiVersion === 2 && (operation.hasEventOutput && service.successfulResponse(resp))) {
|
303 |
|
304 | return;
|
305 | }
|
306 | resp.request.emit('httpDone');
|
307 | done();
|
308 | }
|
309 | });
|
310 | }
|
311 |
|
312 | function progress(httpResp) {
|
313 | httpResp.on('sendProgress', function onSendProgress(value) {
|
314 | resp.request.emit('httpUploadProgress', [value, resp]);
|
315 | });
|
316 |
|
317 | httpResp.on('receiveProgress', function onReceiveProgress(value) {
|
318 | resp.request.emit('httpDownloadProgress', [value, resp]);
|
319 | });
|
320 | }
|
321 |
|
322 | function error(err) {
|
323 | if (err.code !== 'RequestAbortedError') {
|
324 | var errCode = err.code === 'TimeoutError' ? err.code : 'NetworkingError';
|
325 | err = AWS.util.error(err, {
|
326 | code: errCode,
|
327 | region: resp.request.httpRequest.region,
|
328 | hostname: resp.request.httpRequest.endpoint.hostname,
|
329 | retryable: true
|
330 | });
|
331 | }
|
332 | resp.error = err;
|
333 | resp.request.emit('httpError', [resp.error, resp], function() {
|
334 | done();
|
335 | });
|
336 | }
|
337 |
|
338 | function executeSend() {
|
339 | var http = AWS.HttpClient.getInstance();
|
340 | var httpOptions = resp.request.service.config.httpOptions || {};
|
341 | try {
|
342 | var stream = http.handleRequest(resp.request.httpRequest, httpOptions,
|
343 | callback, error);
|
344 | progress(stream);
|
345 | } catch (err) {
|
346 | error(err);
|
347 | }
|
348 | }
|
349 | var timeDiff = (resp.request.service.getSkewCorrectedDate() - this.signedAt) / 1000;
|
350 | if (timeDiff >= 60 * 10) {
|
351 | this.emit('sign', [this], function(err) {
|
352 | if (err) done(err);
|
353 | else executeSend();
|
354 | });
|
355 | } else {
|
356 | executeSend();
|
357 | }
|
358 | });
|
359 |
|
360 | add('HTTP_HEADERS', 'httpHeaders',
|
361 | function HTTP_HEADERS(statusCode, headers, resp, statusMessage) {
|
362 | resp.httpResponse.statusCode = statusCode;
|
363 | resp.httpResponse.statusMessage = statusMessage;
|
364 | resp.httpResponse.headers = headers;
|
365 | resp.httpResponse.body = AWS.util.buffer.toBuffer('');
|
366 | resp.httpResponse.buffers = [];
|
367 | resp.httpResponse.numBytes = 0;
|
368 | var dateHeader = headers.date || headers.Date;
|
369 | var service = resp.request.service;
|
370 | if (dateHeader) {
|
371 | var serverTime = Date.parse(dateHeader);
|
372 | if (service.config.correctClockSkew
|
373 | && service.isClockSkewed(serverTime)) {
|
374 | service.applyClockOffset(serverTime);
|
375 | }
|
376 | }
|
377 | });
|
378 |
|
379 | add('HTTP_DATA', 'httpData', function HTTP_DATA(chunk, resp) {
|
380 | if (chunk) {
|
381 | if (AWS.util.isNode()) {
|
382 | resp.httpResponse.numBytes += chunk.length;
|
383 |
|
384 | var total = resp.httpResponse.headers['content-length'];
|
385 | var progress = { loaded: resp.httpResponse.numBytes, total: total };
|
386 | resp.request.emit('httpDownloadProgress', [progress, resp]);
|
387 | }
|
388 |
|
389 | resp.httpResponse.buffers.push(AWS.util.buffer.toBuffer(chunk));
|
390 | }
|
391 | });
|
392 |
|
393 | add('HTTP_DONE', 'httpDone', function HTTP_DONE(resp) {
|
394 |
|
395 | if (resp.httpResponse.buffers && resp.httpResponse.buffers.length > 0) {
|
396 | var body = AWS.util.buffer.concat(resp.httpResponse.buffers);
|
397 | resp.httpResponse.body = body;
|
398 | }
|
399 | delete resp.httpResponse.numBytes;
|
400 | delete resp.httpResponse.buffers;
|
401 | });
|
402 |
|
403 | add('FINALIZE_ERROR', 'retry', function FINALIZE_ERROR(resp) {
|
404 | if (resp.httpResponse.statusCode) {
|
405 | resp.error.statusCode = resp.httpResponse.statusCode;
|
406 | if (resp.error.retryable === undefined) {
|
407 | resp.error.retryable = this.service.retryableError(resp.error, this);
|
408 | }
|
409 | }
|
410 | });
|
411 |
|
412 | add('INVALIDATE_CREDENTIALS', 'retry', function INVALIDATE_CREDENTIALS(resp) {
|
413 | if (!resp.error) return;
|
414 | switch (resp.error.code) {
|
415 | case 'RequestExpired':
|
416 | case 'ExpiredTokenException':
|
417 | case 'ExpiredToken':
|
418 | resp.error.retryable = true;
|
419 | resp.request.service.config.credentials.expired = true;
|
420 | }
|
421 | });
|
422 |
|
423 | add('EXPIRED_SIGNATURE', 'retry', function EXPIRED_SIGNATURE(resp) {
|
424 | var err = resp.error;
|
425 | if (!err) return;
|
426 | if (typeof err.code === 'string' && typeof err.message === 'string') {
|
427 | if (err.code.match(/Signature/) && err.message.match(/expired/)) {
|
428 | resp.error.retryable = true;
|
429 | }
|
430 | }
|
431 | });
|
432 |
|
433 | add('CLOCK_SKEWED', 'retry', function CLOCK_SKEWED(resp) {
|
434 | if (!resp.error) return;
|
435 | if (this.service.clockSkewError(resp.error)
|
436 | && this.service.config.correctClockSkew) {
|
437 | resp.error.retryable = true;
|
438 | }
|
439 | });
|
440 |
|
441 | add('REDIRECT', 'retry', function REDIRECT(resp) {
|
442 | if (resp.error && resp.error.statusCode >= 300 &&
|
443 | resp.error.statusCode < 400 && resp.httpResponse.headers['location']) {
|
444 | this.httpRequest.endpoint =
|
445 | new AWS.Endpoint(resp.httpResponse.headers['location']);
|
446 | this.httpRequest.headers['Host'] = this.httpRequest.endpoint.host;
|
447 | resp.error.redirect = true;
|
448 | resp.error.retryable = true;
|
449 | }
|
450 | });
|
451 |
|
452 | add('RETRY_CHECK', 'retry', function RETRY_CHECK(resp) {
|
453 | if (resp.error) {
|
454 | if (resp.error.redirect && resp.redirectCount < resp.maxRedirects) {
|
455 | resp.error.retryDelay = 0;
|
456 | } else if (resp.retryCount < resp.maxRetries) {
|
457 | resp.error.retryDelay = this.service.retryDelays(resp.retryCount) || 0;
|
458 | }
|
459 | }
|
460 | });
|
461 |
|
462 | addAsync('RESET_RETRY_STATE', 'afterRetry', function RESET_RETRY_STATE(resp, done) {
|
463 | var delay, willRetry = false;
|
464 |
|
465 | if (resp.error) {
|
466 | delay = resp.error.retryDelay || 0;
|
467 | if (resp.error.retryable && resp.retryCount < resp.maxRetries) {
|
468 | resp.retryCount++;
|
469 | willRetry = true;
|
470 | } else if (resp.error.redirect && resp.redirectCount < resp.maxRedirects) {
|
471 | resp.redirectCount++;
|
472 | willRetry = true;
|
473 | }
|
474 | }
|
475 |
|
476 | if (willRetry) {
|
477 | resp.error = null;
|
478 | setTimeout(done, delay);
|
479 | } else {
|
480 | done();
|
481 | }
|
482 | });
|
483 | }),
|
484 |
|
485 | CorePost: new SequentialExecutor().addNamedListeners(function(add) {
|
486 | add('EXTRACT_REQUEST_ID', 'extractData', AWS.util.extractRequestId);
|
487 | add('EXTRACT_REQUEST_ID', 'extractError', AWS.util.extractRequestId);
|
488 |
|
489 | add('ENOTFOUND_ERROR', 'httpError', function ENOTFOUND_ERROR(err) {
|
490 | if (err.code === 'NetworkingError' && err.errno === 'ENOTFOUND') {
|
491 | var message = 'Inaccessible host: `' + err.hostname +
|
492 | '\'. This service may not be available in the `' + err.region +
|
493 | '\' region.';
|
494 | this.response.error = AWS.util.error(new Error(message), {
|
495 | code: 'UnknownEndpoint',
|
496 | region: err.region,
|
497 | hostname: err.hostname,
|
498 | retryable: true,
|
499 | originalError: err
|
500 | });
|
501 | }
|
502 | });
|
503 | }),
|
504 |
|
505 | Logger: new SequentialExecutor().addNamedListeners(function(add) {
|
506 | add('LOG_REQUEST', 'complete', function LOG_REQUEST(resp) {
|
507 | var req = resp.request;
|
508 | var logger = req.service.config.logger;
|
509 | if (!logger) return;
|
510 | function filterSensitiveLog(inputShape, shape) {
|
511 | if (!shape) {
|
512 | return shape;
|
513 | }
|
514 | switch (inputShape.type) {
|
515 | case 'structure':
|
516 | var struct = {};
|
517 | AWS.util.each(shape, function(subShapeName, subShape) {
|
518 | if (Object.prototype.hasOwnProperty.call(inputShape.members, subShapeName)) {
|
519 | struct[subShapeName] = filterSensitiveLog(inputShape.members[subShapeName], subShape);
|
520 | } else {
|
521 | struct[subShapeName] = subShape;
|
522 | }
|
523 | });
|
524 | return struct;
|
525 | case 'list':
|
526 | var list = [];
|
527 | AWS.util.arrayEach(shape, function(subShape, index) {
|
528 | list.push(filterSensitiveLog(inputShape.member, subShape));
|
529 | });
|
530 | return list;
|
531 | case 'map':
|
532 | var map = {};
|
533 | AWS.util.each(shape, function(key, value) {
|
534 | map[key] = filterSensitiveLog(inputShape.value, value);
|
535 | });
|
536 | return map;
|
537 | default:
|
538 | if (inputShape.isSensitive) {
|
539 | return '***SensitiveInformation***';
|
540 | } else {
|
541 | return shape;
|
542 | }
|
543 | }
|
544 | }
|
545 |
|
546 | function buildMessage() {
|
547 | var time = resp.request.service.getSkewCorrectedDate().getTime();
|
548 | var delta = (time - req.startTime.getTime()) / 1000;
|
549 | var ansi = logger.isTTY ? true : false;
|
550 | var status = resp.httpResponse.statusCode;
|
551 | var censoredParams = req.params;
|
552 | if (
|
553 | req.service.api.operations &&
|
554 | req.service.api.operations[req.operation] &&
|
555 | req.service.api.operations[req.operation].input
|
556 | ) {
|
557 | var inputShape = req.service.api.operations[req.operation].input;
|
558 | censoredParams = filterSensitiveLog(inputShape, req.params);
|
559 | }
|
560 | var params = require('util').inspect(censoredParams, true, null);
|
561 | var message = '';
|
562 | if (ansi) message += '\x1B[33m';
|
563 | message += '[AWS ' + req.service.serviceIdentifier + ' ' + status;
|
564 | message += ' ' + delta.toString() + 's ' + resp.retryCount + ' retries]';
|
565 | if (ansi) message += '\x1B[0;1m';
|
566 | message += ' ' + AWS.util.string.lowerFirst(req.operation);
|
567 | message += '(' + params + ')';
|
568 | if (ansi) message += '\x1B[0m';
|
569 | return message;
|
570 | }
|
571 |
|
572 | var line = buildMessage();
|
573 | if (typeof logger.log === 'function') {
|
574 | logger.log(line);
|
575 | } else if (typeof logger.write === 'function') {
|
576 | logger.write(line + '\n');
|
577 | }
|
578 | });
|
579 | }),
|
580 |
|
581 | Json: new SequentialExecutor().addNamedListeners(function(add) {
|
582 | var svc = require('./protocol/json');
|
583 | add('BUILD', 'build', svc.buildRequest);
|
584 | add('EXTRACT_DATA', 'extractData', svc.extractData);
|
585 | add('EXTRACT_ERROR', 'extractError', svc.extractError);
|
586 | }),
|
587 |
|
588 | Rest: new SequentialExecutor().addNamedListeners(function(add) {
|
589 | var svc = require('./protocol/rest');
|
590 | add('BUILD', 'build', svc.buildRequest);
|
591 | add('EXTRACT_DATA', 'extractData', svc.extractData);
|
592 | add('EXTRACT_ERROR', 'extractError', svc.extractError);
|
593 | }),
|
594 |
|
595 | RestJson: new SequentialExecutor().addNamedListeners(function(add) {
|
596 | var svc = require('./protocol/rest_json');
|
597 | add('BUILD', 'build', svc.buildRequest);
|
598 | add('EXTRACT_DATA', 'extractData', svc.extractData);
|
599 | add('EXTRACT_ERROR', 'extractError', svc.extractError);
|
600 | }),
|
601 |
|
602 | RestXml: new SequentialExecutor().addNamedListeners(function(add) {
|
603 | var svc = require('./protocol/rest_xml');
|
604 | add('BUILD', 'build', svc.buildRequest);
|
605 | add('EXTRACT_DATA', 'extractData', svc.extractData);
|
606 | add('EXTRACT_ERROR', 'extractError', svc.extractError);
|
607 | }),
|
608 |
|
609 | Query: new SequentialExecutor().addNamedListeners(function(add) {
|
610 | var svc = require('./protocol/query');
|
611 | add('BUILD', 'build', svc.buildRequest);
|
612 | add('EXTRACT_DATA', 'extractData', svc.extractData);
|
613 | add('EXTRACT_ERROR', 'extractError', svc.extractError);
|
614 | })
|
615 | };
|