diff --git a/lib/http.js b/lib/http.js index 2468e2c84cb..5216abed2c3 100644 --- a/lib/http.js +++ b/lib/http.js @@ -36,6 +36,122 @@ if (process.env.NODE_DEBUG && /http/.test(process.env.NODE_DEBUG)) { debug = function() { }; } +// Only called in the slow case where slow means +// that the request headers were either fragmented +// across multiple TCP packets or too large to be +// processed in a single run. This method is also +// called to process trailing HTTP headers. +function parserOnHeaders(headers, url) { + // Once we exceeded headers limit - stop collecting them + if (this.maxHeaderPairs <= 0 || + this._headers.length < this.maxHeaderPairs) { + this._headers = this._headers.concat(headers); + } + this._url += url; +} + +// info.headers and info.url are set only if .onHeaders() +// has not been called for this request. +// +// info.url is not set for response parsers but that's not +// applicable here since all our parsers are request parsers. +function parserOnHeadersComplete(info) { + var parser = this; + var headers = info.headers; + var url = info.url; + + if (!headers) { + headers = parser._headers; + parser._headers = []; + } + + if (!url) { + url = parser._url; + parser._url = ''; + } + + parser.incoming = new IncomingMessage(parser.socket); + parser.incoming.httpVersionMajor = info.versionMajor; + parser.incoming.httpVersionMinor = info.versionMinor; + parser.incoming.httpVersion = info.versionMajor + '.' + info.versionMinor; + parser.incoming.url = url; + + var n = headers.length; + + // If parser.maxHeaderPairs <= 0 - assume that there're no limit + if (parser.maxHeaderPairs > 0) { + n = Math.min(n, parser.maxHeaderPairs); + } + + for (var i = 0; i < n; i += 2) { + var k = headers[i]; + var v = headers[i + 1]; + parser.incoming._addHeaderLine(k.toLowerCase(), v); + } + + + if (info.method) { + // server only + parser.incoming.method = info.method; + } else { + // client only + parser.incoming.statusCode = info.statusCode; + // CHECKME dead code? we're always a request parser + } + + parser.incoming.upgrade = info.upgrade; + + var skipBody = false; // response to HEAD or CONNECT + + if (!info.upgrade) { + // For upgraded connections and CONNECT method request, + // we'll emit this after parser.execute + // so that we can capture the first part of the new protocol + skipBody = parser.onIncoming(parser.incoming, info.shouldKeepAlive); + } + + return skipBody; +} + +function parserOnBody(b, start, len) { + var parser = this; + var slice = b.slice(start, start + len); + // body encoding is done in _emitData + parser.incoming._emitData(slice); +} + +function parserOnMessageComplete() { + var parser = this; + parser.incoming.complete = true; + + // Emit any trailing headers. + var headers = parser._headers; + if (headers) { + for (var i = 0, n = headers.length; i < n; i += 2) { + var k = headers[i]; + var v = headers[i + 1]; + parser.incoming._addHeaderLine(k.toLowerCase(), v); + } + parser._headers = []; + parser._url = ''; + } + + if (!parser.incoming.upgrade) { + // For upgraded connections, also emit this after parser.execute + if (parser.incoming._paused || parser.incoming._pendings.length) { + parser.incoming._pendings.push(END_OF_FILE); + } else { + parser.incoming.readable = false; + parser.incoming._emitEnd(); + } + } + + if (parser.socket.readable) { + // force to read the next incoming message + parser.socket.resume(); + } +} + var parsers = new FreeList('parsers', 1000, function() { var parser = new HTTPParser(HTTPParser.REQUEST); @@ -48,116 +164,10 @@ var parsers = new FreeList('parsers', 1000, function() { // across multiple TCP packets or too large to be // processed in a single run. This method is also // called to process trailing HTTP headers. - parser.onHeaders = function(headers, url) { - // Once we exceeded headers limit - stop collecting them - if (parser.maxHeaderPairs <= 0 || - parser._headers.length < parser.maxHeaderPairs) { - parser._headers = parser._headers.concat(headers); - } - parser._url += url; - }; - - // info.headers and info.url are set only if .onHeaders() - // has not been called for this request. - // - // info.url is not set for response parsers but that's not - // applicable here since all our parsers are request parsers. - parser.onHeadersComplete = function(info) { - var headers = info.headers; - var url = info.url; - - if (!headers) { - headers = parser._headers; - parser._headers = []; - } - - if (!url) { - url = parser._url; - parser._url = ''; - } - - parser.incoming = new IncomingMessage(parser.socket); - parser.incoming.httpVersionMajor = info.versionMajor; - parser.incoming.httpVersionMinor = info.versionMinor; - parser.incoming.httpVersion = info.versionMajor + '.' + info.versionMinor; - parser.incoming.url = url; - - var n = headers.length; - - // If parser.maxHeaderPairs <= 0 - assume that there're no limit - if (parser.maxHeaderPairs > 0) { - n = Math.min(n, parser.maxHeaderPairs); - } - - for (var i = 0; i < n; i += 2) { - var k = headers[i]; - var v = headers[i + 1]; - parser.incoming._addHeaderLine(k.toLowerCase(), v); - } - - if (info.method) { - // server only - parser.incoming.method = info.method; - } else { - // client only - parser.incoming.statusCode = info.statusCode; - // CHECKME dead code? we're always a request parser - } - - parser.incoming.upgrade = info.upgrade; - - var skipBody = false; // response to HEAD or CONNECT - - if (!info.upgrade) { - // For upgraded connections and CONNECT method request, - // we'll emit this after parser.execute - // so that we can capture the first part of the new protocol - skipBody = parser.onIncoming(parser.incoming, info.shouldKeepAlive); - } - - return skipBody; - }; - - parser.onBody = function(b, start, len) { - // TODO body encoding? - var slice = b.slice(start, start + len); - if (parser.incoming._paused || parser.incoming._pendings.length) { - parser.incoming._pendings.push(slice); - } else { - parser.incoming._emitData(slice); - } - }; - - parser.onMessageComplete = function() { - parser.incoming.complete = true; - - // Emit any trailing headers. - var headers = parser._headers; - if (headers) { - for (var i = 0, n = headers.length; i < n; i += 2) { - var k = headers[i]; - var v = headers[i + 1]; - parser.incoming._addHeaderLine(k.toLowerCase(), v); - } - parser._headers = []; - parser._url = ''; - } - - if (!parser.incoming.upgrade) { - // For upgraded connections, also emit this after parser.execute - if (parser.incoming._paused || parser.incoming._pendings.length) { - parser.incoming._pendings.push(END_OF_FILE); - } else { - parser.incoming.readable = false; - parser.incoming._emitEnd(); - } - } - - if (parser.socket.readable) { - // force to read the next incoming message - parser.socket.resume(); - } - }; + parser.onHeaders = parserOnHeaders; + parser.onHeadersComplete = parserOnHeadersComplete; + parser.onBody = parserOnBody; + parser.onMessageComplete = parserOnMessageComplete; return parser; }); @@ -1230,6 +1240,31 @@ function createHangUpError() { return error; } +// Free the parser and also break any links that it +// might have to any other things. +// TODO: All parser data should be attached to a +// single object, so that it can be easily cleaned +// up by doing `parser.data = {}`, which should +// be done in FreeList.free. `parsers.free(parser)` +// should be all that is needed. +function freeParser(parser, req) { + if (parser) { + parser._headers = []; + parser.onIncoming = null; + if (parser.socket) { + parser.socket.onend = null; + parser.socket.ondata = null; + } + parser.socket = null; + parser.incoming = null; + parsers.free(parser); + parser = null; + } + if (req) { + req.parser = null; + } +}; + ClientRequest.prototype.onSocket = function(socket) { var req = this; @@ -1254,21 +1289,6 @@ ClientRequest.prototype.onSocket = function(socket) { // Setup 'drain' propogation. httpSocketSetup(socket); - var freeParser = function() { - if (parser) { - parser.onIncoming = null; - parser.socket.onend = null; - parser.socket.ondata = null; - parser.socket = null; - parser.incoming = null; - parsers.free(parser); - parser = null; - } - if (req) { - req.parser = null; - } - }; - var errorListener = function(err) { debug('HTTP SOCKET ERROR: ' + err.message + '\n' + err.stack); req.emit('error', err); @@ -1277,7 +1297,7 @@ ClientRequest.prototype.onSocket = function(socket) { req._hadError = true; if (parser) { parser.finish(); - freeParser(); + freeParser(parser, req); } socket.destroy(); } @@ -1299,7 +1319,7 @@ ClientRequest.prototype.onSocket = function(socket) { var ret = parser.execute(d, start, end - start); if (ret instanceof Error) { debug('parse error'); - freeParser(); + freeParser(parser, req); socket.destroy(ret); } else if (parser.incoming && parser.incoming.upgrade) { // Upgrade or CONNECT @@ -1310,7 +1330,6 @@ ClientRequest.prototype.onSocket = function(socket) { socket.ondata = null; socket.onend = null; parser.finish(); - freeParser(); // This is start + byteParsed var bodyHead = d.slice(start + bytesParsed, end); @@ -1331,12 +1350,13 @@ ClientRequest.prototype.onSocket = function(socket) { // Got Upgrade header or CONNECT method, 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(); + freeParser(parser, req); } }; @@ -1349,7 +1369,7 @@ ClientRequest.prototype.onSocket = function(socket) { } if (parser) { parser.finish(); - freeParser(); + freeParser(parser, req); } socket.destroy(); }; @@ -1586,8 +1606,8 @@ function connectionListener(socket) { function serverSocketCloseListener() { debug('server socket close'); - // unref the parser for easy gc - parsers.free(parser); + // mark this parser as reusable + freeParser(parser); abortIncoming(); } @@ -1632,7 +1652,7 @@ function connectionListener(socket) { socket.onend = null; socket.removeListener('close', serverSocketCloseListener); parser.finish(); - parsers.free(parser); + freeParser(parser, req); // This is start + byteParsed var bodyHead = d.slice(start + bytesParsed, end);