201 lines
6.1 KiB
JavaScript
201 lines
6.1 KiB
JavaScript
|
var AWS = require('../core');
|
||
|
var Stream = AWS.util.stream.Stream;
|
||
|
var TransformStream = AWS.util.stream.Transform;
|
||
|
var ReadableStream = AWS.util.stream.Readable;
|
||
|
require('../http');
|
||
|
|
||
|
/**
|
||
|
* @api private
|
||
|
*/
|
||
|
AWS.NodeHttpClient = AWS.util.inherit({
|
||
|
handleRequest: function handleRequest(httpRequest, httpOptions, callback, errCallback) {
|
||
|
var self = this;
|
||
|
var endpoint = httpRequest.endpoint;
|
||
|
var pathPrefix = '';
|
||
|
if (!httpOptions) httpOptions = {};
|
||
|
if (httpOptions.proxy) {
|
||
|
pathPrefix = endpoint.protocol + '//' + endpoint.hostname;
|
||
|
if (endpoint.port !== 80 && endpoint.port !== 443) {
|
||
|
pathPrefix += ':' + endpoint.port;
|
||
|
}
|
||
|
endpoint = new AWS.Endpoint(httpOptions.proxy);
|
||
|
}
|
||
|
|
||
|
var useSSL = endpoint.protocol === 'https:';
|
||
|
var http = useSSL ? require('https') : require('http');
|
||
|
var options = {
|
||
|
host: endpoint.hostname,
|
||
|
port: endpoint.port,
|
||
|
method: httpRequest.method,
|
||
|
headers: httpRequest.headers,
|
||
|
path: pathPrefix + httpRequest.path
|
||
|
};
|
||
|
|
||
|
if (useSSL && !httpOptions.agent) {
|
||
|
options.agent = this.sslAgent();
|
||
|
}
|
||
|
|
||
|
AWS.util.update(options, httpOptions);
|
||
|
delete options.proxy; // proxy isn't an HTTP option
|
||
|
delete options.timeout; // timeout isn't an HTTP option
|
||
|
|
||
|
var stream = http.request(options, function (httpResp) {
|
||
|
if (stream.didCallback) return;
|
||
|
|
||
|
callback(httpResp);
|
||
|
httpResp.emit(
|
||
|
'headers',
|
||
|
httpResp.statusCode,
|
||
|
httpResp.headers,
|
||
|
httpResp.statusMessage
|
||
|
);
|
||
|
});
|
||
|
httpRequest.stream = stream; // attach stream to httpRequest
|
||
|
stream.didCallback = false;
|
||
|
|
||
|
// connection timeout support
|
||
|
if (httpOptions.connectTimeout) {
|
||
|
var connectTimeoutId;
|
||
|
stream.on('socket', function(socket) {
|
||
|
if (socket.connecting) {
|
||
|
connectTimeoutId = setTimeout(function connectTimeout() {
|
||
|
if (stream.didCallback) return; stream.didCallback = true;
|
||
|
|
||
|
stream.abort();
|
||
|
errCallback(AWS.util.error(
|
||
|
new Error('Socket timed out without establishing a connection'),
|
||
|
{code: 'TimeoutError'}
|
||
|
));
|
||
|
}, httpOptions.connectTimeout);
|
||
|
socket.on('connect', function() {
|
||
|
clearTimeout(connectTimeoutId);
|
||
|
connectTimeoutId = null;
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// timeout support
|
||
|
stream.setTimeout(httpOptions.timeout || 0, function() {
|
||
|
if (stream.didCallback) return; stream.didCallback = true;
|
||
|
|
||
|
var msg = 'Connection timed out after ' + httpOptions.timeout + 'ms';
|
||
|
errCallback(AWS.util.error(new Error(msg), {code: 'TimeoutError'}));
|
||
|
stream.abort();
|
||
|
});
|
||
|
|
||
|
stream.on('error', function() {
|
||
|
if (connectTimeoutId) {
|
||
|
clearTimeout(connectTimeoutId);
|
||
|
connectTimeoutId = null;
|
||
|
}
|
||
|
if (stream.didCallback) return; stream.didCallback = true;
|
||
|
errCallback.apply(stream, arguments);
|
||
|
});
|
||
|
|
||
|
var expect = httpRequest.headers.Expect || httpRequest.headers.expect;
|
||
|
if (expect === '100-continue') {
|
||
|
stream.on('continue', function() {
|
||
|
self.writeBody(stream, httpRequest);
|
||
|
});
|
||
|
} else {
|
||
|
this.writeBody(stream, httpRequest);
|
||
|
}
|
||
|
|
||
|
return stream;
|
||
|
},
|
||
|
|
||
|
writeBody: function writeBody(stream, httpRequest) {
|
||
|
var body = httpRequest.body;
|
||
|
var totalBytes = parseInt(httpRequest.headers['Content-Length'], 10);
|
||
|
|
||
|
if (body instanceof Stream) {
|
||
|
// For progress support of streaming content -
|
||
|
// pipe the data through a transform stream to emit 'sendProgress' events
|
||
|
var progressStream = this.progressStream(stream, totalBytes);
|
||
|
if (progressStream) {
|
||
|
body.pipe(progressStream).pipe(stream);
|
||
|
} else {
|
||
|
body.pipe(stream);
|
||
|
}
|
||
|
} else if (body) {
|
||
|
// The provided body is a buffer/string and is already fully available in memory -
|
||
|
// For performance it's best to send it as a whole by calling stream.end(body),
|
||
|
// Callers expect a 'sendProgress' event which is best emitted once
|
||
|
// the http request stream has been fully written and all data flushed.
|
||
|
// The use of totalBytes is important over body.length for strings where
|
||
|
// length is char length and not byte length.
|
||
|
stream.once('finish', function() {
|
||
|
stream.emit('sendProgress', {
|
||
|
loaded: totalBytes,
|
||
|
total: totalBytes
|
||
|
});
|
||
|
});
|
||
|
stream.end(body);
|
||
|
} else {
|
||
|
// no request body
|
||
|
stream.end();
|
||
|
}
|
||
|
},
|
||
|
|
||
|
sslAgent: function sslAgent() {
|
||
|
var https = require('https');
|
||
|
|
||
|
if (!AWS.NodeHttpClient.sslAgent) {
|
||
|
AWS.NodeHttpClient.sslAgent = new https.Agent({rejectUnauthorized: true});
|
||
|
AWS.NodeHttpClient.sslAgent.setMaxListeners(0);
|
||
|
|
||
|
// delegate maxSockets to globalAgent, set a default limit of 50 if current value is Infinity.
|
||
|
// Users can bypass this default by supplying their own Agent as part of SDK configuration.
|
||
|
Object.defineProperty(AWS.NodeHttpClient.sslAgent, 'maxSockets', {
|
||
|
enumerable: true,
|
||
|
get: function() {
|
||
|
var defaultMaxSockets = 50;
|
||
|
var globalAgent = https.globalAgent;
|
||
|
if (globalAgent && globalAgent.maxSockets !== Infinity && typeof globalAgent.maxSockets === 'number') {
|
||
|
return globalAgent.maxSockets;
|
||
|
}
|
||
|
return defaultMaxSockets;
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
return AWS.NodeHttpClient.sslAgent;
|
||
|
},
|
||
|
|
||
|
progressStream: function progressStream(stream, totalBytes) {
|
||
|
if (typeof TransformStream === 'undefined') {
|
||
|
// for node 0.8 there is no streaming progress
|
||
|
return;
|
||
|
}
|
||
|
var loadedBytes = 0;
|
||
|
var reporter = new TransformStream();
|
||
|
reporter._transform = function(chunk, encoding, callback) {
|
||
|
if (chunk) {
|
||
|
loadedBytes += chunk.length;
|
||
|
stream.emit('sendProgress', {
|
||
|
loaded: loadedBytes,
|
||
|
total: totalBytes
|
||
|
});
|
||
|
}
|
||
|
callback(null, chunk);
|
||
|
};
|
||
|
return reporter;
|
||
|
},
|
||
|
|
||
|
emitter: null
|
||
|
});
|
||
|
|
||
|
/**
|
||
|
* @!ignore
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* @api private
|
||
|
*/
|
||
|
AWS.HttpClient.prototype = AWS.NodeHttpClient.prototype;
|
||
|
|
||
|
/**
|
||
|
* @api private
|
||
|
*/
|
||
|
AWS.HttpClient.streamsApiVersion = ReadableStream ? 2 : 1;
|