From 760bba55186eba039ca00e532f7813d2aea450a2 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 14 Apr 2010 03:52:15 -0700 Subject: [PATCH] Support Upgrade in HTTP messages This allows for web servers to be "hijacked" and used as Web Socket servers (or other). You simply listen for requests as normal, but check if req.upgrade === true If so, this will be the last request of the connection. It's your job now to hijack req.connection and start reading from it. req.upgradeHead is a buffer containing the first part of the new protocol communication (in the case it arrived on the same packet). This needs tests and documentation. API subject to change. --- lib/http.js | 40 +++++++++++++++++++++++++++++++++------- src/node_http_parser.cc | 6 +++++- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/lib/http.js b/lib/http.js index 28e3e3e249..68b4fa2bbf 100644 --- a/lib/http.js +++ b/lib/http.js @@ -76,7 +76,13 @@ var parsers = new FreeList('parsers', 1000, function () { parser.incoming.statusCode = info.statusCode; } - parser.onIncoming(parser.incoming, info.shouldKeepAlive); + parser.incoming.upgrade = info.upgrade; + + if (!info.upgrade) { + // For upgraded connections, we'll emit this after parser.execute + // so that we can capture the first part of the new protocol + parser.onIncoming(parser.incoming, info.shouldKeepAlive); + } }; parser.onBody = function (b, start, len) { @@ -91,7 +97,10 @@ var parsers = new FreeList('parsers', 1000, function () { }; parser.onMessageComplete = function () { - parser.incoming.emit("end"); + if (!parser.incoming.upgrade) { + // For upgraded connections, also emit this after parser.execute + parser.incoming.emit("end"); + } }; return parser; @@ -512,7 +521,16 @@ function connectionListener (socket) { parser.socket = socket; socket.ondata = function (d, start, end) { - parser.execute(d, start, end - start); + var bytesParsed = parser.execute(d, start, end - start); + if (parser.incoming && parser.incoming.upgrade) { + var upgradeHead = d.slice(start + bytesParsed, end - start); + parser.incoming.upgradeHead = upgradeHead; + socket.ondata = null; + socket.onend = null; + + self.emit('request', parser.incoming, null); + parser.incoming.emit('end'); + } }; socket.onend = function () { @@ -579,8 +597,16 @@ function Client ( ) { requests.push(req); }; - this.ondata = function (d, start, end) { - parser.execute(d, start, end - start); + self.ondata = function (d, start, end) { + var bytesParsed = parser.execute(d, start, end - start); + if (parser.incoming && parser.incoming.upgrade) { + var upgradeHead = d.slice(start + bytesParsed, end - start); + parser.incoming.upgradeHead = upgradeHead; + currentRequest.emit("response", parser.incoming); + parser.incoming.emit('end'); + self.ondata = null; + self.onend = null + } }; self.addListener("connect", function () { @@ -590,12 +616,12 @@ function Client ( ) { currentRequest.flush(); }); - self.addListener("end", function () { + self.onend = function () { parser.finish(); debug("self got end closing. readyState = " + self.readyState); self.end(); - }); + }; self.addListener("close", function (e) { if (e) { diff --git a/src/node_http_parser.cc b/src/node_http_parser.cc index e5dbed38f1..c7e69fc132 100644 --- a/src/node_http_parser.cc +++ b/src/node_http_parser.cc @@ -61,6 +61,7 @@ static Persistent http_version_sym; static Persistent version_major_sym; static Persistent version_minor_sym; static Persistent should_keep_alive_sym; +static Persistent upgrade_sym; static struct http_parser_settings settings; @@ -165,6 +166,8 @@ class Parser : public ObjectWrap { message_info->Set(should_keep_alive_sym, http_should_keep_alive(p) ? True() : False()); + message_info->Set(upgrade_sym, p->upgrade ? True() : False()); + Local argv[1] = { message_info }; Local ret = cb->Call(parser->handle_, 1, argv); @@ -243,7 +246,7 @@ class Parser : public ObjectWrap { Local nparsed_obj = Integer::New(nparsed); // If there was a parse error in one of the callbacks // TODO What if there is an error on EOF? - if (nparsed != len) { + if (!parser->parser_.upgrade && nparsed != len) { Local e = Exception::Error(String::New("Parse Error")); Local obj = e->ToObject(); obj->Set(String::NewSymbol("bytesParsed"), nparsed_obj); @@ -345,6 +348,7 @@ void InitHttpParser(Handle target) { version_major_sym = NODE_PSYMBOL("versionMajor"); version_minor_sym = NODE_PSYMBOL("versionMinor"); should_keep_alive_sym = NODE_PSYMBOL("shouldKeepAlive"); + upgrade_sym = NODE_PSYMBOL("upgrade"); settings.on_message_begin = Parser::on_message_begin; settings.on_path = Parser::on_path;