diff --git a/lib/_http_client.js b/lib/_http_client.js index de7b01cbd1b9bd..98b1de374f4bf8 100644 --- a/lib/_http_client.js +++ b/lib/_http_client.js @@ -363,7 +363,7 @@ function socketCloseListener() { res.emit('close'); } } else { - if (!req.socket._hadError) { + if (parser && !req.socket._hadError) { // This socket error fired before we started to // receive a response. The error needs to // fire on the request. @@ -424,7 +424,7 @@ function socketOnEnd() { var req = this._httpMessage; var parser = this.parser; - if (!req.res && !req.socket._hadError) { + if (parser && !req.res && !req.socket._hadError) { // 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.socket._hadError = true; @@ -442,57 +442,59 @@ function socketOnData(d) { var req = this._httpMessage; var parser = this.parser; - assert(parser && parser.socket === socket); - - var ret = parser.execute(d); - if (ret instanceof Error) { - debug('parse error', ret); - freeParser(parser, req, socket); - socket.destroy(); - req.socket._hadError = true; - req.emit('error', ret); - } else if (parser.incoming && parser.incoming.upgrade) { - // Upgrade (if status code 101) or CONNECT - var bytesParsed = ret; - var res = parser.incoming; - req.res = res; - - socket.removeListener('data', socketOnData); - socket.removeListener('end', socketOnEnd); - socket.removeListener('drain', ondrain); - parser.finish(); - freeParser(parser, req, socket); - - var bodyHead = d.slice(bytesParsed, d.length); - - var eventName = req.method === 'CONNECT' ? 'connect' : 'upgrade'; - if (req.listenerCount(eventName) > 0) { - req.upgradeOrConnect = true; - - // detach the socket - socket.emit('agentRemove'); - socket.removeListener('close', socketCloseListener); - socket.removeListener('error', socketErrorListener); - - socket._httpMessage = null; - socket.readableFlowing = null; + if (parser) { + assert(parser.socket === socket); - req.emit(eventName, res, socket, bodyHead); - req.emit('close'); - } else { - // Requested Upgrade or used CONNECT method, but have no handler. + var ret = parser.execute(d); + if (ret instanceof Error) { + debug('parse error', ret); + freeParser(parser, req, socket); socket.destroy(); + req.socket._hadError = true; + req.emit('error', ret); + } else if (parser.incoming && parser.incoming.upgrade) { + // Upgrade (if status code 101) or CONNECT + var bytesParsed = ret; + var res = parser.incoming; + req.res = res; + + socket.removeListener('data', socketOnData); + socket.removeListener('end', socketOnEnd); + socket.removeListener('drain', ondrain); + parser.finish(); + freeParser(parser, req, socket); + + var bodyHead = d.slice(bytesParsed, d.length); + + var eventName = req.method === 'CONNECT' ? 'connect' : 'upgrade'; + if (req.listenerCount(eventName) > 0) { + req.upgradeOrConnect = true; + + // detach the socket + socket.emit('agentRemove'); + socket.removeListener('close', socketCloseListener); + socket.removeListener('error', socketErrorListener); + + socket._httpMessage = null; + socket.readableFlowing = null; + + req.emit(eventName, res, socket, bodyHead); + req.emit('close'); + } else { + // Requested Upgrade or used CONNECT method, but have no handler. + socket.destroy(); + } + } else if (parser.incoming && parser.incoming.complete && + // When the status code is informational (100, 102-199), + // the server will send a final response after this client + // sends a request body, so we must not free the parser. + // 101 (Switching Protocols) and all other status codes + // should be processed normally. + !statusIsInformational(parser.incoming.statusCode)) { + socket.removeListener('data', socketOnData); + socket.removeListener('end', socketOnEnd); + freeParser(parser, req, socket); } - } else if (parser.incoming && parser.incoming.complete && - // When the status code is informational (100, 102-199), - // the server will send a final response after this client - // sends a request body, so we must not free the parser. - // 101 (Switching Protocols) and all other status codes - // should be processed normally. - !statusIsInformational(parser.incoming.statusCode)) { - socket.removeListener('data', socketOnData); - socket.removeListener('end', socketOnEnd); - freeParser(parser, req, socket); } } @@ -628,38 +630,41 @@ function emitFreeNT(socket) { function tickOnSocket(req, socket) { var parser = parsers.alloc(); - req.socket = socket; - req.connection = socket; - parser.reinitialize(HTTPParser.RESPONSE, parser[is_reused_symbol]); - parser.socket = socket; - parser.outgoing = req; - req.parser = parser; - - socket.parser = parser; - socket._httpMessage = req; - - // Setup "drain" propagation. - httpSocketSetup(socket); - - // Propagate headers limit from request object to parser - if (typeof req.maxHeadersCount === 'number') { - parser.maxHeaderPairs = req.maxHeadersCount << 1; - } - - parser.onIncoming = parserOnIncomingClient; - socket.removeListener('error', freeSocketErrorListener); - socket.on('error', socketErrorListener); - socket.on('data', socketOnData); - socket.on('end', socketOnEnd); - socket.on('close', socketCloseListener); - - if ( - req.timeout !== undefined || - (req.agent && req.agent.options && req.agent.options.timeout) - ) { - listenSocketTimeout(req); - } - req.emit('socket', socket); + if (parser) { + req.socket = socket; + req.connection = socket; + parser.reinitialize(HTTPParser.RESPONSE, parser[is_reused_symbol]); + parser.socket = socket; + parser.outgoing = req; + + req.parser = parser; + + socket.parser = parser; + socket._httpMessage = req; + + // Setup "drain" propagation. + httpSocketSetup(socket); + + // Propagate headers limit from request object to parser + if (typeof req.maxHeadersCount === 'number') { + parser.maxHeaderPairs = req.maxHeadersCount << 1; + } + + parser.onIncoming = parserOnIncomingClient; + socket.removeListener('error', freeSocketErrorListener); + socket.on('error', socketErrorListener); + socket.on('data', socketOnData); + socket.on('end', socketOnEnd); + socket.on('close', socketCloseListener); + + if ( + req.timeout !== undefined || + (req.agent && req.agent.options && req.agent.options.timeout) + ) { + listenSocketTimeout(req); + } + req.emit('socket', socket); + } } function listenSocketTimeout(req) { diff --git a/test/parallel/test-http-client-delete-parser.js b/test/parallel/test-http-client-delete-parser.js new file mode 100644 index 00000000000000..bac988853a8b38 --- /dev/null +++ b/test/parallel/test-http-client-delete-parser.js @@ -0,0 +1,24 @@ +'use strict'; + +const common = require('../common'); + +const http = require('http'); + +const server = http.createServer(common.mustCall((req, res) => { + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.write('okay', common.mustCall(() => { + delete req.socket.parser; + })); + res.end(); +})); + +server.listen(1337, '127.0.0.1'); +server.unref(); + +const req = http.request({ + port: 1337, + host: '127.0.0.1', + method: 'GET', +}); + +req.end();