diff --git a/lib/http.js b/lib/http.js index 070942ef24..61204cad4b 100644 --- a/lib/http.js +++ b/lib/http.js @@ -105,6 +105,7 @@ function parserOnBody(b, start, len) { parser.incoming.emit('data', slice); } } + function parserOnMessageComplete() { var parser = this; parser.incoming.complete = true; @@ -490,7 +491,9 @@ OutgoingMessage.prototype._storeHeader = function(firstLine, headers) { // keep-alive logic if (sentConnectionHeader === false) { if (this.shouldKeepAlive && - (sentContentLengthHeader || this.useChunkedEncodingByDefault || this.agent)) { + (sentContentLengthHeader || + this.useChunkedEncodingByDefault || + this.agent)) { messageHeader += 'Connection: keep-alive\r\n'; } else { this._last = true; @@ -523,7 +526,7 @@ OutgoingMessage.prototype._storeHeader = function(firstLine, headers) { OutgoingMessage.prototype.setHeader = function(name, value) { if (arguments.length < 2) { - throw new Error("`name` and `value` are required for setHeader()."); + throw new Error('`name` and `value` are required for setHeader().'); } if (this._header) { @@ -540,7 +543,7 @@ OutgoingMessage.prototype.setHeader = function(name, value) { OutgoingMessage.prototype.getHeader = function(name) { if (arguments.length < 1) { - throw new Error("`name` is required for getHeader()."); + throw new Error('`name` is required for getHeader().'); } if (!this._headers) return; @@ -552,7 +555,7 @@ OutgoingMessage.prototype.getHeader = function(name) { OutgoingMessage.prototype.removeHeader = function(name) { if (arguments.length < 1) { - throw new Error("`name` is required for removeHeader()."); + throw new Error('`name` is required for removeHeader().'); } if (this._header) { @@ -1078,7 +1081,9 @@ function ClientRequest(options, cb) { self._last = true; self.shouldKeepAlive = false; if (options.createConnection) { - self.onSocket(options.createConnection(options.port, options.host, options)); + self.onSocket(options.createConnection(options.port, + options.host, + options)); } else { self.onSocket(net.createConnection(options.port, options.host)); } @@ -1095,7 +1100,8 @@ util.inherits(ClientRequest, OutgoingMessage); exports.ClientRequest = ClientRequest; ClientRequest.prototype._implicitHeader = function() { - this._storeHeader(this.method + ' ' + this.path + ' HTTP/1.1\r\n', this._renderHeaders()); + this._storeHeader(this.method + ' ' + this.path + ' HTTP/1.1\r\n', + this._renderHeaders()); }; ClientRequest.prototype.abort = function() { @@ -1131,6 +1137,7 @@ function freeParser(parser, req) { if (parser.socket) { parser.socket.onend = null; parser.socket.ondata = null; + parser.socket.parser = null; } parser.socket = null; parser.incoming = null; @@ -1140,175 +1147,198 @@ function freeParser(parser, req) { if (req) { req.parser = null; } -}; +} + +function socketCloseListener() { + var socket = this; + var parser = socket.parser; + var req = socket._httpMessage; + debug('HTTP socket close'); + req.emit('close'); + if (req.res && req.res.readable) { + // Socket closed before we emitted "end" below. + req.res.emit('aborted'); + req.res.emit('end'); + req.res.emit('close'); + } else if (!req.res && !req._hadError) { + // This socket error fired before we started to + // receive a response. The error needs to + // fire on the request. + req.emit('error', createHangUpError()); + } + + if (parser) { + parser.finish(); + freeParser(parser, req); + } +} + +function socketErrorListener(err) { + var socket = this; + var parser = socket.parser; + var req = socket._httpMessage; + debug('HTTP SOCKET ERROR: ' + err.message + '\n' + err.stack); + if (req) { + req.emit('error', err); + // For Safety. Some additional errors might fire later on + // and we need to make sure we don't double-fire the error event. + req._hadError = true; + } + if (parser) { + parser.finish(); + freeParser(parser, req); + } + socket.destroy(); +} + +function responseOnEnd() { + var req = this.req; + var socket = req.socket; + + if (req.shouldKeepAlive) { + debug('AGENT socket keep-alive'); + socket.removeListener('close', socketCloseListener); + socket.removeListener('error', socketErrorListener); + socket.emit('free'); + } else { + if (socket.writable) { + debug('AGENT socket.destroySoon()'); + socket.destroySoon(); + } + assert(!socket.writable); + } +} + +function parserOnIncomingClient(res, shouldKeepAlive) { + var parser = this; + var socket = this.socket; + var req = socket._httpMessage; + + debug('AGENT incoming response!'); + + if (req.res) { + // We already have a response object, this means the server + // sent a double response. + socket.destroy(); + return; + } + req.res = res; + + // Responses to HEAD requests are crazy. + // HEAD responses aren't allowed to have an entity-body + // but *can* have a content-length which actually corresponds + // to the content-length of the entity-body had the request + // been a GET. + var isHeadResponse = req.method == 'HEAD'; + debug('AGENT isHeadResponse ' + isHeadResponse); + + if (res.statusCode == 100) { + // restart the parser, as this is a continue message. + delete req.res; // Clear res so that we don't hit double-responses. + req.emit('continue'); + return true; + } + + if (req.shouldKeepAlive && !shouldKeepAlive && !req.upgraded) { + // Server MUST respond with Connection:keep-alive for us to enable it. + // If we've been upgraded (via WebSockets) we also shouldn't try to + // keep the connection open. + req.shouldKeepAlive = false; + } + + DTRACE_HTTP_CLIENT_RESPONSE(socket, req); + req.emit('response', res); + req.res = res; + res.req = req; + + res.on('end', responseOnEnd); + + return isHeadResponse; +} + +function socketOnEnd() { + var socket = this; + var req = this._httpMessage; + var parser = this.parser; + if (!req.res) { + // If we don't have a response then we know that the socket + // ended prematurely and we need to emit an error on the request. + req.emit('error', createHangUpError()); + req._hadError = true; + } + if (parser) { + parser.finish(); + freeParser(parser, req); + } + socket.destroy(); +} +function socketOnData(d, start, end) { + var socket = this; + var req = this._httpMessage; + var parser = this.parser; + + var ret = parser.execute(d, start, end - start); + if (ret instanceof Error) { + debug('parse error'); + freeParser(parser, req); + socket.destroy(ret); + } else if (parser.incoming && parser.incoming.upgrade) { + var bytesParsed = ret; + socket.ondata = null; + socket.onend = null; + + var res = parser.incoming; + req.res = res; + + // This is start + byteParsed + var upgradeHead = d.slice(start + bytesParsed, end); + if (req.listeners('upgrade').length) { + // Emit 'upgrade' on the Agent. + req.upgraded = true; + req.emit('upgrade', res, socket, upgradeHead); + socket.emit('agentRemove'); + } else { + // Got upgrade header, but have no handler. + socket.destroy(); + } + freeParser(parser, req); + } else if (parser.incoming && parser.incoming.complete && + // When the status code is 100 (Continue), the server will + // send a final response after this client sends a request + // body. So, we must not free the parser. + parser.incoming.statusCode !== 100) { + freeParser(parser, req); + } +} + ClientRequest.prototype.onSocket = function(socket) { var req = this; + process.nextTick(function() { var parser = parsers.alloc(); + req.socket = socket; req.connection = socket; - parser.reinitialize(HTTPParser.RESPONSE); parser.socket = socket; + socket.parser = parser; + parser.reinitialize(HTTPParser.RESPONSE); parser.incoming = null; req.parser = parser; socket._httpMessage = req; + // Setup "drain" propogation. httpSocketSetup(socket); + socket.ondata = socketOnData; + socket.onend = socketOnEnd; + socket.on('error', socketErrorListener); + socket.on('close', socketCloseListener); + parser.onIncoming = parserOnIncomingClient; - function errorListener(err) { - debug('HTTP SOCKET ERROR: ' + err.message + '\n' + err.stack); - if (req) { - req.emit('error', err); - // For Safety. Some additional errors might fire later on - // and we need to make sure we don't double-fire the error event. - req._hadError = true; - } - if (parser) { - parser.finish(); - freeParser(parser, req); - } - socket.destroy(); - } - - socket.on('error', errorListener); - - socket.ondata = function(d, start, end) { - var ret = parser.execute(d, start, end - start); - if (ret instanceof Error) { - debug('parse error'); - freeParser(parser, req); - socket.destroy(ret); - } else if (parser.incoming && parser.incoming.upgrade) { - var bytesParsed = ret; - socket.ondata = null; - socket.onend = null; - - var res = parser.incoming; - req.res = res; - - // This is start + byteParsed - var upgradeHead = d.slice(start + bytesParsed, end); - if (req.listeners('upgrade').length) { - // Emit 'upgrade' on the Agent. - req.upgraded = true; - req.emit('upgrade', res, socket, upgradeHead); - socket.emit('agentRemove'); - } else { - // Got upgrade header, but have no handler. - socket.destroy(); - } - freeParser(parser, req); - } else if (parser.incoming && parser.incoming.complete && - // When the status code is 100 (Continue), the server will - // send a final response after this client sends a request - // body. So, we must not free the parser. - parser.incoming.statusCode !== 100) { - freeParser(parser, req); - } - }; - - socket.onend = function() { - if (!req.res) { - // If we don't have a response then we know that the socket - // ended prematurely and we need to emit an error on the request. - req.emit('error', createHangUpError()); - req._hadError = true; - } - if (parser) { - parser.finish(); - freeParser(parser, req); - } - socket.destroy(); - }; - - var closeListener = function() { - debug('HTTP socket close'); - req.emit('close'); - if (req.res && req.res.readable) { - // Socket closed before we emitted "end" below. - req.res.emit('aborted'); - req.res.emit('end'); - req.res.emit('close'); - } else if (!req.res && !req._hadError) { - // This socket error fired before we started to - // receive a response. The error needs to - // fire on the request. - req.emit('error', createHangUpError()); - } - - // Nothing more to be done with this req, since the socket - // is closed, and we've emitted the appropriate abort/end/close/error - // events. Disavow all knowledge, and break the references to - // the variables trapped by closures and on the socket object. - req = null; - socket._httpMessage = null; - } - socket.on('close', closeListener); - - parser.onIncoming = function(res, shouldKeepAlive) { - debug('AGENT incoming response!'); - - if (req.res) { - // We already have a response object, this means the server - // sent a double response. - socket.destroy(); - return; - } - req.res = res; - - // Responses to HEAD requests are crazy. - // HEAD responses aren't allowed to have an entity-body - // but *can* have a content-length which actually corresponds - // to the content-length of the entity-body had the request - // been a GET. - var isHeadResponse = req.method == 'HEAD'; - debug('AGENT isHeadResponse ' + isHeadResponse); - - if (res.statusCode == 100) { - // restart the parser, as this is a continue message. - delete req.res; // Clear res so that we don't hit double-responses. - req.emit('continue'); - return true; - } - - if (req.shouldKeepAlive && !shouldKeepAlive && !req.upgraded) { - // Server MUST respond with Connection:keep-alive for us to enable it. - // If we've been upgraded (via WebSockets) we also shouldn't try to - // keep the connection open. - req.shouldKeepAlive = false; - } - - res.addListener('end', function() { - if (!req.shouldKeepAlive) { - if (socket.writable) { - debug('AGENT socket.destroySoon()'); - socket.destroySoon(); - } - assert(!socket.writable); - } else { - debug('AGENT socket keep-alive'); - } - }); - - DTRACE_HTTP_CLIENT_RESPONSE(socket, req); - req.emit('response', res); - - res.on('end', function() { - if (req.shouldKeepAlive) { - socket.removeListener('close', closeListener); - socket.removeListener('error', errorListener); - socket.emit('free'); - } - }); - - return isHeadResponse; - }; req.emit('socket', socket); }); - }; ClientRequest.prototype._deferToConnect = function(method, arguments_, cb) {