diff --git a/lib/_http_client.js b/lib/_http_client.js index bc294263c7f881..1427d78714433a 100644 --- a/lib/_http_client.js +++ b/lib/_http_client.js @@ -410,6 +410,7 @@ function socketOnData(d) { // client function parserOnIncomingClient(res, shouldKeepAlive) { var socket = this.socket; + var parser = socket.parser; var req = socket._httpMessage; @@ -432,6 +433,7 @@ function parserOnIncomingClient(res, shouldKeepAlive) { // Responses to CONNECT request is handled as Upgrade. if (req.method === 'CONNECT') { res.upgrade = true; + parser.upgrade = true; return true; // skip body } diff --git a/src/node_http_parser.cc b/src/node_http_parser.cc index 4087ed263fb1a9..06d8a5395dfc5f 100644 --- a/src/node_http_parser.cc +++ b/src/node_http_parser.cc @@ -519,6 +519,26 @@ class Parser : public AsyncWrap { args.GetReturnValue().Set(ret); } + static void GetUpgrade(Local property, + const PropertyCallbackInfo& args) { + Parser* parser = Unwrap(args.Holder()); + + Local ret = Boolean::New( + parser->env()->isolate(), + parser->parser_.upgrade); + + args.GetReturnValue().Set(ret); + } + + static void SetUpgrade(Local property, + Local value, + const PropertyCallbackInfo& args) { + Parser* parser = Unwrap(args.Holder()); + + bool upgrade = value->BooleanValue(); + parser->parser_.upgrade = upgrade; + } + protected: class ScopedRetainParser { public: @@ -736,6 +756,7 @@ void InitHttpParser(Local target, void* priv) { Environment* env = Environment::GetCurrent(context); Local t = env->NewFunctionTemplate(Parser::New); + t->InstanceTemplate()->SetInternalFieldCount(1); t->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "HTTPParser")); @@ -771,6 +792,10 @@ void InitHttpParser(Local target, env->SetProtoMethod(t, "unconsume", Parser::Unconsume); env->SetProtoMethod(t, "getCurrentBuffer", Parser::GetCurrentBuffer); + Local o = t->InstanceTemplate(); + o->SetAccessor(FIXED_ONE_BYTE_STRING(env->isolate(), "upgrade"), + Parser::GetUpgrade, Parser::SetUpgrade); + target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "HTTPParser"), t->GetFunction()); } diff --git a/test/parallel/test-http-connect-head.js b/test/parallel/test-http-connect-head.js new file mode 100644 index 00000000000000..60d0cc1c857d13 --- /dev/null +++ b/test/parallel/test-http-connect-head.js @@ -0,0 +1,72 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const http = require('http'); + +var server = http.createServer(function(req, res) { + assert(false); +}); +server.on('connect', common.mustCall(function(req, socket, firstBodyChunk) { + assert.equal(req.method, 'CONNECT'); + assert.equal(req.url, 'example.com:443'); + console.error('Server got CONNECT request'); + + // It is legal for the server to send some data intended for the client + // along with the CONNECT response + socket.write( + 'HTTP/1.1 200 Connection established\r\n' + + 'Date: Tue, 15 Nov 1994 08:12:31 GMT\r\n' + + '\r\n' + + 'Head' + ); + + var data = firstBodyChunk.toString(); + socket.on('data', function(buf) { + data += buf.toString(); + }); + socket.on('end', function() { + socket.end(data); + }); +})); +server.listen(common.PORT, common.mustCall(function() { + var req = http.request({ + port: common.PORT, + method: 'CONNECT', + path: 'example.com:443' + }, function(res) { + assert(false); + }); + + req.on('close', common.mustCall(function() { })); + + req.on('connect', common.mustCall(function(res, socket, firstBodyChunk) { + console.error('Client got CONNECT request'); + + // Make sure this request got removed from the pool. + var name = 'localhost:' + common.PORT; + assert(!http.globalAgent.sockets.hasOwnProperty(name)); + assert(!http.globalAgent.requests.hasOwnProperty(name)); + + // Make sure this socket has detached. + assert(!socket.ondata); + assert(!socket.onend); + assert.equal(socket.listeners('connect').length, 0); + assert.equal(socket.listeners('data').length, 0); + + var data = firstBodyChunk.toString(); + + // test that the firstBodyChunk was not parsed as HTTP + assert.equal(data, 'Head'); + + socket.on('data', function(buf) { + data += buf.toString(); + }); + socket.on('end', function() { + assert.equal(data, 'HeadRequestEnd'); + server.close(); + }); + socket.end('End'); + })); + + req.end('Request'); +}));