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) 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) 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 | if (req.httpRequest.headers['Content-Length'] === undefined) {
|
160 | try {
|
161 | var length = AWS.util.string.byteLength(req.httpRequest.body);
|
162 | req.httpRequest.headers['Content-Length'] = length;
|
163 | } catch (err) {
|
164 | if (authtype.indexOf('unsigned-body') === -1) {
|
165 | throw err;
|
166 | } else {
|
167 |
|
168 | return;
|
169 | }
|
170 | }
|
171 | }
|
172 | });
|
173 |
|
174 | add('SET_HTTP_HOST', 'afterBuild', function SET_HTTP_HOST(req) {
|
175 | req.httpRequest.headers['Host'] = req.httpRequest.endpoint.host;
|
176 | });
|
177 |
|
178 | add('RESTART', 'restart', function RESTART() {
|
179 | var err = this.response.error;
|
180 | if (!err || !err.retryable) return;
|
181 |
|
182 | this.httpRequest = new AWS.HttpRequest(
|
183 | this.service.endpoint,
|
184 | this.service.region
|
185 | );
|
186 |
|
187 | if (this.response.retryCount < this.service.config.maxRetries) {
|
188 | this.response.retryCount++;
|
189 | } else {
|
190 | this.response.error = null;
|
191 | }
|
192 | });
|
193 |
|
194 | var addToHead = true;
|
195 | addAsync('DISCOVER_ENDPOINT', 'sign', DISCOVER_ENDPOINT, addToHead);
|
196 |
|
197 | addAsync('SIGN', 'sign', function SIGN(req, done) {
|
198 | var service = req.service;
|
199 | var operations = req.service.api.operations || {};
|
200 | var operation = operations[req.operation];
|
201 | var authtype = operation ? operation.authtype : '';
|
202 | if (!service.api.signatureVersion && !authtype) return done();
|
203 |
|
204 | service.config.getCredentials(function (err, credentials) {
|
205 | if (err) {
|
206 | req.response.error = err;
|
207 | return done();
|
208 | }
|
209 |
|
210 | try {
|
211 | var date = service.getSkewCorrectedDate();
|
212 | var SignerClass = service.getSignerClass(req);
|
213 | var signer = new SignerClass(req.httpRequest,
|
214 | service.api.signingName || service.api.endpointPrefix,
|
215 | {
|
216 | signatureCache: service.config.signatureCache,
|
217 | operation: operation,
|
218 | signatureVersion: service.api.signatureVersion
|
219 | });
|
220 | signer.setServiceClientId(service._clientId);
|
221 |
|
222 |
|
223 | delete req.httpRequest.headers['Authorization'];
|
224 | delete req.httpRequest.headers['Date'];
|
225 | delete req.httpRequest.headers['X-Amz-Date'];
|
226 |
|
227 |
|
228 | signer.addAuthorization(credentials, date);
|
229 | req.signedAt = date;
|
230 | } catch (e) {
|
231 | req.response.error = e;
|
232 | }
|
233 | done();
|
234 | });
|
235 | });
|
236 |
|
237 | add('VALIDATE_RESPONSE', 'validateResponse', function VALIDATE_RESPONSE(resp) {
|
238 | if (this.service.successfulResponse(resp, this)) {
|
239 | resp.data = {};
|
240 | resp.error = null;
|
241 | } else {
|
242 | resp.data = null;
|
243 | resp.error = AWS.util.error(new Error(),
|
244 | {code: 'UnknownError', message: 'An unknown error occurred.'});
|
245 | }
|
246 | });
|
247 |
|
248 | addAsync('SEND', 'send', function SEND(resp, done) {
|
249 | resp.httpResponse._abortCallback = done;
|
250 | resp.error = null;
|
251 | resp.data = null;
|
252 |
|
253 | function callback(httpResp) {
|
254 | resp.httpResponse.stream = httpResp;
|
255 | var stream = resp.request.httpRequest.stream;
|
256 | var service = resp.request.service;
|
257 | var api = service.api;
|
258 | var operationName = resp.request.operation;
|
259 | var operation = api.operations[operationName] || {};
|
260 |
|
261 | httpResp.on('headers', function onHeaders(statusCode, headers, statusMessage) {
|
262 | resp.request.emit(
|
263 | 'httpHeaders',
|
264 | [statusCode, headers, resp, statusMessage]
|
265 | );
|
266 |
|
267 | if (!resp.httpResponse.streaming) {
|
268 | if (AWS.HttpClient.streamsApiVersion === 2) {
|
269 |
|
270 |
|
271 | if (operation.hasEventOutput && service.successfulResponse(resp)) {
|
272 |
|
273 | resp.request.emit('httpDone');
|
274 | done();
|
275 | return;
|
276 | }
|
277 |
|
278 | httpResp.on('readable', function onReadable() {
|
279 | var data = httpResp.read();
|
280 | if (data !== null) {
|
281 | resp.request.emit('httpData', [data, resp]);
|
282 | }
|
283 | });
|
284 | } else {
|
285 | httpResp.on('data', function onData(data) {
|
286 | resp.request.emit('httpData', [data, resp]);
|
287 | });
|
288 | }
|
289 | }
|
290 | });
|
291 |
|
292 | httpResp.on('end', function onEnd() {
|
293 | if (!stream || !stream.didCallback) {
|
294 | if (AWS.HttpClient.streamsApiVersion === 2 && (operation.hasEventOutput && service.successfulResponse(resp))) {
|
295 |
|
296 | return;
|
297 | }
|
298 | resp.request.emit('httpDone');
|
299 | done();
|
300 | }
|
301 | });
|
302 | }
|
303 |
|
304 | function progress(httpResp) {
|
305 | httpResp.on('sendProgress', function onSendProgress(value) {
|
306 | resp.request.emit('httpUploadProgress', [value, resp]);
|
307 | });
|
308 |
|
309 | httpResp.on('receiveProgress', function onReceiveProgress(value) {
|
310 | resp.request.emit('httpDownloadProgress', [value, resp]);
|
311 | });
|
312 | }
|
313 |
|
314 | function error(err) {
|
315 | if (err.code !== 'RequestAbortedError') {
|
316 | var errCode = err.code === 'TimeoutError' ? err.code : 'NetworkingError';
|
317 | err = AWS.util.error(err, {
|
318 | code: errCode,
|
319 | region: resp.request.httpRequest.region,
|
320 | hostname: resp.request.httpRequest.endpoint.hostname,
|
321 | retryable: true
|
322 | });
|
323 | }
|
324 | resp.error = err;
|
325 | resp.request.emit('httpError', [resp.error, resp], function() {
|
326 | done();
|
327 | });
|
328 | }
|
329 |
|
330 | function executeSend() {
|
331 | var http = AWS.HttpClient.getInstance();
|
332 | var httpOptions = resp.request.service.config.httpOptions || {};
|
333 | try {
|
334 | var stream = http.handleRequest(resp.request.httpRequest, httpOptions,
|
335 | callback, error);
|
336 | progress(stream);
|
337 | } catch (err) {
|
338 | error(err);
|
339 | }
|
340 | }
|
341 | var timeDiff = (resp.request.service.getSkewCorrectedDate() - this.signedAt) / 1000;
|
342 | if (timeDiff >= 60 * 10) {
|
343 | this.emit('sign', [this], function(err) {
|
344 | if (err) done(err);
|
345 | else executeSend();
|
346 | });
|
347 | } else {
|
348 | executeSend();
|
349 | }
|
350 | });
|
351 |
|
352 | add('HTTP_HEADERS', 'httpHeaders',
|
353 | function HTTP_HEADERS(statusCode, headers, resp, statusMessage) {
|
354 | resp.httpResponse.statusCode = statusCode;
|
355 | resp.httpResponse.statusMessage = statusMessage;
|
356 | resp.httpResponse.headers = headers;
|
357 | resp.httpResponse.body = new AWS.util.Buffer('');
|
358 | resp.httpResponse.buffers = [];
|
359 | resp.httpResponse.numBytes = 0;
|
360 | var dateHeader = headers.date || headers.Date;
|
361 | var service = resp.request.service;
|
362 | if (dateHeader) {
|
363 | var serverTime = Date.parse(dateHeader);
|
364 | if (service.config.correctClockSkew
|
365 | && service.isClockSkewed(serverTime)) {
|
366 | service.applyClockOffset(serverTime);
|
367 | }
|
368 | }
|
369 | });
|
370 |
|
371 | add('HTTP_DATA', 'httpData', function HTTP_DATA(chunk, resp) {
|
372 | if (chunk) {
|
373 | if (AWS.util.isNode()) {
|
374 | resp.httpResponse.numBytes += chunk.length;
|
375 |
|
376 | var total = resp.httpResponse.headers['content-length'];
|
377 | var progress = { loaded: resp.httpResponse.numBytes, total: total };
|
378 | resp.request.emit('httpDownloadProgress', [progress, resp]);
|
379 | }
|
380 |
|
381 | resp.httpResponse.buffers.push(new AWS.util.Buffer(chunk));
|
382 | }
|
383 | });
|
384 |
|
385 | add('HTTP_DONE', 'httpDone', function HTTP_DONE(resp) {
|
386 |
|
387 | if (resp.httpResponse.buffers && resp.httpResponse.buffers.length > 0) {
|
388 | var body = AWS.util.buffer.concat(resp.httpResponse.buffers);
|
389 | resp.httpResponse.body = body;
|
390 | }
|
391 | delete resp.httpResponse.numBytes;
|
392 | delete resp.httpResponse.buffers;
|
393 | });
|
394 |
|
395 | add('FINALIZE_ERROR', 'retry', function FINALIZE_ERROR(resp) {
|
396 | if (resp.httpResponse.statusCode) {
|
397 | resp.error.statusCode = resp.httpResponse.statusCode;
|
398 | if (resp.error.retryable === undefined) {
|
399 | resp.error.retryable = this.service.retryableError(resp.error, this);
|
400 | }
|
401 | }
|
402 | });
|
403 |
|
404 | add('INVALIDATE_CREDENTIALS', 'retry', function INVALIDATE_CREDENTIALS(resp) {
|
405 | if (!resp.error) return;
|
406 | switch (resp.error.code) {
|
407 | case 'RequestExpired':
|
408 | case 'ExpiredTokenException':
|
409 | case 'ExpiredToken':
|
410 | resp.error.retryable = true;
|
411 | resp.request.service.config.credentials.expired = true;
|
412 | }
|
413 | });
|
414 |
|
415 | add('EXPIRED_SIGNATURE', 'retry', function EXPIRED_SIGNATURE(resp) {
|
416 | var err = resp.error;
|
417 | if (!err) return;
|
418 | if (typeof err.code === 'string' && typeof err.message === 'string') {
|
419 | if (err.code.match(/Signature/) && err.message.match(/expired/)) {
|
420 | resp.error.retryable = true;
|
421 | }
|
422 | }
|
423 | });
|
424 |
|
425 | add('CLOCK_SKEWED', 'retry', function CLOCK_SKEWED(resp) {
|
426 | if (!resp.error) return;
|
427 | if (this.service.clockSkewError(resp.error)
|
428 | && this.service.config.correctClockSkew) {
|
429 | resp.error.retryable = true;
|
430 | }
|
431 | });
|
432 |
|
433 | add('REDIRECT', 'retry', function REDIRECT(resp) {
|
434 | if (resp.error && resp.error.statusCode >= 300 &&
|
435 | resp.error.statusCode < 400 && resp.httpResponse.headers['location']) {
|
436 | this.httpRequest.endpoint =
|
437 | new AWS.Endpoint(resp.httpResponse.headers['location']);
|
438 | this.httpRequest.headers['Host'] = this.httpRequest.endpoint.host;
|
439 | resp.error.redirect = true;
|
440 | resp.error.retryable = true;
|
441 | }
|
442 | });
|
443 |
|
444 | add('RETRY_CHECK', 'retry', function RETRY_CHECK(resp) {
|
445 | if (resp.error) {
|
446 | if (resp.error.redirect && resp.redirectCount < resp.maxRedirects) {
|
447 | resp.error.retryDelay = 0;
|
448 | } else if (resp.retryCount < resp.maxRetries) {
|
449 | resp.error.retryDelay = this.service.retryDelays(resp.retryCount) || 0;
|
450 | }
|
451 | }
|
452 | });
|
453 |
|
454 | addAsync('RESET_RETRY_STATE', 'afterRetry', function RESET_RETRY_STATE(resp, done) {
|
455 | var delay, willRetry = false;
|
456 |
|
457 | if (resp.error) {
|
458 | delay = resp.error.retryDelay || 0;
|
459 | if (resp.error.retryable && resp.retryCount < resp.maxRetries) {
|
460 | resp.retryCount++;
|
461 | willRetry = true;
|
462 | } else if (resp.error.redirect && resp.redirectCount < resp.maxRedirects) {
|
463 | resp.redirectCount++;
|
464 | willRetry = true;
|
465 | }
|
466 | }
|
467 |
|
468 | if (willRetry) {
|
469 | resp.error = null;
|
470 | setTimeout(done, delay);
|
471 | } else {
|
472 | done();
|
473 | }
|
474 | });
|
475 | }),
|
476 |
|
477 | CorePost: new SequentialExecutor().addNamedListeners(function(add) {
|
478 | add('EXTRACT_REQUEST_ID', 'extractData', AWS.util.extractRequestId);
|
479 | add('EXTRACT_REQUEST_ID', 'extractError', AWS.util.extractRequestId);
|
480 |
|
481 | add('ENOTFOUND_ERROR', 'httpError', function ENOTFOUND_ERROR(err) {
|
482 | if (err.code === 'NetworkingError' && err.errno === 'ENOTFOUND') {
|
483 | var message = 'Inaccessible host: `' + err.hostname +
|
484 | '\'. This service may not be available in the `' + err.region +
|
485 | '\' region.';
|
486 | this.response.error = AWS.util.error(new Error(message), {
|
487 | code: 'UnknownEndpoint',
|
488 | region: err.region,
|
489 | hostname: err.hostname,
|
490 | retryable: true,
|
491 | originalError: err
|
492 | });
|
493 | }
|
494 | });
|
495 | }),
|
496 |
|
497 | Logger: new SequentialExecutor().addNamedListeners(function(add) {
|
498 | add('LOG_REQUEST', 'complete', function LOG_REQUEST(resp) {
|
499 | var req = resp.request;
|
500 | var logger = req.service.config.logger;
|
501 | if (!logger) return;
|
502 | function filterSensitiveLog(inputShape, shape) {
|
503 | if (!shape) {
|
504 | return shape;
|
505 | }
|
506 | switch (inputShape.type) {
|
507 | case 'structure':
|
508 | var struct = {};
|
509 | AWS.util.each(shape, function(subShapeName, subShape) {
|
510 | if (Object.prototype.hasOwnProperty.call(inputShape.members, subShapeName)) {
|
511 | struct[subShapeName] = filterSensitiveLog(inputShape.members[subShapeName], subShape);
|
512 | } else {
|
513 | struct[subShapeName] = subShape;
|
514 | }
|
515 | });
|
516 | return struct;
|
517 | case 'list':
|
518 | var list = [];
|
519 | AWS.util.arrayEach(shape, function(subShape, index) {
|
520 | list.push(filterSensitiveLog(inputShape.member, subShape));
|
521 | });
|
522 | return list;
|
523 | case 'map':
|
524 | var map = {};
|
525 | AWS.util.each(shape, function(key, value) {
|
526 | map[key] = filterSensitiveLog(inputShape.value, value);
|
527 | });
|
528 | return map;
|
529 | default:
|
530 | if (inputShape.isSensitive) {
|
531 | return '***SensitiveInformation***';
|
532 | } else {
|
533 | return shape;
|
534 | }
|
535 | }
|
536 | }
|
537 |
|
538 | function buildMessage() {
|
539 | var time = resp.request.service.getSkewCorrectedDate().getTime();
|
540 | var delta = (time - req.startTime.getTime()) / 1000;
|
541 | var ansi = logger.isTTY ? true : false;
|
542 | var status = resp.httpResponse.statusCode;
|
543 | var censoredParams = req.params;
|
544 | if (
|
545 | req.service.api.operations &&
|
546 | req.service.api.operations[req.operation] &&
|
547 | req.service.api.operations[req.operation].input
|
548 | ) {
|
549 | var inputShape = req.service.api.operations[req.operation].input;
|
550 | censoredParams = filterSensitiveLog(inputShape, req.params);
|
551 | }
|
552 | var params = require('util').inspect(censoredParams, true, null);
|
553 | var message = '';
|
554 | if (ansi) message += '\x1B[33m';
|
555 | message += '[AWS ' + req.service.serviceIdentifier + ' ' + status;
|
556 | message += ' ' + delta.toString() + 's ' + resp.retryCount + ' retries]';
|
557 | if (ansi) message += '\x1B[0;1m';
|
558 | message += ' ' + AWS.util.string.lowerFirst(req.operation);
|
559 | message += '(' + params + ')';
|
560 | if (ansi) message += '\x1B[0m';
|
561 | return message;
|
562 | }
|
563 |
|
564 | var line = buildMessage();
|
565 | if (typeof logger.log === 'function') {
|
566 | logger.log(line);
|
567 | } else if (typeof logger.write === 'function') {
|
568 | logger.write(line + '\n');
|
569 | }
|
570 | });
|
571 | }),
|
572 |
|
573 | Json: new SequentialExecutor().addNamedListeners(function(add) {
|
574 | var svc = require('./protocol/json');
|
575 | add('BUILD', 'build', svc.buildRequest);
|
576 | add('EXTRACT_DATA', 'extractData', svc.extractData);
|
577 | add('EXTRACT_ERROR', 'extractError', svc.extractError);
|
578 | }),
|
579 |
|
580 | Rest: new SequentialExecutor().addNamedListeners(function(add) {
|
581 | var svc = require('./protocol/rest');
|
582 | add('BUILD', 'build', svc.buildRequest);
|
583 | add('EXTRACT_DATA', 'extractData', svc.extractData);
|
584 | add('EXTRACT_ERROR', 'extractError', svc.extractError);
|
585 | }),
|
586 |
|
587 | RestJson: new SequentialExecutor().addNamedListeners(function(add) {
|
588 | var svc = require('./protocol/rest_json');
|
589 | add('BUILD', 'build', svc.buildRequest);
|
590 | add('EXTRACT_DATA', 'extractData', svc.extractData);
|
591 | add('EXTRACT_ERROR', 'extractError', svc.extractError);
|
592 | }),
|
593 |
|
594 | RestXml: new SequentialExecutor().addNamedListeners(function(add) {
|
595 | var svc = require('./protocol/rest_xml');
|
596 | add('BUILD', 'build', svc.buildRequest);
|
597 | add('EXTRACT_DATA', 'extractData', svc.extractData);
|
598 | add('EXTRACT_ERROR', 'extractError', svc.extractError);
|
599 | }),
|
600 |
|
601 | Query: new SequentialExecutor().addNamedListeners(function(add) {
|
602 | var svc = require('./protocol/query');
|
603 | add('BUILD', 'build', svc.buildRequest);
|
604 | add('EXTRACT_DATA', 'extractData', svc.extractData);
|
605 | add('EXTRACT_ERROR', 'extractError', svc.extractError);
|
606 | })
|
607 | };
|