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;