Created
June 15, 2010 18:13
-
-
Save pgriess/439459 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 189ecf32e2540fb8e8d251bef16795868af66323 Mon Sep 17 00:00:00 2001 | |
From: Peter Griess <pg@std.in> | |
Date: Tue, 15 Jun 2010 12:35:49 -0500 | |
Subject: [PATCH] Support 'upgrade' event in HTTP client. | |
- Add a unit test for client HTTP upgrade. | |
- Move around unit tests for server HTTP upgrade. | |
--- | |
lib/http.js | 13 ++- | |
test/simple/test-http-upgrade-client.js | 79 +++++++++++++++ | |
test/simple/test-http-upgrade-server.js | 161 ++++++++++++++++++++++++++++++ | |
test/simple/test-http-upgrade-server2.js | 55 ++++++++++ | |
test/simple/test-http-upgrade.js | 161 ------------------------------ | |
test/simple/test-http-upgrade2.js | 55 ---------- | |
6 files changed, 305 insertions(+), 219 deletions(-) | |
create mode 100644 test/simple/test-http-upgrade-client.js | |
create mode 100644 test/simple/test-http-upgrade-server.js | |
create mode 100644 test/simple/test-http-upgrade-server2.js | |
delete mode 100644 test/simple/test-http-upgrade.js | |
delete mode 100644 test/simple/test-http-upgrade2.js | |
diff --git a/lib/http.js b/lib/http.js | |
index 1cf9eca..1f6ef53 100644 | |
--- a/lib/http.js | |
+++ b/lib/http.js | |
@@ -863,11 +863,18 @@ function Client ( ) { | |
self.destroy(ret); | |
} else if (parser.incoming && parser.incoming.upgrade) { | |
var bytesParsed = ret; | |
- var upgradeHead = d.slice(start + bytesParsed, end - start); | |
- parser.incoming.upgradeHead = upgradeHead; | |
- var req = self._outgoing[0]; | |
self.ondata = null; | |
self.onend = null | |
+ | |
+ var req = self._outgoing[0]; | |
+ | |
+ var upgradeHead = d.slice(start + bytesParsed + 1, end); | |
+ | |
+ if (self.listeners('upgrade').length) { | |
+ self.emit('upgrade', req, self, upgradeHead); | |
+ } else { | |
+ self.destroy(); | |
+ } | |
} | |
}; | |
diff --git a/test/simple/test-http-upgrade-client.js b/test/simple/test-http-upgrade-client.js | |
new file mode 100644 | |
index 0000000..4ca0085 | |
--- /dev/null | |
+++ b/test/simple/test-http-upgrade-client.js | |
@@ -0,0 +1,79 @@ | |
+// Verify that the 'upgrade' header causes an 'upgrade' event to be emitted to | |
+// the HTTP client. This test uses a raw TCP server to better control server | |
+// behavior. | |
+ | |
+require('../common'); | |
+ | |
+var http = require('http'); | |
+var net = require('net'); | |
+var sys = require('sys'); | |
+ | |
+var PORT = 5000 + Math.floor(Math.random() * 1000); | |
+ | |
+// Parse a string of data, returning an object if headers are complete, and | |
+// undefined otherwise | |
+var parseHeaders = function(data) { | |
+ var m = data.search(/\r\n\r\n/); | |
+ if (!m) { | |
+ return; | |
+ } | |
+ | |
+ var o = {}; | |
+ data.substring(0, m.index).split('\r\n').forEach(function(h) { | |
+ var foo = h.split(':'); | |
+ if (foo.length < 2) { | |
+ return; | |
+ } | |
+ | |
+ o[foo[0].trim().toLowerCase()] = foo[1].trim().toLowerCase(); | |
+ }); | |
+ | |
+ return o; | |
+}; | |
+ | |
+// Create a TCP server | |
+var srv = net.createServer(function(c) { | |
+ var data = ''; | |
+ c.addListener('data', function(d) { | |
+ data += d.toString('utf8'); | |
+ | |
+ // We found the end of the headers; make sure that we have an 'upgrade' | |
+ // header and send back a response | |
+ var headers = parseHeaders(data); | |
+ if (!headers) { | |
+ return; | |
+ } | |
+ | |
+ assert.ok('upgrade' in headers); | |
+ | |
+ c.write('HTTP/1.1 101\r\n'); | |
+ c.write('connection: upgrade\r\n'); | |
+ c.write('upgrade: ' + headers.upgrade + '\r\n'); | |
+ c.write('\r\n'); | |
+ c.write('nurtzo'); | |
+ | |
+ c.end(); | |
+ }); | |
+}); | |
+srv.listen(PORT, '127.0.0.1'); | |
+ | |
+var gotUpgrade = false; | |
+var hc = http.createClient(PORT, '127.0.0.1'); | |
+hc.addListener('upgrade', function(req, socket, upgradeHead) { | |
+ // XXX: This test isn't fantastic, as it assumes that the entire response | |
+ // from the server will arrive in a single data callback | |
+ assert.equal(upgradeHead, 'nurtzo'); | |
+ | |
+ socket.end(); | |
+ srv.close(); | |
+ | |
+ gotUpgrade = true; | |
+}); | |
+hc.request('/', { | |
+ 'Connection' : 'Upgrade', | |
+ 'Upgrade' : 'WebSocket' | |
+}).end(); | |
+ | |
+process.addListener('exit', function() { | |
+ assert.ok(gotUpgrade); | |
+}); | |
diff --git a/test/simple/test-http-upgrade-server.js b/test/simple/test-http-upgrade-server.js | |
new file mode 100644 | |
index 0000000..e89c445 | |
--- /dev/null | |
+++ b/test/simple/test-http-upgrade-server.js | |
@@ -0,0 +1,161 @@ | |
+require("../common"); | |
+ | |
+var sys = require("sys"); | |
+var net = require("net"); | |
+var http = require("http"); | |
+ | |
+ | |
+var requests_recv = 0; | |
+var requests_sent = 0; | |
+var request_upgradeHead = null; | |
+ | |
+function createTestServer(){ | |
+ return new testServer(); | |
+}; | |
+ | |
+function testServer(){ | |
+ var server = this; | |
+ http.Server.call(server, function(){}); | |
+ | |
+ server.addListener("connection", function(){ | |
+ requests_recv++; | |
+ }); | |
+ | |
+ server.addListener("request", function(req, res){ | |
+ res.writeHead(200, {"Content-Type": "text/plain"}); | |
+ res.write("okay"); | |
+ res.end(); | |
+ }); | |
+ | |
+ server.addListener("upgrade", function(req, socket, upgradeHead){ | |
+ socket.write( "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" | |
+ + "Upgrade: WebSocket\r\n" | |
+ + "Connection: Upgrade\r\n" | |
+ + "\r\n\r\n" | |
+ ); | |
+ | |
+ request_upgradeHead = upgradeHead; | |
+ | |
+ socket.ondata = function(d, start, end){ | |
+ var data = d.toString('utf8', start, end); | |
+ if(data == "kill"){ | |
+ socket.end(); | |
+ } else { | |
+ socket.write(data, "utf8"); | |
+ } | |
+ }; | |
+ }); | |
+}; | |
+ | |
+sys.inherits(testServer, http.Server); | |
+ | |
+ | |
+function testClient(){ | |
+ var conn = net.createConnection(PORT); | |
+ conn.setEncoding("utf8"); | |
+ return conn; | |
+} | |
+ | |
+function writeReq(socket, data, encoding){ | |
+ requests_sent++; | |
+ socket.write(data); | |
+}; | |
+ | |
+ | |
+/*----------------------------------------------- | |
+ connection: Upgrade with listener | |
+-----------------------------------------------*/ | |
+function test_upgrade_with_listener(_server){ | |
+ var conn = new testClient(); | |
+ var state = 0; | |
+ | |
+ conn.addListener("connect", function () { | |
+ writeReq( conn | |
+ , "GET / HTTP/1.1\r\n" | |
+ + "Upgrade: WebSocket\r\n" | |
+ + "Connection: Upgrade\r\n" | |
+ + "\r\n" | |
+ + "WjN}|M(6" | |
+ ); | |
+ }); | |
+ | |
+ conn.addListener("data", function(data){ | |
+ state++; | |
+ | |
+ if(state == 1){ | |
+ assert.equal("HTTP/1.1 101", data.substr(0, 12)); | |
+ assert.equal("WjN}|M(6", request_upgradeHead.toString("utf8")); | |
+ conn.write("test", "utf8"); | |
+ } else if(state == 2) { | |
+ assert.equal("test", data); | |
+ conn.write("kill", "utf8"); | |
+ } | |
+ }); | |
+ | |
+ conn.addListener("end", function(){ | |
+ assert.equal(2, state); | |
+ conn.end(); | |
+ _server.removeAllListeners("upgrade"); | |
+ test_upgrade_no_listener(); | |
+ }); | |
+}; | |
+ | |
+/*----------------------------------------------- | |
+ connection: Upgrade, no listener | |
+-----------------------------------------------*/ | |
+var test_upgrade_no_listener_ended = false; | |
+ | |
+function test_upgrade_no_listener(){ | |
+ var conn = new testClient(); | |
+ | |
+ conn.addListener("connect", function () { | |
+ writeReq(conn, "GET / HTTP/1.1\r\nUpgrade: WebSocket\r\nConnection: Upgrade\r\n\r\n"); | |
+ }); | |
+ | |
+ conn.addListener("end", function(){ | |
+ test_upgrade_no_listener_ended = true; | |
+ conn.end(); | |
+ }); | |
+ | |
+ conn.addListener("close", function(){ | |
+ test_standard_http(); | |
+ }); | |
+}; | |
+ | |
+/*----------------------------------------------- | |
+ connection: normal | |
+-----------------------------------------------*/ | |
+function test_standard_http(){ | |
+ var conn = new testClient(); | |
+ conn.addListener("connect", function () { | |
+ writeReq(conn, "GET / HTTP/1.1\r\n\r\n"); | |
+ }); | |
+ | |
+ conn.addListener("data", function(data){ | |
+ assert.equal("HTTP/1.1 200", data.substr(0, 12)); | |
+ conn.end(); | |
+ }); | |
+ | |
+ conn.addListener("close", function(){ | |
+ server.close(); | |
+ }); | |
+}; | |
+ | |
+ | |
+var server = createTestServer(); | |
+server.addListener("listening", function(){ | |
+ // All tests get chained after this: | |
+ test_upgrade_with_listener(server); | |
+}); | |
+ | |
+server.listen(PORT); | |
+ | |
+ | |
+/*----------------------------------------------- | |
+ Fin. | |
+-----------------------------------------------*/ | |
+process.addListener("exit", function () { | |
+ assert.equal(3, requests_recv); | |
+ assert.equal(3, requests_sent); | |
+ assert.ok(test_upgrade_no_listener_ended); | |
+}); | |
diff --git a/test/simple/test-http-upgrade-server2.js b/test/simple/test-http-upgrade-server2.js | |
new file mode 100644 | |
index 0000000..a6ea608 | |
--- /dev/null | |
+++ b/test/simple/test-http-upgrade-server2.js | |
@@ -0,0 +1,55 @@ | |
+require('../common'); | |
+ | |
+http = require('http'); | |
+net = require('net'); | |
+ | |
+server = http.createServer(function (req, res) { | |
+ error('got req'); | |
+ throw new Error("This shouldn't happen."); | |
+}); | |
+ | |
+server.addListener('upgrade', function (req, socket, upgradeHead) { | |
+ error('got upgrade event'); | |
+ // test that throwing an error from upgrade gets | |
+ // is uncaught | |
+ throw new Error('upgrade error'); | |
+}); | |
+ | |
+gotError = false; | |
+ | |
+process.addListener('uncaughtException', function (e) { | |
+ error('got "clientError" event'); | |
+ assert.equal('upgrade error', e.message); | |
+ gotError = true; | |
+ process.exit(0); | |
+}); | |
+ | |
+ | |
+server.listen(PORT); | |
+ | |
+ | |
+server.addListener('listening', function () { | |
+ var c = net.createConnection(PORT); | |
+ | |
+ c.addListener('connect', function () { | |
+ error('client wrote message'); | |
+ c.write( "GET /blah HTTP/1.1\r\n" | |
+ + "Upgrade: WebSocket\r\n" | |
+ + "Connection: Upgrade\r\n" | |
+ + "\r\n\r\nhello world" | |
+ ); | |
+ }); | |
+ | |
+ c.addListener('end', function () { | |
+ c.end(); | |
+ }); | |
+ | |
+ c.addListener('close', function () { | |
+ error('client close'); | |
+ server.close(); | |
+ }); | |
+}); | |
+ | |
+process.addListener('exit', function () { | |
+ assert.ok(gotError); | |
+}); | |
diff --git a/test/simple/test-http-upgrade.js b/test/simple/test-http-upgrade.js | |
deleted file mode 100644 | |
index e89c445..0000000 | |
--- a/test/simple/test-http-upgrade.js | |
+++ /dev/null | |
@@ -1,161 +0,0 @@ | |
-require("../common"); | |
- | |
-var sys = require("sys"); | |
-var net = require("net"); | |
-var http = require("http"); | |
- | |
- | |
-var requests_recv = 0; | |
-var requests_sent = 0; | |
-var request_upgradeHead = null; | |
- | |
-function createTestServer(){ | |
- return new testServer(); | |
-}; | |
- | |
-function testServer(){ | |
- var server = this; | |
- http.Server.call(server, function(){}); | |
- | |
- server.addListener("connection", function(){ | |
- requests_recv++; | |
- }); | |
- | |
- server.addListener("request", function(req, res){ | |
- res.writeHead(200, {"Content-Type": "text/plain"}); | |
- res.write("okay"); | |
- res.end(); | |
- }); | |
- | |
- server.addListener("upgrade", function(req, socket, upgradeHead){ | |
- socket.write( "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" | |
- + "Upgrade: WebSocket\r\n" | |
- + "Connection: Upgrade\r\n" | |
- + "\r\n\r\n" | |
- ); | |
- | |
- request_upgradeHead = upgradeHead; | |
- | |
- socket.ondata = function(d, start, end){ | |
- var data = d.toString('utf8', start, end); | |
- if(data == "kill"){ | |
- socket.end(); | |
- } else { | |
- socket.write(data, "utf8"); | |
- } | |
- }; | |
- }); | |
-}; | |
- | |
-sys.inherits(testServer, http.Server); | |
- | |
- | |
-function testClient(){ | |
- var conn = net.createConnection(PORT); | |
- conn.setEncoding("utf8"); | |
- return conn; | |
-} | |
- | |
-function writeReq(socket, data, encoding){ | |
- requests_sent++; | |
- socket.write(data); | |
-}; | |
- | |
- | |
-/*----------------------------------------------- | |
- connection: Upgrade with listener | |
------------------------------------------------*/ | |
-function test_upgrade_with_listener(_server){ | |
- var conn = new testClient(); | |
- var state = 0; | |
- | |
- conn.addListener("connect", function () { | |
- writeReq( conn | |
- , "GET / HTTP/1.1\r\n" | |
- + "Upgrade: WebSocket\r\n" | |
- + "Connection: Upgrade\r\n" | |
- + "\r\n" | |
- + "WjN}|M(6" | |
- ); | |
- }); | |
- | |
- conn.addListener("data", function(data){ | |
- state++; | |
- | |
- if(state == 1){ | |
- assert.equal("HTTP/1.1 101", data.substr(0, 12)); | |
- assert.equal("WjN}|M(6", request_upgradeHead.toString("utf8")); | |
- conn.write("test", "utf8"); | |
- } else if(state == 2) { | |
- assert.equal("test", data); | |
- conn.write("kill", "utf8"); | |
- } | |
- }); | |
- | |
- conn.addListener("end", function(){ | |
- assert.equal(2, state); | |
- conn.end(); | |
- _server.removeAllListeners("upgrade"); | |
- test_upgrade_no_listener(); | |
- }); | |
-}; | |
- | |
-/*----------------------------------------------- | |
- connection: Upgrade, no listener | |
------------------------------------------------*/ | |
-var test_upgrade_no_listener_ended = false; | |
- | |
-function test_upgrade_no_listener(){ | |
- var conn = new testClient(); | |
- | |
- conn.addListener("connect", function () { | |
- writeReq(conn, "GET / HTTP/1.1\r\nUpgrade: WebSocket\r\nConnection: Upgrade\r\n\r\n"); | |
- }); | |
- | |
- conn.addListener("end", function(){ | |
- test_upgrade_no_listener_ended = true; | |
- conn.end(); | |
- }); | |
- | |
- conn.addListener("close", function(){ | |
- test_standard_http(); | |
- }); | |
-}; | |
- | |
-/*----------------------------------------------- | |
- connection: normal | |
------------------------------------------------*/ | |
-function test_standard_http(){ | |
- var conn = new testClient(); | |
- conn.addListener("connect", function () { | |
- writeReq(conn, "GET / HTTP/1.1\r\n\r\n"); | |
- }); | |
- | |
- conn.addListener("data", function(data){ | |
- assert.equal("HTTP/1.1 200", data.substr(0, 12)); | |
- conn.end(); | |
- }); | |
- | |
- conn.addListener("close", function(){ | |
- server.close(); | |
- }); | |
-}; | |
- | |
- | |
-var server = createTestServer(); | |
-server.addListener("listening", function(){ | |
- // All tests get chained after this: | |
- test_upgrade_with_listener(server); | |
-}); | |
- | |
-server.listen(PORT); | |
- | |
- | |
-/*----------------------------------------------- | |
- Fin. | |
------------------------------------------------*/ | |
-process.addListener("exit", function () { | |
- assert.equal(3, requests_recv); | |
- assert.equal(3, requests_sent); | |
- assert.ok(test_upgrade_no_listener_ended); | |
-}); | |
diff --git a/test/simple/test-http-upgrade2.js b/test/simple/test-http-upgrade2.js | |
deleted file mode 100644 | |
index a6ea608..0000000 | |
--- a/test/simple/test-http-upgrade2.js | |
+++ /dev/null | |
@@ -1,55 +0,0 @@ | |
-require('../common'); | |
- | |
-http = require('http'); | |
-net = require('net'); | |
- | |
-server = http.createServer(function (req, res) { | |
- error('got req'); | |
- throw new Error("This shouldn't happen."); | |
-}); | |
- | |
-server.addListener('upgrade', function (req, socket, upgradeHead) { | |
- error('got upgrade event'); | |
- // test that throwing an error from upgrade gets | |
- // is uncaught | |
- throw new Error('upgrade error'); | |
-}); | |
- | |
-gotError = false; | |
- | |
-process.addListener('uncaughtException', function (e) { | |
- error('got "clientError" event'); | |
- assert.equal('upgrade error', e.message); | |
- gotError = true; | |
- process.exit(0); | |
-}); | |
- | |
- | |
-server.listen(PORT); | |
- | |
- | |
-server.addListener('listening', function () { | |
- var c = net.createConnection(PORT); | |
- | |
- c.addListener('connect', function () { | |
- error('client wrote message'); | |
- c.write( "GET /blah HTTP/1.1\r\n" | |
- + "Upgrade: WebSocket\r\n" | |
- + "Connection: Upgrade\r\n" | |
- + "\r\n\r\nhello world" | |
- ); | |
- }); | |
- | |
- c.addListener('end', function () { | |
- c.end(); | |
- }); | |
- | |
- c.addListener('close', function () { | |
- error('client close'); | |
- server.close(); | |
- }); | |
-}); | |
- | |
-process.addListener('exit', function () { | |
- assert.ok(gotError); | |
-}); | |
-- | |
1.7.1 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment