807 lines
27 KiB
JavaScript
807 lines
27 KiB
JavaScript
var AWS = require('./core');
|
|
var AcceptorStateMachine = require('./state_machine');
|
|
var inherit = AWS.util.inherit;
|
|
var domain = AWS.util.domain;
|
|
var jmespath = require('jmespath');
|
|
|
|
/**
|
|
* @api private
|
|
*/
|
|
var hardErrorStates = {success: 1, error: 1, complete: 1};
|
|
|
|
function isTerminalState(machine) {
|
|
return Object.prototype.hasOwnProperty.call(hardErrorStates, machine._asm.currentState);
|
|
}
|
|
|
|
var fsm = new AcceptorStateMachine();
|
|
fsm.setupStates = function() {
|
|
var transition = function(_, done) {
|
|
var self = this;
|
|
self._haltHandlersOnError = false;
|
|
|
|
self.emit(self._asm.currentState, function(err) {
|
|
if (err) {
|
|
if (isTerminalState(self)) {
|
|
if (domain && self.domain instanceof domain.Domain) {
|
|
err.domainEmitter = self;
|
|
err.domain = self.domain;
|
|
err.domainThrown = false;
|
|
self.domain.emit('error', err);
|
|
} else {
|
|
throw err;
|
|
}
|
|
} else {
|
|
self.response.error = err;
|
|
done(err);
|
|
}
|
|
} else {
|
|
done(self.response.error);
|
|
}
|
|
});
|
|
|
|
};
|
|
|
|
this.addState('validate', 'build', 'error', transition);
|
|
this.addState('build', 'afterBuild', 'restart', transition);
|
|
this.addState('afterBuild', 'sign', 'restart', transition);
|
|
this.addState('sign', 'send', 'retry', transition);
|
|
this.addState('retry', 'afterRetry', 'afterRetry', transition);
|
|
this.addState('afterRetry', 'sign', 'error', transition);
|
|
this.addState('send', 'validateResponse', 'retry', transition);
|
|
this.addState('validateResponse', 'extractData', 'extractError', transition);
|
|
this.addState('extractError', 'extractData', 'retry', transition);
|
|
this.addState('extractData', 'success', 'retry', transition);
|
|
this.addState('restart', 'build', 'error', transition);
|
|
this.addState('success', 'complete', 'complete', transition);
|
|
this.addState('error', 'complete', 'complete', transition);
|
|
this.addState('complete', null, null, transition);
|
|
};
|
|
fsm.setupStates();
|
|
|
|
/**
|
|
* ## Asynchronous Requests
|
|
*
|
|
* All requests made through the SDK are asynchronous and use a
|
|
* callback interface. Each service method that kicks off a request
|
|
* returns an `AWS.Request` object that you can use to register
|
|
* callbacks.
|
|
*
|
|
* For example, the following service method returns the request
|
|
* object as "request", which can be used to register callbacks:
|
|
*
|
|
* ```javascript
|
|
* // request is an AWS.Request object
|
|
* var request = ec2.describeInstances();
|
|
*
|
|
* // register callbacks on request to retrieve response data
|
|
* request.on('success', function(response) {
|
|
* console.log(response.data);
|
|
* });
|
|
* ```
|
|
*
|
|
* When a request is ready to be sent, the {send} method should
|
|
* be called:
|
|
*
|
|
* ```javascript
|
|
* request.send();
|
|
* ```
|
|
*
|
|
* Since registered callbacks may or may not be idempotent, requests should only
|
|
* be sent once. To perform the same operation multiple times, you will need to
|
|
* create multiple request objects, each with its own registered callbacks.
|
|
*
|
|
* ## Removing Default Listeners for Events
|
|
*
|
|
* Request objects are built with default listeners for the various events,
|
|
* depending on the service type. In some cases, you may want to remove
|
|
* some built-in listeners to customize behaviour. Doing this requires
|
|
* access to the built-in listener functions, which are exposed through
|
|
* the {AWS.EventListeners.Core} namespace. For instance, you may
|
|
* want to customize the HTTP handler used when sending a request. In this
|
|
* case, you can remove the built-in listener associated with the 'send'
|
|
* event, the {AWS.EventListeners.Core.SEND} listener and add your own.
|
|
*
|
|
* ## Multiple Callbacks and Chaining
|
|
*
|
|
* You can register multiple callbacks on any request object. The
|
|
* callbacks can be registered for different events, or all for the
|
|
* same event. In addition, you can chain callback registration, for
|
|
* example:
|
|
*
|
|
* ```javascript
|
|
* request.
|
|
* on('success', function(response) {
|
|
* console.log("Success!");
|
|
* }).
|
|
* on('error', function(response) {
|
|
* console.log("Error!");
|
|
* }).
|
|
* on('complete', function(response) {
|
|
* console.log("Always!");
|
|
* }).
|
|
* send();
|
|
* ```
|
|
*
|
|
* The above example will print either "Success! Always!", or "Error! Always!",
|
|
* depending on whether the request succeeded or not.
|
|
*
|
|
* @!attribute httpRequest
|
|
* @readonly
|
|
* @!group HTTP Properties
|
|
* @return [AWS.HttpRequest] the raw HTTP request object
|
|
* containing request headers and body information
|
|
* sent by the service.
|
|
*
|
|
* @!attribute startTime
|
|
* @readonly
|
|
* @!group Operation Properties
|
|
* @return [Date] the time that the request started
|
|
*
|
|
* @!group Request Building Events
|
|
*
|
|
* @!event validate(request)
|
|
* Triggered when a request is being validated. Listeners
|
|
* should throw an error if the request should not be sent.
|
|
* @param request [Request] the request object being sent
|
|
* @see AWS.EventListeners.Core.VALIDATE_CREDENTIALS
|
|
* @see AWS.EventListeners.Core.VALIDATE_REGION
|
|
* @example Ensuring that a certain parameter is set before sending a request
|
|
* var req = s3.putObject(params);
|
|
* req.on('validate', function() {
|
|
* if (!req.params.Body.match(/^Hello\s/)) {
|
|
* throw new Error('Body must start with "Hello "');
|
|
* }
|
|
* });
|
|
* req.send(function(err, data) { ... });
|
|
*
|
|
* @!event build(request)
|
|
* Triggered when the request payload is being built. Listeners
|
|
* should fill the necessary information to send the request
|
|
* over HTTP.
|
|
* @param (see AWS.Request~validate)
|
|
* @example Add a custom HTTP header to a request
|
|
* var req = s3.putObject(params);
|
|
* req.on('build', function() {
|
|
* req.httpRequest.headers['Custom-Header'] = 'value';
|
|
* });
|
|
* req.send(function(err, data) { ... });
|
|
*
|
|
* @!event sign(request)
|
|
* Triggered when the request is being signed. Listeners should
|
|
* add the correct authentication headers and/or adjust the body,
|
|
* depending on the authentication mechanism being used.
|
|
* @param (see AWS.Request~validate)
|
|
*
|
|
* @!group Request Sending Events
|
|
*
|
|
* @!event send(response)
|
|
* Triggered when the request is ready to be sent. Listeners
|
|
* should call the underlying transport layer to initiate
|
|
* the sending of the request.
|
|
* @param response [Response] the response object
|
|
* @context [Request] the request object that was sent
|
|
* @see AWS.EventListeners.Core.SEND
|
|
*
|
|
* @!event retry(response)
|
|
* Triggered when a request failed and might need to be retried or redirected.
|
|
* If the response is retryable, the listener should set the
|
|
* `response.error.retryable` property to `true`, and optionally set
|
|
* `response.error.retryDelay` to the millisecond delay for the next attempt.
|
|
* In the case of a redirect, `response.error.redirect` should be set to
|
|
* `true` with `retryDelay` set to an optional delay on the next request.
|
|
*
|
|
* If a listener decides that a request should not be retried,
|
|
* it should set both `retryable` and `redirect` to false.
|
|
*
|
|
* Note that a retryable error will be retried at most
|
|
* {AWS.Config.maxRetries} times (based on the service object's config).
|
|
* Similarly, a request that is redirected will only redirect at most
|
|
* {AWS.Config.maxRedirects} times.
|
|
*
|
|
* @param (see AWS.Request~send)
|
|
* @context (see AWS.Request~send)
|
|
* @example Adding a custom retry for a 404 response
|
|
* request.on('retry', function(response) {
|
|
* // this resource is not yet available, wait 10 seconds to get it again
|
|
* if (response.httpResponse.statusCode === 404 && response.error) {
|
|
* response.error.retryable = true; // retry this error
|
|
* response.error.retryDelay = 10000; // wait 10 seconds
|
|
* }
|
|
* });
|
|
*
|
|
* @!group Data Parsing Events
|
|
*
|
|
* @!event extractError(response)
|
|
* Triggered on all non-2xx requests so that listeners can extract
|
|
* error details from the response body. Listeners to this event
|
|
* should set the `response.error` property.
|
|
* @param (see AWS.Request~send)
|
|
* @context (see AWS.Request~send)
|
|
*
|
|
* @!event extractData(response)
|
|
* Triggered in successful requests to allow listeners to
|
|
* de-serialize the response body into `response.data`.
|
|
* @param (see AWS.Request~send)
|
|
* @context (see AWS.Request~send)
|
|
*
|
|
* @!group Completion Events
|
|
*
|
|
* @!event success(response)
|
|
* Triggered when the request completed successfully.
|
|
* `response.data` will contain the response data and
|
|
* `response.error` will be null.
|
|
* @param (see AWS.Request~send)
|
|
* @context (see AWS.Request~send)
|
|
*
|
|
* @!event error(error, response)
|
|
* Triggered when an error occurs at any point during the
|
|
* request. `response.error` will contain details about the error
|
|
* that occurred. `response.data` will be null.
|
|
* @param error [Error] the error object containing details about
|
|
* the error that occurred.
|
|
* @param (see AWS.Request~send)
|
|
* @context (see AWS.Request~send)
|
|
*
|
|
* @!event complete(response)
|
|
* Triggered whenever a request cycle completes. `response.error`
|
|
* should be checked, since the request may have failed.
|
|
* @param (see AWS.Request~send)
|
|
* @context (see AWS.Request~send)
|
|
*
|
|
* @!group HTTP Events
|
|
*
|
|
* @!event httpHeaders(statusCode, headers, response, statusMessage)
|
|
* Triggered when headers are sent by the remote server
|
|
* @param statusCode [Integer] the HTTP response code
|
|
* @param headers [map<String,String>] the response headers
|
|
* @param (see AWS.Request~send)
|
|
* @param statusMessage [String] A status message corresponding to the HTTP
|
|
* response code
|
|
* @context (see AWS.Request~send)
|
|
*
|
|
* @!event httpData(chunk, response)
|
|
* Triggered when data is sent by the remote server
|
|
* @param chunk [Buffer] the buffer data containing the next data chunk
|
|
* from the server
|
|
* @param (see AWS.Request~send)
|
|
* @context (see AWS.Request~send)
|
|
* @see AWS.EventListeners.Core.HTTP_DATA
|
|
*
|
|
* @!event httpUploadProgress(progress, response)
|
|
* Triggered when the HTTP request has uploaded more data
|
|
* @param progress [map] An object containing the `loaded` and `total` bytes
|
|
* of the request.
|
|
* @param (see AWS.Request~send)
|
|
* @context (see AWS.Request~send)
|
|
* @note This event will not be emitted in Node.js 0.8.x.
|
|
*
|
|
* @!event httpDownloadProgress(progress, response)
|
|
* Triggered when the HTTP request has downloaded more data
|
|
* @param progress [map] An object containing the `loaded` and `total` bytes
|
|
* of the request.
|
|
* @param (see AWS.Request~send)
|
|
* @context (see AWS.Request~send)
|
|
* @note This event will not be emitted in Node.js 0.8.x.
|
|
*
|
|
* @!event httpError(error, response)
|
|
* Triggered when the HTTP request failed
|
|
* @param error [Error] the error object that was thrown
|
|
* @param (see AWS.Request~send)
|
|
* @context (see AWS.Request~send)
|
|
*
|
|
* @!event httpDone(response)
|
|
* Triggered when the server is finished sending data
|
|
* @param (see AWS.Request~send)
|
|
* @context (see AWS.Request~send)
|
|
*
|
|
* @see AWS.Response
|
|
*/
|
|
AWS.Request = inherit({
|
|
|
|
/**
|
|
* Creates a request for an operation on a given service with
|
|
* a set of input parameters.
|
|
*
|
|
* @param service [AWS.Service] the service to perform the operation on
|
|
* @param operation [String] the operation to perform on the service
|
|
* @param params [Object] parameters to send to the operation.
|
|
* See the operation's documentation for the format of the
|
|
* parameters.
|
|
*/
|
|
constructor: function Request(service, operation, params) {
|
|
var endpoint = service.endpoint;
|
|
var region = service.config.region;
|
|
var customUserAgent = service.config.customUserAgent;
|
|
|
|
// global endpoints sign as us-east-1
|
|
if (service.isGlobalEndpoint) region = 'us-east-1';
|
|
|
|
this.domain = domain && domain.active;
|
|
this.service = service;
|
|
this.operation = operation;
|
|
this.params = params || {};
|
|
this.httpRequest = new AWS.HttpRequest(endpoint, region);
|
|
this.httpRequest.appendToUserAgent(customUserAgent);
|
|
this.startTime = service.getSkewCorrectedDate();
|
|
|
|
this.response = new AWS.Response(this);
|
|
this._asm = new AcceptorStateMachine(fsm.states, 'validate');
|
|
this._haltHandlersOnError = false;
|
|
|
|
AWS.SequentialExecutor.call(this);
|
|
this.emit = this.emitEvent;
|
|
},
|
|
|
|
/**
|
|
* @!group Sending a Request
|
|
*/
|
|
|
|
/**
|
|
* @overload send(callback = null)
|
|
* Sends the request object.
|
|
*
|
|
* @callback callback function(err, data)
|
|
* If a callback is supplied, it is called when a response is returned
|
|
* from the service.
|
|
* @context [AWS.Request] the request object being sent.
|
|
* @param err [Error] the error object returned from the request.
|
|
* Set to `null` if the request is successful.
|
|
* @param data [Object] the de-serialized data returned from
|
|
* the request. Set to `null` if a request error occurs.
|
|
* @example Sending a request with a callback
|
|
* request = s3.putObject({Bucket: 'bucket', Key: 'key'});
|
|
* request.send(function(err, data) { console.log(err, data); });
|
|
* @example Sending a request with no callback (using event handlers)
|
|
* request = s3.putObject({Bucket: 'bucket', Key: 'key'});
|
|
* request.on('complete', function(response) { ... }); // register a callback
|
|
* request.send();
|
|
*/
|
|
send: function send(callback) {
|
|
if (callback) {
|
|
// append to user agent
|
|
this.httpRequest.appendToUserAgent('callback');
|
|
this.on('complete', function (resp) {
|
|
callback.call(resp, resp.error, resp.data);
|
|
});
|
|
}
|
|
this.runTo();
|
|
|
|
return this.response;
|
|
},
|
|
|
|
/**
|
|
* @!method promise()
|
|
* Sends the request and returns a 'thenable' promise.
|
|
*
|
|
* Two callbacks can be provided to the `then` method on the returned promise.
|
|
* The first callback will be called if the promise is fulfilled, and the second
|
|
* callback will be called if the promise is rejected.
|
|
* @callback fulfilledCallback function(data)
|
|
* Called if the promise is fulfilled.
|
|
* @param data [Object] the de-serialized data returned from the request.
|
|
* @callback rejectedCallback function(error)
|
|
* Called if the promise is rejected.
|
|
* @param error [Error] the error object returned from the request.
|
|
* @return [Promise] A promise that represents the state of the request.
|
|
* @example Sending a request using promises.
|
|
* var request = s3.putObject({Bucket: 'bucket', Key: 'key'});
|
|
* var result = request.promise();
|
|
* result.then(function(data) { ... }, function(error) { ... });
|
|
*/
|
|
|
|
/**
|
|
* @api private
|
|
*/
|
|
build: function build(callback) {
|
|
return this.runTo('send', callback);
|
|
},
|
|
|
|
/**
|
|
* @api private
|
|
*/
|
|
runTo: function runTo(state, done) {
|
|
this._asm.runTo(state, done, this);
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Aborts a request, emitting the error and complete events.
|
|
*
|
|
* @!macro nobrowser
|
|
* @example Aborting a request after sending
|
|
* var params = {
|
|
* Bucket: 'bucket', Key: 'key',
|
|
* Body: new Buffer(1024 * 1024 * 5) // 5MB payload
|
|
* };
|
|
* var request = s3.putObject(params);
|
|
* request.send(function (err, data) {
|
|
* if (err) console.log("Error:", err.code, err.message);
|
|
* else console.log(data);
|
|
* });
|
|
*
|
|
* // abort request in 1 second
|
|
* setTimeout(request.abort.bind(request), 1000);
|
|
*
|
|
* // prints "Error: RequestAbortedError Request aborted by user"
|
|
* @return [AWS.Request] the same request object, for chaining.
|
|
* @since v1.4.0
|
|
*/
|
|
abort: function abort() {
|
|
this.removeAllListeners('validateResponse');
|
|
this.removeAllListeners('extractError');
|
|
this.on('validateResponse', function addAbortedError(resp) {
|
|
resp.error = AWS.util.error(new Error('Request aborted by user'), {
|
|
code: 'RequestAbortedError', retryable: false
|
|
});
|
|
});
|
|
|
|
if (this.httpRequest.stream && !this.httpRequest.stream.didCallback) { // abort HTTP stream
|
|
this.httpRequest.stream.abort();
|
|
if (this.httpRequest._abortCallback) {
|
|
this.httpRequest._abortCallback();
|
|
} else {
|
|
this.removeAllListeners('send'); // haven't sent yet, so let's not
|
|
}
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Iterates over each page of results given a pageable request, calling
|
|
* the provided callback with each page of data. After all pages have been
|
|
* retrieved, the callback is called with `null` data.
|
|
*
|
|
* @note This operation can generate multiple requests to a service.
|
|
* @example Iterating over multiple pages of objects in an S3 bucket
|
|
* var pages = 1;
|
|
* s3.listObjects().eachPage(function(err, data) {
|
|
* if (err) return;
|
|
* console.log("Page", pages++);
|
|
* console.log(data);
|
|
* });
|
|
* @example Iterating over multiple pages with an asynchronous callback
|
|
* s3.listObjects(params).eachPage(function(err, data, done) {
|
|
* doSomethingAsyncAndOrExpensive(function() {
|
|
* // The next page of results isn't fetched until done is called
|
|
* done();
|
|
* });
|
|
* });
|
|
* @callback callback function(err, data, [doneCallback])
|
|
* Called with each page of resulting data from the request. If the
|
|
* optional `doneCallback` is provided in the function, it must be called
|
|
* when the callback is complete.
|
|
*
|
|
* @param err [Error] an error object, if an error occurred.
|
|
* @param data [Object] a single page of response data. If there is no
|
|
* more data, this object will be `null`.
|
|
* @param doneCallback [Function] an optional done callback. If this
|
|
* argument is defined in the function declaration, it should be called
|
|
* when the next page is ready to be retrieved. This is useful for
|
|
* controlling serial pagination across asynchronous operations.
|
|
* @return [Boolean] if the callback returns `false`, pagination will
|
|
* stop.
|
|
*
|
|
* @see AWS.Request.eachItem
|
|
* @see AWS.Response.nextPage
|
|
* @since v1.4.0
|
|
*/
|
|
eachPage: function eachPage(callback) {
|
|
// Make all callbacks async-ish
|
|
callback = AWS.util.fn.makeAsync(callback, 3);
|
|
|
|
function wrappedCallback(response) {
|
|
callback.call(response, response.error, response.data, function (result) {
|
|
if (result === false) return;
|
|
|
|
if (response.hasNextPage()) {
|
|
response.nextPage().on('complete', wrappedCallback).send();
|
|
} else {
|
|
callback.call(response, null, null, AWS.util.fn.noop);
|
|
}
|
|
});
|
|
}
|
|
|
|
this.on('complete', wrappedCallback).send();
|
|
},
|
|
|
|
/**
|
|
* Enumerates over individual items of a request, paging the responses if
|
|
* necessary.
|
|
*
|
|
* @api experimental
|
|
* @since v1.4.0
|
|
*/
|
|
eachItem: function eachItem(callback) {
|
|
var self = this;
|
|
function wrappedCallback(err, data) {
|
|
if (err) return callback(err, null);
|
|
if (data === null) return callback(null, null);
|
|
|
|
var config = self.service.paginationConfig(self.operation);
|
|
var resultKey = config.resultKey;
|
|
if (Array.isArray(resultKey)) resultKey = resultKey[0];
|
|
var items = jmespath.search(data, resultKey);
|
|
var continueIteration = true;
|
|
AWS.util.arrayEach(items, function(item) {
|
|
continueIteration = callback(null, item);
|
|
if (continueIteration === false) {
|
|
return AWS.util.abort;
|
|
}
|
|
});
|
|
return continueIteration;
|
|
}
|
|
|
|
this.eachPage(wrappedCallback);
|
|
},
|
|
|
|
/**
|
|
* @return [Boolean] whether the operation can return multiple pages of
|
|
* response data.
|
|
* @see AWS.Response.eachPage
|
|
* @since v1.4.0
|
|
*/
|
|
isPageable: function isPageable() {
|
|
return this.service.paginationConfig(this.operation) ? true : false;
|
|
},
|
|
|
|
/**
|
|
* Sends the request and converts the request object into a readable stream
|
|
* that can be read from or piped into a writable stream.
|
|
*
|
|
* @note The data read from a readable stream contains only
|
|
* the raw HTTP body contents.
|
|
* @example Manually reading from a stream
|
|
* request.createReadStream().on('data', function(data) {
|
|
* console.log("Got data:", data.toString());
|
|
* });
|
|
* @example Piping a request body into a file
|
|
* var out = fs.createWriteStream('/path/to/outfile.jpg');
|
|
* s3.service.getObject(params).createReadStream().pipe(out);
|
|
* @return [Stream] the readable stream object that can be piped
|
|
* or read from (by registering 'data' event listeners).
|
|
* @!macro nobrowser
|
|
*/
|
|
createReadStream: function createReadStream() {
|
|
var streams = AWS.util.stream;
|
|
var req = this;
|
|
var stream = null;
|
|
|
|
if (AWS.HttpClient.streamsApiVersion === 2) {
|
|
stream = new streams.PassThrough();
|
|
process.nextTick(function() { req.send(); });
|
|
} else {
|
|
stream = new streams.Stream();
|
|
stream.readable = true;
|
|
|
|
stream.sent = false;
|
|
stream.on('newListener', function(event) {
|
|
if (!stream.sent && event === 'data') {
|
|
stream.sent = true;
|
|
process.nextTick(function() { req.send(); });
|
|
}
|
|
});
|
|
}
|
|
|
|
this.on('error', function(err) {
|
|
stream.emit('error', err);
|
|
});
|
|
|
|
this.on('httpHeaders', function streamHeaders(statusCode, headers, resp) {
|
|
if (statusCode < 300) {
|
|
req.removeListener('httpData', AWS.EventListeners.Core.HTTP_DATA);
|
|
req.removeListener('httpError', AWS.EventListeners.Core.HTTP_ERROR);
|
|
req.on('httpError', function streamHttpError(error) {
|
|
resp.error = error;
|
|
resp.error.retryable = false;
|
|
});
|
|
|
|
var shouldCheckContentLength = false;
|
|
var expectedLen;
|
|
if (req.httpRequest.method !== 'HEAD') {
|
|
expectedLen = parseInt(headers['content-length'], 10);
|
|
}
|
|
if (expectedLen !== undefined && !isNaN(expectedLen) && expectedLen >= 0) {
|
|
shouldCheckContentLength = true;
|
|
var receivedLen = 0;
|
|
}
|
|
|
|
var checkContentLengthAndEmit = function checkContentLengthAndEmit() {
|
|
if (shouldCheckContentLength && receivedLen !== expectedLen) {
|
|
stream.emit('error', AWS.util.error(
|
|
new Error('Stream content length mismatch. Received ' +
|
|
receivedLen + ' of ' + expectedLen + ' bytes.'),
|
|
{ code: 'StreamContentLengthMismatch' }
|
|
));
|
|
} else if (AWS.HttpClient.streamsApiVersion === 2) {
|
|
stream.end();
|
|
} else {
|
|
stream.emit('end');
|
|
}
|
|
};
|
|
|
|
var httpStream = resp.httpResponse.createUnbufferedStream();
|
|
|
|
if (AWS.HttpClient.streamsApiVersion === 2) {
|
|
if (shouldCheckContentLength) {
|
|
var lengthAccumulator = new streams.PassThrough();
|
|
lengthAccumulator._write = function(chunk) {
|
|
if (chunk && chunk.length) {
|
|
receivedLen += chunk.length;
|
|
}
|
|
return streams.PassThrough.prototype._write.apply(this, arguments);
|
|
};
|
|
|
|
lengthAccumulator.on('end', checkContentLengthAndEmit);
|
|
stream.on('error', function(err) {
|
|
shouldCheckContentLength = false;
|
|
httpStream.unpipe(lengthAccumulator);
|
|
lengthAccumulator.emit('end');
|
|
lengthAccumulator.end();
|
|
});
|
|
httpStream.pipe(lengthAccumulator).pipe(stream, { end: false });
|
|
} else {
|
|
httpStream.pipe(stream);
|
|
}
|
|
} else {
|
|
|
|
if (shouldCheckContentLength) {
|
|
httpStream.on('data', function(arg) {
|
|
if (arg && arg.length) {
|
|
receivedLen += arg.length;
|
|
}
|
|
});
|
|
}
|
|
|
|
httpStream.on('data', function(arg) {
|
|
stream.emit('data', arg);
|
|
});
|
|
httpStream.on('end', checkContentLengthAndEmit);
|
|
}
|
|
|
|
httpStream.on('error', function(err) {
|
|
shouldCheckContentLength = false;
|
|
stream.emit('error', err);
|
|
});
|
|
}
|
|
});
|
|
|
|
return stream;
|
|
},
|
|
|
|
/**
|
|
* @param [Array,Response] args This should be the response object,
|
|
* or an array of args to send to the event.
|
|
* @api private
|
|
*/
|
|
emitEvent: function emit(eventName, args, done) {
|
|
if (typeof args === 'function') { done = args; args = null; }
|
|
if (!done) done = function() { };
|
|
if (!args) args = this.eventParameters(eventName, this.response);
|
|
|
|
var origEmit = AWS.SequentialExecutor.prototype.emit;
|
|
origEmit.call(this, eventName, args, function (err) {
|
|
if (err) this.response.error = err;
|
|
done.call(this, err);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* @api private
|
|
*/
|
|
eventParameters: function eventParameters(eventName) {
|
|
switch (eventName) {
|
|
case 'restart':
|
|
case 'validate':
|
|
case 'sign':
|
|
case 'build':
|
|
case 'afterValidate':
|
|
case 'afterBuild':
|
|
return [this];
|
|
case 'error':
|
|
return [this.response.error, this.response];
|
|
default:
|
|
return [this.response];
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @api private
|
|
*/
|
|
presign: function presign(expires, callback) {
|
|
if (!callback && typeof expires === 'function') {
|
|
callback = expires;
|
|
expires = null;
|
|
}
|
|
return new AWS.Signers.Presign().sign(this.toGet(), expires, callback);
|
|
},
|
|
|
|
/**
|
|
* @api private
|
|
*/
|
|
isPresigned: function isPresigned() {
|
|
return Object.prototype.hasOwnProperty.call(this.httpRequest.headers, 'presigned-expires');
|
|
},
|
|
|
|
/**
|
|
* @api private
|
|
*/
|
|
toUnauthenticated: function toUnauthenticated() {
|
|
this._unAuthenticated = true;
|
|
this.removeListener('validate', AWS.EventListeners.Core.VALIDATE_CREDENTIALS);
|
|
this.removeListener('sign', AWS.EventListeners.Core.SIGN);
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* @api private
|
|
*/
|
|
toGet: function toGet() {
|
|
if (this.service.api.protocol === 'query' ||
|
|
this.service.api.protocol === 'ec2') {
|
|
this.removeListener('build', this.buildAsGet);
|
|
this.addListener('build', this.buildAsGet);
|
|
}
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* @api private
|
|
*/
|
|
buildAsGet: function buildAsGet(request) {
|
|
request.httpRequest.method = 'GET';
|
|
request.httpRequest.path = request.service.endpoint.path +
|
|
'?' + request.httpRequest.body;
|
|
request.httpRequest.body = '';
|
|
|
|
// don't need these headers on a GET request
|
|
delete request.httpRequest.headers['Content-Length'];
|
|
delete request.httpRequest.headers['Content-Type'];
|
|
},
|
|
|
|
/**
|
|
* @api private
|
|
*/
|
|
haltHandlersOnError: function haltHandlersOnError() {
|
|
this._haltHandlersOnError = true;
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @api private
|
|
*/
|
|
AWS.Request.addPromisesToClass = function addPromisesToClass(PromiseDependency) {
|
|
this.prototype.promise = function promise() {
|
|
var self = this;
|
|
// append to user agent
|
|
this.httpRequest.appendToUserAgent('promise');
|
|
return new PromiseDependency(function(resolve, reject) {
|
|
self.on('complete', function(resp) {
|
|
if (resp.error) {
|
|
reject(resp.error);
|
|
} else {
|
|
// define $response property so that it is not enumberable
|
|
// this prevents circular reference errors when stringifying the JSON object
|
|
resolve(Object.defineProperty(
|
|
resp.data || {},
|
|
'$response',
|
|
{value: resp}
|
|
));
|
|
}
|
|
});
|
|
self.runTo();
|
|
});
|
|
};
|
|
};
|
|
|
|
/**
|
|
* @api private
|
|
*/
|
|
AWS.Request.deletePromisesFromClass = function deletePromisesFromClass() {
|
|
delete this.prototype.promise;
|
|
};
|
|
|
|
AWS.util.addPromises(AWS.Request);
|
|
|
|
AWS.util.mixin(AWS.Request, AWS.SequentialExecutor);
|