1 | var AWS = require('./core');
|
2 | var AcceptorStateMachine = require('./state_machine');
|
3 | var inherit = AWS.util.inherit;
|
4 | var domain = AWS.util.domain;
|
5 | var jmespath = require('jmespath');
|
6 |
|
7 | /**
|
8 | * @api private
|
9 | */
|
10 | var hardErrorStates = {success: 1, error: 1, complete: 1};
|
11 |
|
12 | function isTerminalState(machine) {
|
13 | return Object.prototype.hasOwnProperty.call(hardErrorStates, machine._asm.currentState);
|
14 | }
|
15 |
|
16 | var fsm = new AcceptorStateMachine();
|
17 | fsm.setupStates = function() {
|
18 | var transition = function(_, done) {
|
19 | var self = this;
|
20 | self._haltHandlersOnError = false;
|
21 |
|
22 | self.emit(self._asm.currentState, function(err) {
|
23 | if (err) {
|
24 | if (isTerminalState(self)) {
|
25 | if (domain && self.domain instanceof domain.Domain) {
|
26 | err.domainEmitter = self;
|
27 | err.domain = self.domain;
|
28 | err.domainThrown = false;
|
29 | self.domain.emit('error', err);
|
30 | } else {
|
31 | throw err;
|
32 | }
|
33 | } else {
|
34 | self.response.error = err;
|
35 | done(err);
|
36 | }
|
37 | } else {
|
38 | done(self.response.error);
|
39 | }
|
40 | });
|
41 |
|
42 | };
|
43 |
|
44 | this.addState('validate', 'build', 'error', transition);
|
45 | this.addState('build', 'afterBuild', 'restart', transition);
|
46 | this.addState('afterBuild', 'sign', 'restart', transition);
|
47 | this.addState('sign', 'send', 'retry', transition);
|
48 | this.addState('retry', 'afterRetry', 'afterRetry', transition);
|
49 | this.addState('afterRetry', 'sign', 'error', transition);
|
50 | this.addState('send', 'validateResponse', 'retry', transition);
|
51 | this.addState('validateResponse', 'extractData', 'extractError', transition);
|
52 | this.addState('extractError', 'extractData', 'retry', transition);
|
53 | this.addState('extractData', 'success', 'retry', transition);
|
54 | this.addState('restart', 'build', 'error', transition);
|
55 | this.addState('success', 'complete', 'complete', transition);
|
56 | this.addState('error', 'complete', 'complete', transition);
|
57 | this.addState('complete', null, null, transition);
|
58 | };
|
59 | fsm.setupStates();
|
60 |
|
61 | /**
|
62 | * ## Asynchronous Requests
|
63 | *
|
64 | * All requests made through the SDK are asynchronous and use a
|
65 | * callback interface. Each service method that kicks off a request
|
66 | * returns an `AWS.Request` object that you can use to register
|
67 | * callbacks.
|
68 | *
|
69 | * For example, the following service method returns the request
|
70 | * object as "request", which can be used to register callbacks:
|
71 | *
|
72 | * ```javascript
|
73 | * // request is an AWS.Request object
|
74 | * var request = ec2.describeInstances();
|
75 | *
|
76 | * // register callbacks on request to retrieve response data
|
77 | * request.on('success', function(response) {
|
78 | * console.log(response.data);
|
79 | * });
|
80 | * ```
|
81 | *
|
82 | * When a request is ready to be sent, the {send} method should
|
83 | * be called:
|
84 | *
|
85 | * ```javascript
|
86 | * request.send();
|
87 | * ```
|
88 | *
|
89 | * Since registered callbacks may or may not be idempotent, requests should only
|
90 | * be sent once. To perform the same operation multiple times, you will need to
|
91 | * create multiple request objects, each with its own registered callbacks.
|
92 | *
|
93 | * ## Removing Default Listeners for Events
|
94 | *
|
95 | * Request objects are built with default listeners for the various events,
|
96 | * depending on the service type. In some cases, you may want to remove
|
97 | * some built-in listeners to customize behaviour. Doing this requires
|
98 | * access to the built-in listener functions, which are exposed through
|
99 | * the {AWS.EventListeners.Core} namespace. For instance, you may
|
100 | * want to customize the HTTP handler used when sending a request. In this
|
101 | * case, you can remove the built-in listener associated with the 'send'
|
102 | * event, the {AWS.EventListeners.Core.SEND} listener and add your own.
|
103 | *
|
104 | * ## Multiple Callbacks and Chaining
|
105 | *
|
106 | * You can register multiple callbacks on any request object. The
|
107 | * callbacks can be registered for different events, or all for the
|
108 | * same event. In addition, you can chain callback registration, for
|
109 | * example:
|
110 | *
|
111 | * ```javascript
|
112 | * request.
|
113 | * on('success', function(response) {
|
114 | * console.log("Success!");
|
115 | * }).
|
116 | * on('error', function(response) {
|
117 | * console.log("Error!");
|
118 | * }).
|
119 | * on('complete', function(response) {
|
120 | * console.log("Always!");
|
121 | * }).
|
122 | * send();
|
123 | * ```
|
124 | *
|
125 | * The above example will print either "Success! Always!", or "Error! Always!",
|
126 | * depending on whether the request succeeded or not.
|
127 | *
|
128 | * @!attribute httpRequest
|
129 | * @readonly
|
130 | * @!group HTTP Properties
|
131 | * @return [AWS.HttpRequest] the raw HTTP request object
|
132 | * containing request headers and body information
|
133 | * sent by the service.
|
134 | *
|
135 | * @!attribute startTime
|
136 | * @readonly
|
137 | * @!group Operation Properties
|
138 | * @return [Date] the time that the request started
|
139 | *
|
140 | * @!group Request Building Events
|
141 | *
|
142 | * @!event validate(request)
|
143 | * Triggered when a request is being validated. Listeners
|
144 | * should throw an error if the request should not be sent.
|
145 | * @param request [Request] the request object being sent
|
146 | * @see AWS.EventListeners.Core.VALIDATE_CREDENTIALS
|
147 | * @see AWS.EventListeners.Core.VALIDATE_REGION
|
148 | * @example Ensuring that a certain parameter is set before sending a request
|
149 | * var req = s3.putObject(params);
|
150 | * req.on('validate', function() {
|
151 | * if (!req.params.Body.match(/^Hello\s/)) {
|
152 | * throw new Error('Body must start with "Hello "');
|
153 | * }
|
154 | * });
|
155 | * req.send(function(err, data) { ... });
|
156 | *
|
157 | * @!event build(request)
|
158 | * Triggered when the request payload is being built. Listeners
|
159 | * should fill the necessary information to send the request
|
160 | * over HTTP.
|
161 | * @param (see AWS.Request~validate)
|
162 | * @example Add a custom HTTP header to a request
|
163 | * var req = s3.putObject(params);
|
164 | * req.on('build', function() {
|
165 | * req.httpRequest.headers['Custom-Header'] = 'value';
|
166 | * });
|
167 | * req.send(function(err, data) { ... });
|
168 | *
|
169 | * @!event sign(request)
|
170 | * Triggered when the request is being signed. Listeners should
|
171 | * add the correct authentication headers and/or adjust the body,
|
172 | * depending on the authentication mechanism being used.
|
173 | * @param (see AWS.Request~validate)
|
174 | *
|
175 | * @!group Request Sending Events
|
176 | *
|
177 | * @!event send(response)
|
178 | * Triggered when the request is ready to be sent. Listeners
|
179 | * should call the underlying transport layer to initiate
|
180 | * the sending of the request.
|
181 | * @param response [Response] the response object
|
182 | * @context [Request] the request object that was sent
|
183 | * @see AWS.EventListeners.Core.SEND
|
184 | *
|
185 | * @!event retry(response)
|
186 | * Triggered when a request failed and might need to be retried or redirected.
|
187 | * If the response is retryable, the listener should set the
|
188 | * `response.error.retryable` property to `true`, and optionally set
|
189 | * `response.error.retryDelay` to the millisecond delay for the next attempt.
|
190 | * In the case of a redirect, `response.error.redirect` should be set to
|
191 | * `true` with `retryDelay` set to an optional delay on the next request.
|
192 | *
|
193 | * If a listener decides that a request should not be retried,
|
194 | * it should set both `retryable` and `redirect` to false.
|
195 | *
|
196 | * Note that a retryable error will be retried at most
|
197 | * {AWS.Config.maxRetries} times (based on the service object's config).
|
198 | * Similarly, a request that is redirected will only redirect at most
|
199 | * {AWS.Config.maxRedirects} times.
|
200 | *
|
201 | * @param (see AWS.Request~send)
|
202 | * @context (see AWS.Request~send)
|
203 | * @example Adding a custom retry for a 404 response
|
204 | * request.on('retry', function(response) {
|
205 | * // this resource is not yet available, wait 10 seconds to get it again
|
206 | * if (response.httpResponse.statusCode === 404 && response.error) {
|
207 | * response.error.retryable = true; // retry this error
|
208 | * response.error.retryDelay = 10000; // wait 10 seconds
|
209 | * }
|
210 | * });
|
211 | *
|
212 | * @!group Data Parsing Events
|
213 | *
|
214 | * @!event extractError(response)
|
215 | * Triggered on all non-2xx requests so that listeners can extract
|
216 | * error details from the response body. Listeners to this event
|
217 | * should set the `response.error` property.
|
218 | * @param (see AWS.Request~send)
|
219 | * @context (see AWS.Request~send)
|
220 | *
|
221 | * @!event extractData(response)
|
222 | * Triggered in successful requests to allow listeners to
|
223 | * de-serialize the response body into `response.data`.
|
224 | * @param (see AWS.Request~send)
|
225 | * @context (see AWS.Request~send)
|
226 | *
|
227 | * @!group Completion Events
|
228 | *
|
229 | * @!event success(response)
|
230 | * Triggered when the request completed successfully.
|
231 | * `response.data` will contain the response data and
|
232 | * `response.error` will be null.
|
233 | * @param (see AWS.Request~send)
|
234 | * @context (see AWS.Request~send)
|
235 | *
|
236 | * @!event error(error, response)
|
237 | * Triggered when an error occurs at any point during the
|
238 | * request. `response.error` will contain details about the error
|
239 | * that occurred. `response.data` will be null.
|
240 | * @param error [Error] the error object containing details about
|
241 | * the error that occurred.
|
242 | * @param (see AWS.Request~send)
|
243 | * @context (see AWS.Request~send)
|
244 | *
|
245 | * @!event complete(response)
|
246 | * Triggered whenever a request cycle completes. `response.error`
|
247 | * should be checked, since the request may have failed.
|
248 | * @param (see AWS.Request~send)
|
249 | * @context (see AWS.Request~send)
|
250 | *
|
251 | * @!group HTTP Events
|
252 | *
|
253 | * @!event httpHeaders(statusCode, headers, response, statusMessage)
|
254 | * Triggered when headers are sent by the remote server
|
255 | * @param statusCode [Integer] the HTTP response code
|
256 | * @param headers [map<String,String>] the response headers
|
257 | * @param (see AWS.Request~send)
|
258 | * @param statusMessage [String] A status message corresponding to the HTTP
|
259 | * response code
|
260 | * @context (see AWS.Request~send)
|
261 | *
|
262 | * @!event httpData(chunk, response)
|
263 | * Triggered when data is sent by the remote server
|
264 | * @param chunk [Buffer] the buffer data containing the next data chunk
|
265 | * from the server
|
266 | * @param (see AWS.Request~send)
|
267 | * @context (see AWS.Request~send)
|
268 | * @see AWS.EventListeners.Core.HTTP_DATA
|
269 | *
|
270 | * @!event httpUploadProgress(progress, response)
|
271 | * Triggered when the HTTP request has uploaded more data
|
272 | * @param progress [map] An object containing the `loaded` and `total` bytes
|
273 | * of the request.
|
274 | * @param (see AWS.Request~send)
|
275 | * @context (see AWS.Request~send)
|
276 | * @note This event will not be emitted in Node.js 0.8.x.
|
277 | *
|
278 | * @!event httpDownloadProgress(progress, response)
|
279 | * Triggered when the HTTP request has downloaded more data
|
280 | * @param progress [map] An object containing the `loaded` and `total` bytes
|
281 | * of the request.
|
282 | * @param (see AWS.Request~send)
|
283 | * @context (see AWS.Request~send)
|
284 | * @note This event will not be emitted in Node.js 0.8.x.
|
285 | *
|
286 | * @!event httpError(error, response)
|
287 | * Triggered when the HTTP request failed
|
288 | * @param error [Error] the error object that was thrown
|
289 | * @param (see AWS.Request~send)
|
290 | * @context (see AWS.Request~send)
|
291 | *
|
292 | * @!event httpDone(response)
|
293 | * Triggered when the server is finished sending data
|
294 | * @param (see AWS.Request~send)
|
295 | * @context (see AWS.Request~send)
|
296 | *
|
297 | * @see AWS.Response
|
298 | */
|
299 | AWS.Request = inherit({
|
300 |
|
301 | /**
|
302 | * Creates a request for an operation on a given service with
|
303 | * a set of input parameters.
|
304 | *
|
305 | * @param service [AWS.Service] the service to perform the operation on
|
306 | * @param operation [String] the operation to perform on the service
|
307 | * @param params [Object] parameters to send to the operation.
|
308 | * See the operation's documentation for the format of the
|
309 | * parameters.
|
310 | */
|
311 | constructor: function Request(service, operation, params) {
|
312 | var endpoint = service.endpoint;
|
313 | var region = service.config.region;
|
314 | var customUserAgent = service.config.customUserAgent;
|
315 |
|
316 | // global endpoints sign as us-east-1
|
317 | if (service.isGlobalEndpoint) region = 'us-east-1';
|
318 |
|
319 | this.domain = domain && domain.active;
|
320 | this.service = service;
|
321 | this.operation = operation;
|
322 | this.params = params || {};
|
323 | this.httpRequest = new AWS.HttpRequest(endpoint, region);
|
324 | this.httpRequest.appendToUserAgent(customUserAgent);
|
325 | this.startTime = service.getSkewCorrectedDate();
|
326 |
|
327 | this.response = new AWS.Response(this);
|
328 | this._asm = new AcceptorStateMachine(fsm.states, 'validate');
|
329 | this._haltHandlersOnError = false;
|
330 |
|
331 | AWS.SequentialExecutor.call(this);
|
332 | this.emit = this.emitEvent;
|
333 | },
|
334 |
|
335 | /**
|
336 | * @!group Sending a Request
|
337 | */
|
338 |
|
339 | /**
|
340 | * @overload send(callback = null)
|
341 | * Sends the request object.
|
342 | *
|
343 | * @callback callback function(err, data)
|
344 | * If a callback is supplied, it is called when a response is returned
|
345 | * from the service.
|
346 | * @context [AWS.Request] the request object being sent.
|
347 | * @param err [Error] the error object returned from the request.
|
348 | * Set to `null` if the request is successful.
|
349 | * @param data [Object] the de-serialized data returned from
|
350 | * the request. Set to `null` if a request error occurs.
|
351 | * @example Sending a request with a callback
|
352 | * request = s3.putObject({Bucket: 'bucket', Key: 'key'});
|
353 | * request.send(function(err, data) { console.log(err, data); });
|
354 | * @example Sending a request with no callback (using event handlers)
|
355 | * request = s3.putObject({Bucket: 'bucket', Key: 'key'});
|
356 | * request.on('complete', function(response) { ... }); // register a callback
|
357 | * request.send();
|
358 | */
|
359 | send: function send(callback) {
|
360 | if (callback) {
|
361 | // append to user agent
|
362 | this.httpRequest.appendToUserAgent('callback');
|
363 | this.on('complete', function (resp) {
|
364 | callback.call(resp, resp.error, resp.data);
|
365 | });
|
366 | }
|
367 | this.runTo();
|
368 |
|
369 | return this.response;
|
370 | },
|
371 |
|
372 | /**
|
373 | * @!method promise()
|
374 | * Sends the request and returns a 'thenable' promise.
|
375 | *
|
376 | * Two callbacks can be provided to the `then` method on the returned promise.
|
377 | * The first callback will be called if the promise is fulfilled, and the second
|
378 | * callback will be called if the promise is rejected.
|
379 | * @callback fulfilledCallback function(data)
|
380 | * Called if the promise is fulfilled.
|
381 | * @param data [Object] the de-serialized data returned from the request.
|
382 | * @callback rejectedCallback function(error)
|
383 | * Called if the promise is rejected.
|
384 | * @param error [Error] the error object returned from the request.
|
385 | * @return [Promise] A promise that represents the state of the request.
|
386 | * @example Sending a request using promises.
|
387 | * var request = s3.putObject({Bucket: 'bucket', Key: 'key'});
|
388 | * var result = request.promise();
|
389 | * result.then(function(data) { ... }, function(error) { ... });
|
390 | */
|
391 |
|
392 | /**
|
393 | * @api private
|
394 | */
|
395 | build: function build(callback) {
|
396 | return this.runTo('send', callback);
|
397 | },
|
398 |
|
399 | /**
|
400 | * @api private
|
401 | */
|
402 | runTo: function runTo(state, done) {
|
403 | this._asm.runTo(state, done, this);
|
404 | return this;
|
405 | },
|
406 |
|
407 | /**
|
408 | * Aborts a request, emitting the error and complete events.
|
409 | *
|
410 | * @!macro nobrowser
|
411 | * @example Aborting a request after sending
|
412 | * var params = {
|
413 | * Bucket: 'bucket', Key: 'key',
|
414 | * Body: new Buffer(1024 * 1024 * 5) // 5MB payload
|
415 | * };
|
416 | * var request = s3.putObject(params);
|
417 | * request.send(function (err, data) {
|
418 | * if (err) console.log("Error:", err.code, err.message);
|
419 | * else console.log(data);
|
420 | * });
|
421 | *
|
422 | * // abort request in 1 second
|
423 | * setTimeout(request.abort.bind(request), 1000);
|
424 | *
|
425 | * // prints "Error: RequestAbortedError Request aborted by user"
|
426 | * @return [AWS.Request] the same request object, for chaining.
|
427 | * @since v1.4.0
|
428 | */
|
429 | abort: function abort() {
|
430 | this.removeAllListeners('validateResponse');
|
431 | this.removeAllListeners('extractError');
|
432 | this.on('validateResponse', function addAbortedError(resp) {
|
433 | resp.error = AWS.util.error(new Error('Request aborted by user'), {
|
434 | code: 'RequestAbortedError', retryable: false
|
435 | });
|
436 | });
|
437 |
|
438 | if (this.httpRequest.stream && !this.httpRequest.stream.didCallback) { // abort HTTP stream
|
439 | this.httpRequest.stream.abort();
|
440 | if (this.httpRequest._abortCallback) {
|
441 | this.httpRequest._abortCallback();
|
442 | } else {
|
443 | this.removeAllListeners('send'); // haven't sent yet, so let's not
|
444 | }
|
445 | }
|
446 |
|
447 | return this;
|
448 | },
|
449 |
|
450 | /**
|
451 | * Iterates over each page of results given a pageable request, calling
|
452 | * the provided callback with each page of data. After all pages have been
|
453 | * retrieved, the callback is called with `null` data.
|
454 | *
|
455 | * @note This operation can generate multiple requests to a service.
|
456 | * @example Iterating over multiple pages of objects in an S3 bucket
|
457 | * var pages = 1;
|
458 | * s3.listObjects().eachPage(function(err, data) {
|
459 | * if (err) return;
|
460 | * console.log("Page", pages++);
|
461 | * console.log(data);
|
462 | * });
|
463 | * @example Iterating over multiple pages with an asynchronous callback
|
464 | * s3.listObjects(params).eachPage(function(err, data, done) {
|
465 | * doSomethingAsyncAndOrExpensive(function() {
|
466 | * // The next page of results isn't fetched until done is called
|
467 | * done();
|
468 | * });
|
469 | * });
|
470 | * @callback callback function(err, data, [doneCallback])
|
471 | * Called with each page of resulting data from the request. If the
|
472 | * optional `doneCallback` is provided in the function, it must be called
|
473 | * when the callback is complete.
|
474 | *
|
475 | * @param err [Error] an error object, if an error occurred.
|
476 | * @param data [Object] a single page of response data. If there is no
|
477 | * more data, this object will be `null`.
|
478 | * @param doneCallback [Function] an optional done callback. If this
|
479 | * argument is defined in the function declaration, it should be called
|
480 | * when the next page is ready to be retrieved. This is useful for
|
481 | * controlling serial pagination across asynchronous operations.
|
482 | * @return [Boolean] if the callback returns `false`, pagination will
|
483 | * stop.
|
484 | *
|
485 | * @see AWS.Request.eachItem
|
486 | * @see AWS.Response.nextPage
|
487 | * @since v1.4.0
|
488 | */
|
489 | eachPage: function eachPage(callback) {
|
490 | // Make all callbacks async-ish
|
491 | callback = AWS.util.fn.makeAsync(callback, 3);
|
492 |
|
493 | function wrappedCallback(response) {
|
494 | callback.call(response, response.error, response.data, function (result) {
|
495 | if (result === false) return;
|
496 |
|
497 | if (response.hasNextPage()) {
|
498 | response.nextPage().on('complete', wrappedCallback).send();
|
499 | } else {
|
500 | callback.call(response, null, null, AWS.util.fn.noop);
|
501 | }
|
502 | });
|
503 | }
|
504 |
|
505 | this.on('complete', wrappedCallback).send();
|
506 | },
|
507 |
|
508 | /**
|
509 | * Enumerates over individual items of a request, paging the responses if
|
510 | * necessary.
|
511 | *
|
512 | * @api experimental
|
513 | * @since v1.4.0
|
514 | */
|
515 | eachItem: function eachItem(callback) {
|
516 | var self = this;
|
517 | function wrappedCallback(err, data) {
|
518 | if (err) return callback(err, null);
|
519 | if (data === null) return callback(null, null);
|
520 |
|
521 | var config = self.service.paginationConfig(self.operation);
|
522 | var resultKey = config.resultKey;
|
523 | if (Array.isArray(resultKey)) resultKey = resultKey[0];
|
524 | var items = jmespath.search(data, resultKey);
|
525 | var continueIteration = true;
|
526 | AWS.util.arrayEach(items, function(item) {
|
527 | continueIteration = callback(null, item);
|
528 | if (continueIteration === false) {
|
529 | return AWS.util.abort;
|
530 | }
|
531 | });
|
532 | return continueIteration;
|
533 | }
|
534 |
|
535 | this.eachPage(wrappedCallback);
|
536 | },
|
537 |
|
538 | /**
|
539 | * @return [Boolean] whether the operation can return multiple pages of
|
540 | * response data.
|
541 | * @see AWS.Response.eachPage
|
542 | * @since v1.4.0
|
543 | */
|
544 | isPageable: function isPageable() {
|
545 | return this.service.paginationConfig(this.operation) ? true : false;
|
546 | },
|
547 |
|
548 | /**
|
549 | * Sends the request and converts the request object into a readable stream
|
550 | * that can be read from or piped into a writable stream.
|
551 | *
|
552 | * @note The data read from a readable stream contains only
|
553 | * the raw HTTP body contents.
|
554 | * @example Manually reading from a stream
|
555 | * request.createReadStream().on('data', function(data) {
|
556 | * console.log("Got data:", data.toString());
|
557 | * });
|
558 | * @example Piping a request body into a file
|
559 | * var out = fs.createWriteStream('/path/to/outfile.jpg');
|
560 | * s3.service.getObject(params).createReadStream().pipe(out);
|
561 | * @return [Stream] the readable stream object that can be piped
|
562 | * or read from (by registering 'data' event listeners).
|
563 | * @!macro nobrowser
|
564 | */
|
565 | createReadStream: function createReadStream() {
|
566 | var streams = AWS.util.stream;
|
567 | var req = this;
|
568 | var stream = null;
|
569 |
|
570 | if (AWS.HttpClient.streamsApiVersion === 2) {
|
571 | stream = new streams.PassThrough();
|
572 | process.nextTick(function() { req.send(); });
|
573 | } else {
|
574 | stream = new streams.Stream();
|
575 | stream.readable = true;
|
576 |
|
577 | stream.sent = false;
|
578 | stream.on('newListener', function(event) {
|
579 | if (!stream.sent && event === 'data') {
|
580 | stream.sent = true;
|
581 | process.nextTick(function() { req.send(); });
|
582 | }
|
583 | });
|
584 | }
|
585 |
|
586 | this.on('error', function(err) {
|
587 | stream.emit('error', err);
|
588 | });
|
589 |
|
590 | this.on('httpHeaders', function streamHeaders(statusCode, headers, resp) {
|
591 | if (statusCode < 300) {
|
592 | req.removeListener('httpData', AWS.EventListeners.Core.HTTP_DATA);
|
593 | req.removeListener('httpError', AWS.EventListeners.Core.HTTP_ERROR);
|
594 | req.on('httpError', function streamHttpError(error) {
|
595 | resp.error = error;
|
596 | resp.error.retryable = false;
|
597 | });
|
598 |
|
599 | var shouldCheckContentLength = false;
|
600 | var expectedLen;
|
601 | if (req.httpRequest.method !== 'HEAD') {
|
602 | expectedLen = parseInt(headers['content-length'], 10);
|
603 | }
|
604 | if (expectedLen !== undefined && !isNaN(expectedLen) && expectedLen >= 0) {
|
605 | shouldCheckContentLength = true;
|
606 | var receivedLen = 0;
|
607 | }
|
608 |
|
609 | var checkContentLengthAndEmit = function checkContentLengthAndEmit() {
|
610 | if (shouldCheckContentLength && receivedLen !== expectedLen) {
|
611 | stream.emit('error', AWS.util.error(
|
612 | new Error('Stream content length mismatch. Received ' +
|
613 | receivedLen + ' of ' + expectedLen + ' bytes.'),
|
614 | { code: 'StreamContentLengthMismatch' }
|
615 | ));
|
616 | } else if (AWS.HttpClient.streamsApiVersion === 2) {
|
617 | stream.end();
|
618 | } else {
|
619 | stream.emit('end');
|
620 | }
|
621 | };
|
622 |
|
623 | var httpStream = resp.httpResponse.createUnbufferedStream();
|
624 |
|
625 | if (AWS.HttpClient.streamsApiVersion === 2) {
|
626 | if (shouldCheckContentLength) {
|
627 | var lengthAccumulator = new streams.PassThrough();
|
628 | lengthAccumulator._write = function(chunk) {
|
629 | if (chunk && chunk.length) {
|
630 | receivedLen += chunk.length;
|
631 | }
|
632 | return streams.PassThrough.prototype._write.apply(this, arguments);
|
633 | };
|
634 |
|
635 | lengthAccumulator.on('end', checkContentLengthAndEmit);
|
636 | stream.on('error', function(err) {
|
637 | shouldCheckContentLength = false;
|
638 | httpStream.unpipe(lengthAccumulator);
|
639 | lengthAccumulator.emit('end');
|
640 | lengthAccumulator.end();
|
641 | });
|
642 | httpStream.pipe(lengthAccumulator).pipe(stream, { end: false });
|
643 | } else {
|
644 | httpStream.pipe(stream);
|
645 | }
|
646 | } else {
|
647 |
|
648 | if (shouldCheckContentLength) {
|
649 | httpStream.on('data', function(arg) {
|
650 | if (arg && arg.length) {
|
651 | receivedLen += arg.length;
|
652 | }
|
653 | });
|
654 | }
|
655 |
|
656 | httpStream.on('data', function(arg) {
|
657 | stream.emit('data', arg);
|
658 | });
|
659 | httpStream.on('end', checkContentLengthAndEmit);
|
660 | }
|
661 |
|
662 | httpStream.on('error', function(err) {
|
663 | shouldCheckContentLength = false;
|
664 | stream.emit('error', err);
|
665 | });
|
666 | }
|
667 | });
|
668 |
|
669 | return stream;
|
670 | },
|
671 |
|
672 | /**
|
673 | * @param [Array,Response] args This should be the response object,
|
674 | * or an array of args to send to the event.
|
675 | * @api private
|
676 | */
|
677 | emitEvent: function emit(eventName, args, done) {
|
678 | if (typeof args === 'function') { done = args; args = null; }
|
679 | if (!done) done = function() { };
|
680 | if (!args) args = this.eventParameters(eventName, this.response);
|
681 |
|
682 | var origEmit = AWS.SequentialExecutor.prototype.emit;
|
683 | origEmit.call(this, eventName, args, function (err) {
|
684 | if (err) this.response.error = err;
|
685 | done.call(this, err);
|
686 | });
|
687 | },
|
688 |
|
689 | /**
|
690 | * @api private
|
691 | */
|
692 | eventParameters: function eventParameters(eventName) {
|
693 | switch (eventName) {
|
694 | case 'restart':
|
695 | case 'validate':
|
696 | case 'sign':
|
697 | case 'build':
|
698 | case 'afterValidate':
|
699 | case 'afterBuild':
|
700 | return [this];
|
701 | case 'error':
|
702 | return [this.response.error, this.response];
|
703 | default:
|
704 | return [this.response];
|
705 | }
|
706 | },
|
707 |
|
708 | /**
|
709 | * @api private
|
710 | */
|
711 | presign: function presign(expires, callback) {
|
712 | if (!callback && typeof expires === 'function') {
|
713 | callback = expires;
|
714 | expires = null;
|
715 | }
|
716 | return new AWS.Signers.Presign().sign(this.toGet(), expires, callback);
|
717 | },
|
718 |
|
719 | /**
|
720 | * @api private
|
721 | */
|
722 | isPresigned: function isPresigned() {
|
723 | return Object.prototype.hasOwnProperty.call(this.httpRequest.headers, 'presigned-expires');
|
724 | },
|
725 |
|
726 | /**
|
727 | * @api private
|
728 | */
|
729 | toUnauthenticated: function toUnauthenticated() {
|
730 | this._unAuthenticated = true;
|
731 | this.removeListener('validate', AWS.EventListeners.Core.VALIDATE_CREDENTIALS);
|
732 | this.removeListener('sign', AWS.EventListeners.Core.SIGN);
|
733 | return this;
|
734 | },
|
735 |
|
736 | /**
|
737 | * @api private
|
738 | */
|
739 | toGet: function toGet() {
|
740 | if (this.service.api.protocol === 'query' ||
|
741 | this.service.api.protocol === 'ec2') {
|
742 | this.removeListener('build', this.buildAsGet);
|
743 | this.addListener('build', this.buildAsGet);
|
744 | }
|
745 | return this;
|
746 | },
|
747 |
|
748 | /**
|
749 | * @api private
|
750 | */
|
751 | buildAsGet: function buildAsGet(request) {
|
752 | request.httpRequest.method = 'GET';
|
753 | request.httpRequest.path = request.service.endpoint.path +
|
754 | '?' + request.httpRequest.body;
|
755 | request.httpRequest.body = '';
|
756 |
|
757 | // don't need these headers on a GET request
|
758 | delete request.httpRequest.headers['Content-Length'];
|
759 | delete request.httpRequest.headers['Content-Type'];
|
760 | },
|
761 |
|
762 | /**
|
763 | * @api private
|
764 | */
|
765 | haltHandlersOnError: function haltHandlersOnError() {
|
766 | this._haltHandlersOnError = true;
|
767 | }
|
768 | });
|
769 |
|
770 | /**
|
771 | * @api private
|
772 | */
|
773 | AWS.Request.addPromisesToClass = function addPromisesToClass(PromiseDependency) {
|
774 | this.prototype.promise = function promise() {
|
775 | var self = this;
|
776 | // append to user agent
|
777 | this.httpRequest.appendToUserAgent('promise');
|
778 | return new PromiseDependency(function(resolve, reject) {
|
779 | self.on('complete', function(resp) {
|
780 | if (resp.error) {
|
781 | reject(resp.error);
|
782 | } else {
|
783 | // define $response property so that it is not enumberable
|
784 | // this prevents circular reference errors when stringifying the JSON object
|
785 | resolve(Object.defineProperty(
|
786 | resp.data || {},
|
787 | '$response',
|
788 | {value: resp}
|
789 | ));
|
790 | }
|
791 | });
|
792 | self.runTo();
|
793 | });
|
794 | };
|
795 | };
|
796 |
|
797 | /**
|
798 | * @api private
|
799 | */
|
800 | AWS.Request.deletePromisesFromClass = function deletePromisesFromClass() {
|
801 | delete this.prototype.promise;
|
802 | };
|
803 |
|
804 | AWS.util.addPromises(AWS.Request);
|
805 |
|
806 | AWS.util.mixin(AWS.Request, AWS.SequentialExecutor);
|