Merge branch '0.8.2'

This commit is contained in:
indexzero 2012-07-22 02:49:43 -04:00
commit 4999b20b84
20 changed files with 1249 additions and 1578 deletions

View File

@ -42,6 +42,7 @@ var ProxyTable = exports.ProxyTable = function (options) {
events.EventEmitter.call(this);
this.silent = options.silent || options.silent !== true;
this.target = options.target || {};
this.hostnameOnly = options.hostnameOnly === true;
if (typeof options.router === 'object') {
@ -91,19 +92,62 @@ ProxyTable.prototype.setRoutes = function (router) {
throw new Error('Cannot update ProxyTable routes without router.');
}
var self = this;
this.router = router;
if (this.hostnameOnly === false) {
var self = this;
this.routes = [];
Object.keys(router).forEach(function (path) {
var route = new RegExp('^' + path, 'i');
if (!/http[s]?/.test(router[path])) {
router[path] = (self.target.https ? 'https://' : 'http://')
+ router[path];
}
var target = url.parse(router[path]),
defaultPort = self.target.https ? 443 : 80;
//
// Setup a robust lookup table for the route:
//
// {
// source: {
// regexp: /^foo.com/i,
// sref: 'foo.com',
// url: {
// protocol: 'http:',
// slashes: true,
// host: 'foo.com',
// hostname: 'foo.com',
// href: 'http://foo.com/',
// pathname: '/',
// path: '/'
// }
// },
// {
// target: {
// sref: '127.0.0.1:8000/',
// url: {
// protocol: 'http:',
// slashes: true,
// host: '127.0.0.1:8000',
// hostname: '127.0.0.1',
// href: 'http://127.0.0.1:8000/',
// pathname: '/',
// path: '/'
// }
// },
//
self.routes.push({
route: route,
target: router[path],
path: path
source: {
regexp: new RegExp('^' + path, 'i'),
sref: path,
url: url.parse('http://' + path)
},
target: {
sref: target.hostname + ':' + (target.port || defaultPort) + target.path,
url: target
}
});
});
}
@ -137,22 +181,30 @@ ProxyTable.prototype.getProxyLocation = function (req) {
target += req.url;
for (var i in this.routes) {
var route = this.routes[i];
if (target.match(route.route)) {
var requrl = url.parse(req.url);
//add the 'http://'' to get around a url.parse bug, it won't actually be used.
var targeturl = url.parse('http://'+route.target);
var pathurl = url.parse('http://'+route.path);
if (target.match(route.source.regexp)) {
//
// Attempt to perform any path replacement for differences
// between the source path and the target path. This replaces the
// path's part of the URL to the target's part of the URL.
//
// 1. Parse the request URL
// 2. Replace any portions of the source path with the target path
// 3. Set the request URL to the formatted URL with replacements.
//
var parsed = url.parse(req.url);
//This replaces the path's part of the URL to the target's part of the URL.
requrl.pathname = requrl.pathname.replace(pathurl.pathname, targeturl.pathname);
req.url = url.format(requrl);
parsed.pathname = parsed.pathname.replace(
route.source.url.pathname,
route.target.url.pathname
);
var host = targeturl.hostname,
port = targeturl.port || 80;
req.url = url.format(parsed);
return {
port: port,
host: host
protocol: route.target.url.protocol.replace(':', ''),
host: route.target.url.hostname,
port: route.target.url.port
|| (this.target.https ? 443 : 80)
};
}
}

View File

@ -47,8 +47,8 @@ var RoutingProxy = exports.RoutingProxy = function (options) {
// Setup other default options to be used for instances of
// `HttpProxy` created by this `RoutingProxy` instance.
//
this.source = options.source || { host: 'localhost', port: 8000 };
this.https = this.source.https || options.https;
this.source = options.source || { host: 'localhost', port: 8000 };
this.https = this.source.https || options.https;
this.enable = options.enable;
this.forward = options.forward;
@ -88,8 +88,7 @@ RoutingProxy.prototype.add = function (options) {
options.target.host = options.target.host || options.host;
options.target.port = options.target.port || options.port;
options.target.https = this.target && this.target.https ||
options.target && options.target.https ||
options.https;
options.target && options.target.https;
//
// Setup options to pass-thru to the new `HttpProxy` instance
@ -102,12 +101,15 @@ RoutingProxy.prototype.add = function (options) {
});
this.proxies[key] = new HttpProxy(options);
if (this.listeners('proxyError').length > 0) {
this.proxies[key].on('proxyError', this.emit.bind(this, 'proxyError'));
}
if (this.listeners('webSocketProxyError').length > 0) {
this.proxies[key].on('webSocketProxyError', this.emit.bind(this, 'webSocketProxyError'));
}
this.proxies[key].on('start', this.emit.bind(this, 'start'));
this.proxies[key].on('forward', this.emit.bind(this, 'forward'));
this.proxies[key].on('end', this.emit.bind(this, 'end'));
@ -204,7 +206,13 @@ RoutingProxy.prototype.proxyRequest = function (req, res, options) {
}
var key = this._getKey(options),
proxy;
proxy;
if ((this.target && this.target.https)
|| (location && location.protocol === 'https')) {
options.target = options.target || {};
options.target.https = true;
}
if (!this.proxies[key]) {
this.add(options);

View File

@ -18,23 +18,25 @@
],
"dependencies": {
"colors": "0.x.x",
"optimist": "0.2.x",
"optimist": "0.3.x",
"pkginfo": "0.2.x"
},
"devDependencies": {
"request": "1.9.x",
"vows": "0.5.x",
"vows": "0.6.x",
"async": "0.1.x",
"socket.io": "0.6.17"
"socket.io": "0.9.6",
"socket.io-client": "0.9.6",
"ws": "0.4.21"
},
"main": "./lib/node-http-proxy",
"bin": {
"node-http-proxy": "./bin/node-http-proxy"
},
"scripts": {
"test": "npm run-script test-http && npm run-script test-https && npm run-script test-core",
"test-http": "vows --spec && vows --spec --target=secure",
"test-https": "vows --spec --source=secure && vows --spec --source=secure --target=secure",
"test": "npm run-script test-http && npm run-script test-https",
"test-http": "vows --spec && vows --spec --target=https",
"test-https": "vows --spec --proxy=https && vows --spec --proxy=https --target=https",
"test-core": "test/core/run"
},
"engines": {

View File

@ -1,407 +0,0 @@
/*
* helpers.js: Helpers for node-http-proxy tests.
*
* (C) 2010, Charlie Robbins
*
*/
var assert = require('assert'),
fs = require('fs'),
http = require('http'),
https = require('https'),
path = require('path'),
argv = require('optimist').argv,
request = require('request'),
vows = require('vows'),
websocket = require('../vendor/websocket'),
httpProxy = require('../lib/node-http-proxy');
var loadHttps = exports.loadHttps = function () {
return {
key: fs.readFileSync(path.join(__dirname, 'fixtures', 'agent2-key.pem'), 'utf8'),
cert: fs.readFileSync(path.join(__dirname, 'fixtures', 'agent2-cert.pem'), 'utf8')
};
};
var parseProtocol = exports.parseProtocol = function () {
function setupProtocol (secure) {
return {
secure: secure,
protocols: {
http: secure ? 'https' : 'http',
ws: secure ? 'wss' : 'ws'
}
}
}
return {
source: setupProtocol(argv.source === 'secure'),
target: setupProtocol(argv.target === 'secure')
};
}
var TestRunner = exports.TestRunner = function (options) {
options = options || {};
this.source = options.source || {};
this.target = options.target || {};
this.testServers = [];
if (this.source.secure) {
this.source.https = loadHttps();
}
if (this.target.secure) {
this.target.https = loadHttps();
}
};
TestRunner.prototype.assertProxied = function (host, proxyPort, port, requestPath, targetPath, createProxy) {
if (!targetPath) targetPath = "";
var self = this,
output = "hello " + host + targetPath,
assertion = "should receive '" + output + "'";
var test = {
topic: function () {
var that = this,
options;
options = {
method: 'GET',
uri: self.source.protocols.http + '://localhost:' + proxyPort,
headers: {
host: host
}
};
if (requestPath) options.uri += requestPath;
function startTest () {
if (port) {
return self.startTargetServer(port, output, function () {
request(options, that.callback);
});
}
request(options, this.callback);
}
return createProxy ? createProxy(startTest) : startTest();
}
};
test[assertion] = function (err, res, body) {
assert.isNull(err);
assert.equal(body, output);
};
return test;
};
TestRunner.prototype.assertResponseCode = function (proxyPort, statusCode, createProxy) {
var assertion = "should receive " + statusCode + " responseCode",
protocol = this.source.protocols.http;
var test = {
topic: function () {
var that = this, options = {
method: 'GET',
uri: protocol + '://localhost:' + proxyPort,
headers: {
host: 'unknown.com'
}
};
if (createProxy) {
return createProxy(function () {
request(options, that.callback);
});
}
request(options, this.callback);
}
};
test[assertion] = function (err, res, body) {
assert.isNull(err);
assert.equal(res.statusCode, statusCode);
};
return test;
};
// A test helper to check and see if the http headers were set properly.
TestRunner.prototype.assertHeaders = function (proxyPort, headerName, createProxy) {
var assertion = "should receive http header \"" + headerName + "\"",
protocol = this.source.protocols.http;
var test = {
topic: function () {
var that = this, options = {
method: 'GET',
uri: protocol + '://localhost:' + proxyPort,
headers: {
host: 'unknown.com'
}
};
if (createProxy) {
return createProxy(function () {
request(options, that.callback);
});
}
request(options, this.callback);
}
};
test[assertion] = function (err, res, body) {
assert.isNull(err);
assert.isNotNull(res.headers[headerName]);
};
return test;
};
//
// WebSocketTest
//
TestRunner.prototype.webSocketTest = function (options) {
var self = this;
this.startTargetServer(options.ports.target, 'hello websocket', function (err, target) {
var socket = options.io.listen(target);
if (options.onListen) {
options.onListen(socket);
}
self.startProxyServer(
options.ports.proxy,
options.ports.target,
options.host,
function (err, proxy) {
if (options.onServer) { options.onServer(proxy) }
//
// Setup the web socket against our proxy
//
var uri = options.wsprotocol + '://' + options.host + ':' + options.ports.proxy;
var ws = new websocket.WebSocket(uri + '/socket.io/websocket/', 'borf', {
origin: options.protocol + '://' + options.host
});
if (options.onWsupgrade) { ws.on('wsupgrade', options.onWsupgrade) }
if (options.onMessage) { ws.on('message', options.onMessage) }
if (options.onOpen) { ws.on('open', function () { options.onOpen(ws) }) }
}
);
});
}
//
// WebSocketTestWithTable
//
TestRunner.prototype.webSocketTestWithTable = function (options) {
var self = this;
this.startTargetServer(options.ports.target, 'hello websocket', function (err, target) {
var socket = options.io.listen(target);
if (options.onListen) {
options.onListen(socket);
}
self.startProxyServerWithTable(
options.ports.proxy,
{ router: options.router },
function (err, proxy) {
if (options.onServer) { options.onServer(proxy) }
//
// Setup the web socket against our proxy
//
var uri = options.wsprotocol + '://' + options.host + ':' + options.ports.proxy;
var ws = new websocket.WebSocket(uri + '/socket.io/websocket/', 'borf', {
origin: options.protocol + '://' + options.host
});
if (options.onWsupgrade) { ws.on('wsupgrade', options.onWsupgrade) }
if (options.onMessage) { ws.on('message', options.onMessage) }
if (options.onOpen) { ws.on('open', function () { options.onOpen(ws) }) }
}
);
});
}
//
// Creates the reverse proxy server
//
TestRunner.prototype.startProxyServer = function (port, targetPort, host, callback) {
var that = this,
proxyServer = httpProxy.createServer(host, targetPort, this.getOptions());
proxyServer.listen(port, function () {
that.testServers.push(proxyServer);
callback(null, proxyServer);
});
};
//
// Creates the reverse proxy server with a specified latency
//
TestRunner.prototype.startLatentProxyServer = function (port, targetPort, host, latency, callback) {
//
// Initialize the nodeProxy and start proxying the request
//
var that = this,
proxyServer;
proxyServer = httpProxy.createServer(host, targetPort, function (req, res, proxy) {
var buffer = httpProxy.buffer(req);
setTimeout(function () {
proxy.proxyRequest(req, res, buffer);
}, latency);
}, this.getOptions());
proxyServer.listen(port, function () {
that.testServers.push(proxyServer);
callback();
});
};
//
// Creates the reverse proxy server with a ProxyTable
//
TestRunner.prototype.startProxyServerWithTable = function (port, options, callback) {
var that = this,
proxyServer = httpProxy.createServer(merge({}, options, this.getOptions()));
proxyServer.listen(port, function () {
that.testServers.push(proxyServer);
callback();
});
return proxyServer;
};
//
// Creates a latent reverse proxy server using a ProxyTable
//
TestRunner.prototype.startProxyServerWithTableAndLatency = function (port, latency, options, callback) {
//
// Initialize the nodeProxy and start proxying the request
//
var that = this,
proxy = new httpProxy.RoutingProxy(merge({}, options, this.getOptions())),
proxyServer;
var handler = function (req, res) {
var buffer = httpProxy.buffer(req);
setTimeout(function () {
proxy.proxyRequest(req, res, {
buffer: buffer
});
}, latency);
};
proxyServer = this.source.https
? https.createServer(this.source.https, handler)
: http.createServer(handler);
proxyServer.listen(port, function () {
that.testServers.push(proxyServer);
callback();
});
return proxyServer;
};
//
// Creates proxy server forwarding to the specified options
//
TestRunner.prototype.startProxyServerWithForwarding = function (port, targetPort, host, options, callback) {
var that = this,
proxyServer = httpProxy.createServer(targetPort, host, merge({}, options, this.getOptions()));
proxyServer.listen(port, function () {
that.testServers.push(proxyServer);
callback(null, proxyServer);
});
};
//
// Creates the 'hellonode' server
//
TestRunner.prototype.startTargetServer = function (port, output, callback) {
var that = this,
targetServer,
handler;
handler = function (req, res) {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.write(output);
res.end();
};
targetServer = this.target.https
? https.createServer(this.target.https, handler)
: http.createServer(handler);
targetServer.listen(port, function () {
that.testServers.push(targetServer);
callback(null, targetServer);
});
};
//
// Close all of the testServers
//
TestRunner.prototype.closeServers = function () {
this.testServers.forEach(function (server) {
server.close();
});
return this.testServers;
};
//
// Creates a new instance of the options to
// pass to `httpProxy.createServer()`
//
TestRunner.prototype.getOptions = function () {
return {
https: clone(this.source.https),
target: {
https: clone(this.target.https)
}
};
};
//
// ### @private function clone (object)
// #### @object {Object} Object to clone
// Shallow clones the specified object.
//
function clone (object) {
if (!object) { return null }
return Object.keys(object).reduce(function (obj, k) {
obj[k] = object[k];
return obj;
}, {});
}
function merge (target) {
var objs = Array.prototype.slice.call(arguments, 1);
objs.forEach(function(o) {
Object.keys(o).forEach(function (attr) {
if (! o.__lookupGetter__(attr)) {
target[attr] = o[attr];
}
});
});
return target;
}

165
test/helpers/http.js Normal file
View File

@ -0,0 +1,165 @@
/*
* http.js: Top level include for node-http-proxy http helpers
*
* (C) 2010 Nodejitsu Inc.
* MIT LICENCE
*
*/
var assert = require('assert'),
http = require('http'),
https = require('https'),
url = require('url'),
async = require('async'),
helpers = require('./index'),
protocols = helpers.protocols,
httpProxy = require('../../lib/node-http-proxy');
//
// ### function createServerPair (options, callback)
// #### @options {Object} Options to create target and proxy server.
// #### @callback {function} Continuation to respond to when complete.
//
// Creates http target and proxy servers
//
exports.createServerPair = function (options, callback) {
async.series([
//
// 1. Create the target server
//
function createTarget(next) {
exports.createServer(options.target, next);
},
//
// 2. Create the proxy server
//
function createTarget(next) {
exports.createProxyServer(options.proxy, next);
}
], callback);
};
//
// ### function createServer (options, callback)
// #### @options {Object} Options for creatig an http server.
// #### @port {number} Port to listen on
// #### @output {string} String to write to each HTTP response
// #### @headers {Object} Headers to assert are sent by `node-http-proxy`.
// #### @callback {function} Continuation to respond to when complete.
//
// Creates a target server that the tests will proxy to.
//
exports.createServer = function (options, callback) {
//
// Request handler to use in either `http`
// or `https` server.
//
function requestHandler(req, res) {
if (options.headers) {
Object.keys(options.headers).forEach(function (key) {
assert.equal(req.headers[key], options.headers[key]);
});
}
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.write(options.output || 'hello proxy');
res.end();
}
var server = protocols.target === 'https'
? https.createServer(helpers.https, requestHandler)
: http.createServer(requestHandler);
server.listen(options.port, function () {
callback(null, this);
});
};
//
// ### function createProxyServer (options, callback)
// #### @options {Object} Options for creatig an http server.
// #### @port {number} Port to listen on
// #### @latency {number} Latency of this server in milliseconds
// #### @proxy {Object} Options to pass to the HttpProxy.
// #### @routing {boolean} Enables `httpProxy.RoutingProxy`
// #### @callback {function} Continuation to respond to when complete.
//
// Creates a proxy server that the tests will request against.
//
exports.createProxyServer = function (options, callback) {
if (!options.latency) {
if (protocols.proxy === 'https') {
options.proxy.https = helpers.https;
}
return httpProxy
.createServer(options.proxy)
.listen(options.port, function () {
callback(null, this);
});
}
var proxy = options.routing
? new httpProxy.RoutingProxy(options.proxy)
: new httpProxy.HttpProxy(options.proxy);
//
// Request handler to use in either `http`
// or `https` server.
//
function requestHandler(req, res) {
var buffer = httpProxy.buffer(req);
setTimeout(function () {
//
// Setup options dynamically for `RoutingProxy.prototype.proxyRequest`
// or `HttpProxy.prototype.proxyRequest`.
//
buffer = options.routing ? { buffer: buffer } : buffer
proxy.proxyRequest(req, res, buffer);
}, options.latency);
}
var server = protocols.proxy === 'https'
? https.createServer(helpers.https, requestHandler)
: http.createServer(requestHandler);
server.listen(options.port, function () {
callback(null, this);
});
};
//
// ### function assignPortsToRoutes (routes)
// #### @routes {Object} Routing table to assign ports to
//
// Assigns dynamic ports to the `routes` for runtime testing.
//
exports.assignPortsToRoutes = function (routes) {
Object.keys(routes).forEach(function (source) {
routes[source] = routes[source].replace('{PORT}', helpers.nextPort);
});
return routes;
};
//
// ### function parseRoutes (options)
// #### @options {Object} Options to use when parsing routes
// #### @protocol {string} Protocol to use in the routes
// #### @routes {Object} Routes to parse.
//
// Returns an Array of fully-parsed URLs for the source and
// target of `options.routes`.
//
exports.parseRoutes = function (options) {
var protocol = options.protocol || 'http',
routes = options.routes;
return Object.keys(routes).map(function (source) {
return {
source: url.parse(protocol + '://' + source),
target: url.parse(protocol + '://' + routes[source])
};
});
};

105
test/helpers/index.js Normal file
View File

@ -0,0 +1,105 @@
/*
* index.js: Top level include for node-http-proxy helpers
*
* (C) 2010 Nodejitsu Inc.
* MIT LICENCE
*
*/
var fs = require('fs'),
path = require('path');
var fixturesDir = path.join(__dirname, '..', 'fixtures');
//
// @https {Object}
// Returns the necessary `https` credentials.
//
Object.defineProperty(exports, 'https', {
get: function () {
delete this.https;
return this.https = {
key: fs.readFileSync(path.join(fixturesDir, 'agent2-key.pem'), 'utf8'),
cert: fs.readFileSync(path.join(fixturesDir, 'agent2-cert.pem'), 'utf8')
};
}
});
//
// @protocols {Object}
// Returns an object representing the desired protocols
// for the `proxy` and `target` server.
//
Object.defineProperty(exports, 'protocols', {
get: function () {
delete this.protocols;
return this.protocols = {
target: exports.argv.target || 'http',
proxy: exports.argv.proxy || 'http'
};
}
});
//
// @nextPort {number}
// Returns an auto-incrementing port for tests.
//
Object.defineProperty(exports, 'nextPort', {
get: function () {
var current = this.port || 8000;
this.port = current + 1;
return current;
}
});
//
// @nextPortPair {Object}
// Returns an auto-incrementing pair of ports for tests.
//
Object.defineProperty(exports, 'nextPortPair', {
get: function () {
return {
target: this.nextPort,
proxy: this.nextPort
};
}
});
//
// ### function describe(prefix)
// #### @prefix {string} Prefix to use before the description
//
// Returns a string representing the protocols that this suite
// is testing based on CLI arguments.
//
exports.describe = function (prefix, base) {
prefix = prefix || '';
base = base || 'http'
function protocol(endpoint) {
return exports.protocols[endpoint] === 'https'
? base + 's'
: base;
}
return [
'node-http-proxy',
prefix,
[
protocol('proxy'),
'-to-',
protocol('target')
].join('')
].filter(Boolean).join('/');
};
//
// Expose the CLI arguments
//
exports.argv = require('optimist').argv;
//
// Export additional helpers for `http` and `websockets`.
//
exports.http = require('./http');
exports.ws = require('./ws');

112
test/helpers/ws.js Normal file
View File

@ -0,0 +1,112 @@
/*
* ws.js: Top level include for node-http-proxy websocket helpers
*
* (C) 2010 Nodejitsu Inc.
* MIT LICENCE
*
*/
var assert = require('assert'),
https = require('https'),
async = require('async'),
io = require('socket.io'),
ws = require('ws'),
helpers = require('./index'),
protocols = helpers.protocols,
http = require('./http');
//
// ### function createServerPair (options, callback)
// #### @options {Object} Options to create target and proxy server.
// #### @target {Object} Options for the target server.
// #### @proxy {Object} Options for the proxy server.
// #### @callback {function} Continuation to respond to when complete.
//
// Creates http target and proxy servers
//
exports.createServerPair = function (options, callback) {
async.series([
//
// 1. Create the target server
//
function createTarget(next) {
exports.createServer(options.target, next);
},
//
// 2. Create the proxy server
//
function createTarget(next) {
http.createProxyServer(options.proxy, next);
}
], callback);
};
//
// ### function createServer (options, callback)
// #### @options {Object} Options for creating the socket.io or ws server.
// #### @raw {boolean} Enables ws.Websocket server.
//
// Creates a socket.io or ws server using the specified `options`.
//
exports.createServer = function (options, callback) {
return options.raw
? exports.createWsServer(options, callback)
: exports.createSocketIoServer(options, callback);
};
//
// ### function createSocketIoServer (options, callback)
// #### @options {Object} Options for creating the socket.io server
// #### @port {number} Port to listen on
// #### @input {string} Input to expect from the only socket
// #### @output {string} Output to send the only socket
//
// Creates a socket.io server on the specified `options.port` which
// will expect `options.input` and then send `options.output`.
//
exports.createSocketIoServer = function (options, callback) {
var server = protocols.target === 'https'
? io.listen(options.port, helpers.https, callback)
: io.listen(options.port, callback);
server.sockets.on('connection', function (socket) {
socket.on('incoming', function (data) {
assert.equal(data, options.input);
socket.emit('outgoing', options.output);
});
});
};
//
// ### function createWsServer (options, callback)
// #### @options {Object} Options for creating the ws.Server instance
// #### @port {number} Port to listen on
// #### @input {string} Input to expect from the only socket
// #### @output {string} Output to send the only socket
//
// Creates a ws.Server instance on the specified `options.port` which
// will expect `options.input` and then send `options.output`.
//
exports.createWsServer = function (options, callback) {
var server,
wss;
if (protocols.target === 'https') {
server = https.createServer(helpers.https, function (req, res) {
req.writeHead(200);
req.end();
}).listen(options.port, callback);
wss = new ws.Server({ server: server });
}
else {
wss = new ws.Server({ port: options.port }, callback);
}
wss.on('connection', function (socket) {
socket.on('message', function (data) {
assert.equal(data, options.input);
socket.send(options.output);
});
});
};

View File

@ -1,95 +0,0 @@
/*
node-http-proxy-test.js: http proxy for node.js
Copyright (c) 2010 Charlie Robbins, Marak Squires and Fedor Indutny
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
var assert = require('assert'),
util = require('util'),
request = require('request'),
vows = require('vows'),
helpers = require('../helpers');
var forwardOptions = {
forward: {
port: 8300,
host: 'localhost'
}
};
var badForwardOptions = {
forward: {
port: 9000,
host: 'localhost'
}
};
var options = helpers.parseProtocol(),
testName = [options.source.protocols.http, options.target.protocols.http].join('-to-'),
runner = new helpers.TestRunner(options);
vows.describe('node-http-proxy/http-proxy/' + testName).addBatch({
"When using server created by httpProxy.createServer()": {
"with no latency" : {
"and a valid target server": runner.assertProxied('localhost', 8080, 8081, false, false, function (callback) {
runner.startProxyServer(8080, 8081, 'localhost', callback);
}),
"and without a valid target server": runner.assertResponseCode(8082, 500, function (callback) {
runner.startProxyServer(8082, 9000, 'localhost', callback);
})
},
"with latency": {
"and a valid target server": runner.assertProxied('localhost', 8083, 8084, false, false, function (callback) {
runner.startLatentProxyServer(8083, 8084, 'localhost', 1000, callback);
}),
"and without a valid target server": runner.assertResponseCode(8085, 500, function (callback) {
runner.startLatentProxyServer(8085, 9000, 'localhost', 1000, callback);
})
},
"with forwarding enabled": {
topic: function () {
runner.startTargetServer(8300, 'forward proxy', this.callback);
},
"with no latency" : {
"and a valid target server": runner.assertProxied('localhost', 8120, 8121, false, false, function (callback) {
runner.startProxyServerWithForwarding(8120, 8121, 'localhost', forwardOptions, callback);
}),
"and also a valid target server": runner.assertHeaders(8122, "x-forwarded-for", function (callback) {
runner.startProxyServerWithForwarding(8122, 8123, 'localhost', forwardOptions, callback);
}),
"and without a valid forward server": runner.assertProxied('localhost', 8124, 8125, false, false, function (callback) {
runner.startProxyServerWithForwarding(8124, 8125, 'localhost', badForwardOptions, callback);
})
}
}
}
}).addBatch({
"When the tests are over": {
topic: function () {
return runner.closeServers();
},
"the servers should clean up": function () {
assert.isTrue(true);
}
}
}).export(module);

57
test/http/http-test.js Normal file
View File

@ -0,0 +1,57 @@
/*
node-http-proxy-test.js: http proxy for node.js
Copyright (c) 2010 Charlie Robbins, Marak Squires and Fedor Indutny
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
var assert = require('assert'),
fs = require('fs'),
path = require('path'),
async = require('async'),
request = require('request'),
vows = require('vows'),
macros = require('../macros'),
helpers = require('../helpers');
var routeFile = path.join(__dirname, 'config.json');
vows.describe(helpers.describe()).addBatch({
"With a valid target server": {
"and no latency": {
"and no headers": macros.http.assertProxied(),
"and headers": macros.http.assertProxied({
request: { headers: { host: 'unknown.com' } }
}),
"and forwarding enabled": macros.http.assertForwardProxied()
},
"and latency": macros.http.assertProxied({
latency: 2000
})
},
"With a no valid target server": {
"and no latency": macros.http.assertInvalidProxy(),
"and latency": macros.http.assertInvalidProxy({
latency: 2000
})
}
}).export(module);

View File

@ -1,137 +0,0 @@
/*
* proxy-table-test.js: Tests for the ProxyTable object.
*
* (C) 2010, Charlie Robbins
*
*/
var assert = require('assert'),
fs = require('fs'),
path = require('path'),
util = require('util'),
argv = require('optimist').argv,
request = require('request'),
vows = require('vows'),
helpers = require('../helpers');
var options = helpers.parseProtocol(),
testName = [options.source.protocols.http, options.target.protocols.http].join('-to-'),
runner = new helpers.TestRunner(options),
routeFile = path.join(__dirname, 'config.json');
var fileOptions = {
router: {
"foo.com": "127.0.0.1:8101",
"bar.com": "127.0.0.1:8102"
}
};
var defaultOptions = {
router: {
"foo.com": "127.0.0.1:8091",
"bar.com": "127.0.0.1:8092",
"baz.com/taco": "127.0.0.1:8098",
"pizza.com/taco/muffins": "127.0.0.1:8099",
"blah.com/me": "127.0.0.1:8088/remapped",
"bleh.com/remap/this": "127.0.0.1:8087/remap/remapped",
"test.com/double/tap": "127.0.0.1:8086/remap",
}
};
var hostnameOptions = {
hostnameOnly: true,
router: {
"foo.com": "127.0.0.1:8091",
"bar.com": "127.0.0.1:8092"
}
};
vows.describe('node-http-proxy/routing-proxy/' + testName).addBatch({
"When using server created by httpProxy.createServer()": {
"when passed a routing table": {
"and routing by RegExp": {
topic: function () {
this.server = runner.startProxyServerWithTable(8090, defaultOptions, this.callback);
},
"an incoming request to foo.com": runner.assertProxied('foo.com', 8090, 8091),
"an incoming request to bar.com": runner.assertProxied('bar.com', 8090, 8092),
"an incoming request to baz.com/taco": runner.assertProxied('baz.com', 8090, 8098, "/taco", "/"),
"an incoming request to pizza.com/taco/muffins": runner.assertProxied('pizza.com', 8090, 8099, "/taco/muffins", "/"),
"an incoming request to blah.com/me/fun": runner.assertProxied('blah.com', 8090, 8088, "/me/fun", "/remapped/fun"),
"an incoming request to bleh.com/remap/this": runner.assertProxied('bleh.com', 8090, 8087, "/remap/this", "/remap/remapped"),
"an incoming request to test.com/double/tap/double/tap": runner.assertProxied('test.com', 8090, 8086, "/double/tap/double/tap", "/remap/double/tap"),
"an incoming request to unknown.com": runner.assertResponseCode(8090, 404)
},
"and routing by Hostname": {
topic: function () {
this.server = runner.startProxyServerWithTable(8093, hostnameOptions, this.callback);
},
"an incoming request to foo.com": runner.assertProxied('foo.com', 8093, 8094),
"an incoming request to bar.com": runner.assertProxied('bar.com', 8093, 8095),
"an incoming request to unknown.com": runner.assertResponseCode(8093, 404)
}
},
"when passed a routing file": {
topic: function () {
fs.writeFileSync(routeFile, JSON.stringify(fileOptions));
this.server = runner.startProxyServerWithTable(8100, {
router: routeFile
}, this.callback);
},
"an incoming request to foo.com": runner.assertProxied('foo.com', 8100, 8101),
"an incoming request to bar.com": runner.assertProxied('bar.com', 8100, 8102),
"an incoming request to unknown.com": runner.assertResponseCode(8100, 404),
"an incoming request to dynamic.com": {
"after the file has been modified": {
topic: function () {
var that = this,
data = fs.readFileSync(routeFile),
config = JSON.parse(data);
config.router['dynamic.com'] = "127.0.0.1:8103";
fs.writeFileSync(routeFile, JSON.stringify(config));
this.server.on('routes', function () {
runner.startTargetServer(8103, 'hello dynamic.com', function () {
request({
method: 'GET',
uri: options.source.protocols.http + '://localhost:8100',
headers: {
host: 'dynamic.com'
}
}, that.callback);
});
});
},
"should receive 'hello dynamic.com'": function (err, res, body) {
assert.equal(body, 'hello dynamic.com');
}
}
}
}
}
}).addBatch({
"When using an instance of ProxyTable combined with HttpProxy directly": {
topic: function () {
this.server = runner.startProxyServerWithTableAndLatency(8110, 100, {
router: {
'foo.com': 'localhost:8111',
'bar.com': 'localhost:8112'
}
}, this.callback);
},
"an incoming request to foo.com": runner.assertProxied('foo.com', 8110, 8111),
"an incoming request to bar.com": runner.assertProxied('bar.com', 8110, 8112),
"an incoming request to unknown.com": runner.assertResponseCode(8110, 404)
}
}).addBatch({
"When the tests are over": {
topic: function () {
//fs.unlinkSync(routeFile);
return runner.closeServers();
},
"the servers should clean up": function () {
assert.isTrue(true);
}
}
}).export(module);

View File

@ -0,0 +1,89 @@
/*
* routing-table-test.js: Tests for the proxying using the ProxyTable object.
*
* (C) 2010, Charlie Robbins
*
*/
var assert = require('assert'),
fs = require('fs'),
path = require('path'),
async = require('async'),
request = require('request'),
vows = require('vows'),
macros = require('../macros'),
helpers = require('../helpers/index');
var routeFile = path.join(__dirname, 'config.json');
vows.describe(helpers.describe('routing-table')).addBatch({
"With a routing table": {
"with latency": macros.http.assertProxiedToRoutes({
latency: 2000,
routes: {
"icanhaz.com": "127.0.0.1:{PORT}",
"latency.com": "127.0.0.1:{PORT}"
}
}),
"using RegExp": macros.http.assertProxiedToRoutes({
routes: {
"foo.com": "127.0.0.1:{PORT}",
"bar.com": "127.0.0.1:{PORT}",
"baz.com/taco": "127.0.0.1:{PORT}",
"pizza.com/taco/muffins": "127.0.0.1:{PORT}",
"blah.com/me": "127.0.0.1:{PORT}/remapped",
"bleh.com/remap/this": "127.0.0.1:{PORT}/remap/remapped",
"test.com/double/tap": "127.0.0.1:{PORT}/remap"
}
}),
"using hostnameOnly": macros.http.assertProxiedToRoutes({
hostnameOnly: true,
routes: {
"foo.com": "127.0.0.1:{PORT}",
"bar.com": "127.0.0.1:{PORT}"
}
}),
"using a routing file": macros.http.assertProxiedToRoutes({
filename: routeFile,
routes: {
"foo.com": "127.0.0.1:{PORT}",
"bar.com": "127.0.0.1:{PORT}"
}
}, {
"after the file has been modified": {
topic: function () {
var config = JSON.parse(fs.readFileSync(routeFile, 'utf8')),
protocol = helpers.protocols.proxy,
port = helpers.nextPort,
that = this;
config.router['dynamic.com'] = "127.0.0.1:" + port;
fs.writeFileSync(routeFile, JSON.stringify(config));
async.parallel([
function waitForRoutes(next) {
that.proxyServer.on('routes', next);
},
async.apply(
helpers.http.createServer,
{
port: port,
output: 'hello from dynamic.com'
}
)
], function () {
request({
uri: protocol + '://127.0.0.1:' + that.port,
headers: {
host: 'dynamic.com'
}
}, that.callback);
});
},
"should receive 'hello from dynamic.com'": function (err, res, body) {
assert.equal(body, 'hello from dynamic.com');
}
}
})
}
}).export(module);

323
test/macros/http.js Normal file
View File

@ -0,0 +1,323 @@
/*
* http.js: Macros for proxying HTTP requests
*
* (C) 2010 Nodejitsu Inc.
* MIT LICENCE
*
*/
var assert = require('assert'),
fs = require('fs'),
async = require('async'),
request = require('request'),
helpers = require('../helpers/index');
//
// ### function assertRequest (options)
// #### @options {Object} Options for this request assertion.
// #### @request {Object} Options to use for `request`.
// #### @assert {Object} Test assertions against the response.
//
// Makes a request using `options.request` and then asserts the response
// and body against anything in `options.assert`.
//
exports.assertRequest = function (options) {
return {
topic: function () {
//
// Now make the HTTP request and assert.
//
request(options.request, this.callback);
},
"should succeed": function (err, res, body) {
assert.isNull(err);
if (options.assert.body) {
assert.equal(body, options.assert.body);
}
if (options.assert.statusCode) {
assert.equal(res.statusCode, options.assert.statusCode);
}
}
};
};
//
// ### function assertProxied (options)
// #### @options {Object} Options for this test
// #### @latency {number} Latency in milliseconds for the proxy server.
// #### @ports {Object} Ports for the request (target, proxy).
// #### @output {string} Output to assert from.
// #### @forward {Object} Options for forward proxying.
//
// Creates a complete end-to-end test for requesting against an
// http proxy.
//
exports.assertProxied = function (options) {
options = options || {};
var ports = options.ports || helpers.nextPortPair,
output = options.output || 'hello world from ' + ports.target,
protocol = helpers.protocols.proxy,
req = options.request || {};
req.uri = req.uri || protocol + '://127.0.0.1:' + ports.proxy;
return {
topic: function () {
//
// Create a target server and a proxy server
// using the `options` supplied.
//
helpers.http.createServerPair({
target: {
output: output,
port: ports.target,
headers: req.headers
},
proxy: {
latency: options.latency,
port: ports.proxy,
proxy: {
forward: options.forward,
target: {
https: helpers.protocols.target === 'https',
host: '127.0.0.1',
port: ports.target
}
}
}
}, this.callback);
},
"the proxy request": exports.assertRequest({
request: req,
assert: {
body: output
}
})
};
};
//
// ### function assertInvalidProxy (options)
// #### @options {Object} Options for this test
// #### @latency {number} Latency in milliseconds for the proxy server
// #### @ports {Object} Ports for the request (target, proxy)
//
// Creates a complete end-to-end test for requesting against an
// http proxy with no target server.
//
exports.assertInvalidProxy = function (options) {
options = options || {};
var ports = options.ports || helpers.nextPortPair,
req = options.request || {},
protocol = helpers.protocols.proxy;
req.uri = req.uri || protocol + '://127.0.0.1:' + ports.proxy;
return {
topic: function () {
//
// Only create the proxy server, simulating a reverse-proxy
// to an invalid location.
//
helpers.http.createProxyServer({
latency: options.latency,
port: ports.proxy,
proxy: {
target: {
host: '127.0.0.1',
port: ports.target
}
}
}, this.callback);
},
"the proxy request": exports.assertRequest({
request: req,
assert: {
statusCode: 500
}
})
};
};
//
// ### function assertForwardProxied (options)
// #### @options {Object} Options for this test.
//
// Creates a complete end-to-end test for requesting against an
// http proxy with both a valid and invalid forward target.
//
exports.assertForwardProxied = function (options) {
var forwardPort = helpers.nextPort;
return {
topic: function () {
helpers.http.createServer({
output: 'hello from forward',
port: forwardPort
}, this.callback)
},
"and a valid forward target": exports.assertProxied({
forward: {
port: forwardPort,
host: '127.0.0.1'
}
}),
"and an invalid forward target": exports.assertProxied({
forward: {
port: 9898,
host: '127.0.0.1'
}
})
};
};
//
// ### function assertProxiedtoRoutes (options, nested)
// #### @options {Object} Options for this ProxyTable-based test
// #### @routes {Object|string} Routes to use for the proxy.
// #### @hostnameOnly {boolean} Enables hostnameOnly routing.
// #### @nested {Object} Nested vows to add to the returned context.
//
// Creates a complete end-to-end test for requesting against an
// http proxy using `options.routes`:
//
// 1. Creates target servers for all routes in `options.routes.`
// 2. Creates a proxy server.
// 3. Ensure requests to the proxy server for all route targets
// returns the unique expected output.
//
exports.assertProxiedToRoutes = function (options, nested) {
//
// Assign dynamic ports to the routes to use.
//
options.routes = helpers.http.assignPortsToRoutes(options.routes);
//
// Parse locations from routes for making assertion requests.
//
var locations = helpers.http.parseRoutes(options),
port = helpers.nextPort,
protocol = helpers.protocols.proxy,
context,
proxy;
if (options.filename) {
//
// If we've been passed a filename write the routes to it
// and setup the proxy options to use that file.
//
fs.writeFileSync(options.filename, JSON.stringify({ router: options.routes }));
proxy = { router: options.filename };
}
else {
//
// Otherwise just use the routes themselves.
//
proxy = {
hostnameOnly: options.hostnameOnly,
router: options.routes
};
}
//
// Set the https options if necessary
//
if (helpers.protocols.target === 'https') {
proxy.target = { https: true };
}
//
// Create the test context which creates all target
// servers for all routes and a proxy server.
//
context = {
topic: function () {
var that = this;
async.waterfall([
//
// 1. Create all the target servers
//
async.apply(
async.forEach,
locations,
function createRouteTarget(location, next) {
helpers.http.createServer({
port: location.target.port,
output: 'hello from ' + location.source.href
}, next);
}
),
//
// 2. Create the proxy server
//
async.apply(
helpers.http.createProxyServer,
{
port: port,
latency: options.latency,
routing: true,
proxy: proxy
}
)
], function (_, server) {
//
// 3. Set the proxy server for later use
//
that.proxyServer = server;
that.callback();
});
//
// 4. Assign the port to the context for later use
//
this.port = port;
},
//
// Add an extra assertion to a route which
// should respond with 404
//
"a request to unknown.com": exports.assertRequest({
assert: { statusCode: 404 },
request: {
uri: protocol + '://127.0.0.1:' + port,
headers: {
host: 'unknown.com'
}
}
})
};
//
// Add test assertions for each of the route locations.
//
locations.forEach(function (location) {
context[location.source.href] = exports.assertRequest({
request: {
uri: protocol + '://127.0.0.1:' + port + location.source.path,
headers: {
host: location.source.hostname
}
},
assert: {
body: 'hello from ' + location.source.href
}
});
});
//
// If there are any nested vows to add to the context
// add them before returning the full context.
//
if (nested) {
Object.keys(nested).forEach(function (key) {
context[key] = nested[key];
});
}
return context;
};

10
test/macros/index.js Normal file
View File

@ -0,0 +1,10 @@
/*
* index.js: Top level include for node-http-proxy macros
*
* (C) 2010 Nodejitsu Inc.
* MIT LICENCE
*
*/
exports.http = require('./http');
exports.ws = require('./ws');

230
test/macros/ws.js Normal file
View File

@ -0,0 +1,230 @@
/*
* ws.js: Macros for proxying Websocket requests
*
* (C) 2010 Nodejitsu Inc.
* MIT LICENCE
*
*/
var assert = require('assert'),
io = require('socket.io-client'),
WebSocket = require('ws'),
helpers = require('../helpers/index');
//
// ### function assertSendRecieve (options)
// #### @options {Object} Options for creating this assertion.
// #### @raw {boolean} Enables raw `ws.WebSocket`.
// #### @uri {string} URI of the proxy server.
// #### @input {string} Input to assert sent to the target ws server.
// #### @output {string} Output to assert from the taget ws server.
//
// Creates a `socket.io` or raw `WebSocket` connection and asserts that
// `options.input` is sent to and `options.output` is received from the
// connection.
//
exports.assertSendReceive = function (options) {
if (!options.raw) {
return {
topic: function () {
var socket = io.connect(options.uri);
socket.on('outgoing', this.callback.bind(this, null));
socket.emit('incoming', options.input);
},
"should send input and receive output": function (_, data) {
assert.equal(data, options.output);
}
}
}
return {
topic: function () {
var socket = new WebSocket(options.uri);
socket.on('message', this.callback.bind(this, null));
socket.on('open', function () {
socket.send(options.input);
});
},
"should send input and recieve output": function (_, data, flags) {
assert.equal(data, options.output);
}
}
}
//
// ### function assertProxied (options)
// #### @options {Object} Options for this test
// #### @latency {number} Latency in milliseconds for the proxy server.
// #### @ports {Object} Ports for the request (target, proxy).
// #### @input {string} Input to assert sent to the target ws server.
// #### @output {string} Output to assert from the taget ws server.
// #### @raw {boolean} Enables raw `ws.Server` usage.
//
// Creates a complete end-to-end test for requesting against an
// http proxy.
//
exports.assertProxied = function (options) {
options = options || {};
var ports = options.ports || helpers.nextPortPair,
input = options.input || 'hello world to ' + ports.target,
output = options.output || 'hello world from ' + ports.target,
protocol = helpers.protocols.proxy;
if (options.raw) {
protocol = helpers.protocols.proxy === 'https'
? 'wss'
: 'ws';
}
return {
topic: function () {
helpers.ws.createServerPair({
target: {
input: input,
output: output,
port: ports.target,
raw: options.raw
},
proxy: {
latency: options.latency,
port: ports.proxy,
proxy: {
target: {
https: helpers.protocols.target === 'https',
host: '127.0.0.1',
port: ports.target
}
}
}
}, this.callback);
},
"the proxy Websocket connection": exports.assertSendReceive({
uri: protocol + '://127.0.0.1:' + ports.proxy,
input: input,
output: output,
raw: options.raw
})
};
};
//
// ### function assertProxiedtoRoutes (options, nested)
// #### @options {Object} Options for this ProxyTable-based test
// #### @raw {boolean} Enables ws.Server usage.
// #### @routes {Object|string} Routes to use for the proxy.
// #### @hostnameOnly {boolean} Enables hostnameOnly routing.
// #### @nested {Object} Nested vows to add to the returned context.
//
// Creates a complete end-to-end test for requesting against an
// http proxy using `options.routes`:
//
// 1. Creates target servers for all routes in `options.routes.`
// 2. Creates a proxy server.
// 3. Ensure Websocket connections to the proxy server for all route targets
// can send input and recieve output.
//
exports.assertProxiedToRoutes = function (options, nested) {
//
// Assign dynamic ports to the routes to use.
//
options.routes = helpers.http.assignPortsToRoutes(options.routes);
//
// Parse locations from routes for making assertion requests.
//
var locations = helpers.http.parseRoutes(options),
protocol = helpers.protocols.proxy,
port = helpers.nextPort,
context,
proxy;
if (options.raw) {
protocol = helpers.protocols.proxy === 'https'
? 'wss'
: 'ws';
}
if (options.filename) {
//
// If we've been passed a filename write the routes to it
// and setup the proxy options to use that file.
//
fs.writeFileSync(options.filename, JSON.stringify({ router: options.routes }));
proxy = { router: options.filename };
}
else {
//
// Otherwise just use the routes themselves.
//
proxy = {
hostnameOnly: options.hostnameOnly,
router: options.routes
};
}
//
// Create the test context which creates all target
// servers for all routes and a proxy server.
//
context = {
topic: function () {
var that = this;
async.waterfall([
//
// 1. Create all the target servers
//
async.apply(
async.forEach,
locations,
function createRouteTarget(location, next) {
helpers.ws.createServer({
raw: options.raw,
port: location.target.port,
output: 'hello from ' + location.source.href,
input: 'hello to ' + location.source.href
}, next);
}
),
//
// 2. Create the proxy server
//
async.apply(
helpers.http.createProxyServer,
{
port: port,
latency: options.latency,
routing: true,
proxy: proxy
}
)
], function (_, server) {
//
// 3. Set the proxy server for later use
//
that.proxyServer = server;
that.callback();
});
//
// 4. Assign the port to the context for later use
//
this.port = port;
}
};
//
// Add test assertions for each of the route locations.
//
locations.forEach(function (location) {
context[location.source.href] = exports.assertSendRecieve({
uri: protocol + '://127.0.0.1:' + port + location.source.path,
output: 'hello from ' + location.source.href,
input: 'hello to ' + location.source.href,
raw: options.raw
});
});
return context;
};

View File

@ -1,171 +0,0 @@
/*
node-http-proxy-test.js: http proxy for node.js
Copyright (c) 2010 Charlie Robbins, Marak Squires and Fedor Indutny
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
var util = require('util'),
assert = require('assert'),
argv = require('optimist').argv,
colors = require('colors'),
request = require('request'),
vows = require('vows'),
websocket = require('../../vendor/websocket'),
helpers = require('../helpers');
try {
var utils = require('socket.io/lib/socket.io/utils'),
io = require('socket.io');
}
catch (ex) {
console.error('Socket.io is required for this example:');
console.error('npm ' + 'install'.green + ' socket.io@0.6.17'.magenta);
process.exit(1);
}
var options = helpers.parseProtocol(),
testName = [options.source.protocols.ws, options.target.protocols.ws].join('-to-'),
runner = new helpers.TestRunner(options);
vows.describe('node-http-proxy/http-proxy/' + testName).addBatch({
"When using server created by httpProxy.createServer()": {
"with no latency" : {
"when an inbound message is sent from a WebSocket client": {
topic: function () {
var that = this
headers = {};
runner.webSocketTest({
io: io,
host: 'localhost',
wsprotocol: options.source.protocols.ws,
protocol: options.source.protocols.http,
ports: {
target: 8130,
proxy: 8131
},
onListen: function (socket) {
socket.on('connection', function (client) {
client.on('message', function (msg) {
that.callback(null, msg, headers);
});
});
},
onWsupgrade: function (req, res) {
headers.request = req;
headers.response = res.headers;
},
onOpen: function (ws) {
ws.send(utils.encode('from client'));
}
});
},
"the target server should receive the message": function (err, msg, headers) {
assert.equal(msg, 'from client');
},
"the origin and sec-websocket-origin headers should match": function (err, msg, headers) {
assert.isString(headers.response['sec-websocket-location']);
assert.isTrue(headers.response['sec-websocket-location'].indexOf(options.source.protocols.ws) !== -1);
assert.equal(headers.request.Origin, headers.response['sec-websocket-origin']);
}
},
"when an inbound message is sent from a WebSocket client with event listeners": {
topic: function () {
var that = this
headers = {};
runner.webSocketTest({
io: io,
host: 'localhost',
wsprotocol: options.source.protocols.ws,
protocol: options.source.protocols.http,
ports: {
target: 8132,
proxy: 8133
},
onServer: function (server) {
server.proxy.on('websocket:incoming', function (req, socket, head, data) {
that.callback(null, data);
});
},
onOpen: function (ws) {
ws.send(utils.encode('from client'));
}
});
},
"should raise the `websocket:incoming` event": function (ign, data) {
assert.equal(utils.decode(data.toString().replace('\u0000', '')), 'from client');
},
},
"when an outbound message is sent from the target server": {
topic: function () {
var that = this,
headers = {};
runner.webSocketTest({
io: io,
host: 'localhost',
wsprotocol: options.source.protocols.ws,
protocol: options.source.protocols.http,
ports: {
target: 8134,
proxy: 8135
},
onListen: function (socket) {
socket.on('connection', function (client) {
socket.broadcast('from server');
});
},
onWsupgrade: function (req, res) {
headers.request = req;
headers.response = res.headers;
},
onMessage: function (msg) {
msg = utils.decode(msg);
if (!/\d+/.test(msg)) {
that.callback(null, msg, headers);
}
}
});
},
"the client should receive the message": function (err, msg, headers) {
assert.equal(msg, 'from server');
},
"the origin and sec-websocket-origin headers should match": function (err, msg, headers) {
assert.isString(headers.response['sec-websocket-location']);
assert.isTrue(headers.response['sec-websocket-location'].indexOf(options.source.protocols.ws) !== -1);
assert.equal(headers.request.Origin, headers.response['sec-websocket-origin']);
}
}
}
}
}).addBatch({
"When the tests are over": {
topic: function () {
return runner.closeServers();
},
"the servers should clean up": function () {
assert.isTrue(true);
}
}
}).export(module);

View File

@ -1,104 +0,0 @@
/*
node-http-proxy-test.js: http proxy for node.js
Copyright (c) 2010 Charlie Robbins, Marak Squires and Fedor Indutny
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
var util = require('util'),
assert = require('assert'),
argv = require('optimist').argv,
colors = require('colors'),
request = require('request'),
vows = require('vows'),
websocket = require('../../vendor/websocket'),
helpers = require('../helpers');
try {
var utils = require('socket.io/lib/socket.io/utils'),
io = require('socket.io');
}
catch (ex) {
console.error('Socket.io is required for this example:');
console.error('npm ' + 'install'.green + ' socket.io@0.6.18'.magenta);
process.exit(1);
}
var options = helpers.parseProtocol(),
testName = [options.source.protocols.ws, options.target.protocols.ws].join('-to-'),
runner = new helpers.TestRunner(options);
vows.describe('node-http-proxy/routing-proxy/' + testName).addBatch({
"When using server created by httpProxy.createServer()": {
"using proxy table with no latency": {
"when an inbound message is sent from a WebSocket client": {
topic: function () {
var that = this
headers = {};
runner.webSocketTestWithTable({
io: io,
host: 'localhost',
wsprotocol: options.source.protocols.ws,
protocol: options.source.protocols.http,
router: { 'localhost' : 'localhost:8230' },
ports: {
target: 8230,
proxy: 8231
},
onListen: function (socket) {
socket.on('connection', function (client) {
client.on('message', function (msg) {
that.callback(null, msg, headers);
});
});
},
onWsupgrade: function (req, res) {
headers.request = req;
headers.response = res.headers;
},
onOpen: function (ws) {
ws.send(utils.encode('from client'));
}
});
},
"the target server should receive the message": function (err, msg, headers) {
assert.equal(msg, 'from client');
},
"the origin and sec-websocket-origin headers should match": function (err, msg, headers) {
assert.isString(headers.response['sec-websocket-location']);
assert.isTrue(headers.response['sec-websocket-location'].indexOf(options.source.protocols.ws) !== -1);
assert.equal(headers.request.Origin, headers.response['sec-websocket-origin']);
}
}
}
}
}).addBatch({
"When the tests are over": {
topic: function () {
return runner.closeServers();
},
"the servers should clean up": function () {
assert.isTrue(true);
}
}
}).export(module);

View File

@ -0,0 +1,25 @@
/*
* routing-tabletest.js: Test for proxying `socket.io` and raw `WebSocket` requests using a ProxyTable.
*
* (C) 2010 Nodejitsu Inc.
* MIT LICENCE
*
*/
var vows = require('vows'),
macros = require('../macros'),
helpers = require('../helpers/index');
vows.describe(helpers.describe('routing-proxy', 'ws')).addBatch({
"With a valid target server": {
"and no latency": {
"using ws": macros.ws.assertProxied(),
"using socket.io": macros.ws.assertProxied({
raw: true
}),
},
// "and latency": macros.websocket.assertProxied({
// latency: 2000
// })
}
}).export(module);

20
test/ws/socket.io-test.js Normal file
View File

@ -0,0 +1,20 @@
/*
* socket.io-test.js: Test for proxying `socket.io` requests.
*
* (C) 2010 Nodejitsu Inc.
* MIT LICENCE
*
*/
var vows = require('vows'),
macros = require('../macros'),
helpers = require('../helpers/index');
vows.describe(helpers.describe('socket.io', 'ws')).addBatch({
"With a valid target server": {
"and no latency": macros.ws.assertProxied(),
// "and latency": macros.ws.assertProxied({
// latency: 2000
// })
}
}).export(module);

23
test/ws/ws-test.js Normal file
View File

@ -0,0 +1,23 @@
/*
* ws-test.js: Tests for proxying raw Websocket requests.
*
* (C) 2010 Nodejitsu Inc.
* MIT LICENCE
*
*/
var vows = require('vows'),
macros = require('../macros'),
helpers = require('../helpers/index');
vows.describe(helpers.describe('websocket', 'ws')).addBatch({
"With a valid target server": {
"and no latency": macros.ws.assertProxied({
raw: true
}),
// "and latency": macros.ws.assertProxied({
// raw: true,
// latency: 2000
// })
}
}).export(module);

636
vendor/websocket.js vendored
View File

@ -1,636 +0,0 @@
/*
* Copyright (c) 2010, Peter Griess <pg@std.in>
* https://github.com/pgriess/node-websocket-client
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of node-websocket-client nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
var assert = require('assert');
var buffer = require('buffer');
var crypto = require('crypto');
var events = require('events');
var http = require('http');
var https = require('https');
var net = require('net');
var urllib = require('url');
var util = require('util');
var FRAME_NO = 0;
var FRAME_LO = 1;
var FRAME_HI = 2;
// Values for readyState as per the W3C spec
var CONNECTING = 0;
var OPEN = 1;
var CLOSING = 2;
var CLOSED = 3;
var debugLevel = parseInt(process.env.NODE_DEBUG, 16);
var debug = (debugLevel & 0x4) ?
function() { util.error.apply(this, arguments); } :
function() { };
// Generate a Sec-WebSocket-* value
var createSecretKey = function() {
// How many spaces will we be inserting?
var numSpaces = 1 + Math.floor(Math.random() * 12);
assert.ok(1 <= numSpaces && numSpaces <= 12);
// What is the numerical value of our key?
var keyVal = (Math.floor(
Math.random() * (4294967295 / numSpaces)
) * numSpaces);
// Our string starts with a string representation of our key
var s = keyVal.toString();
// Insert 'numChars' worth of noise in the character ranges
// [0x21, 0x2f] (14 characters) and [0x3a, 0x7e] (68 characters)
var numChars = 1 + Math.floor(Math.random() * 12);
assert.ok(1 <= numChars && numChars <= 12);
for (var i = 0; i < numChars; i++) {
var pos = Math.floor(Math.random() * s.length + 1);
var c = Math.floor(Math.random() * (14 + 68));
c = (c <= 14) ?
String.fromCharCode(c + 0x21) :
String.fromCharCode((c - 14) + 0x3a);
s = s.substring(0, pos) + c + s.substring(pos, s.length);
}
// We shoudln't have any spaces in our value until we insert them
assert.equal(s.indexOf(' '), -1);
// Insert 'numSpaces' worth of spaces
for (var i = 0; i < numSpaces; i++) {
var pos = Math.floor(Math.random() * (s.length - 1)) + 1;
s = s.substring(0, pos) + ' ' + s.substring(pos, s.length);
}
assert.notEqual(s.charAt(0), ' ');
assert.notEqual(s.charAt(s.length), ' ');
return s;
};
// Generate a challenge sequence
var createChallenge = function() {
var c = '';
for (var i = 0; i < 8; i++) {
c += String.fromCharCode(Math.floor(Math.random() * 255));
}
return c;
};
// Get the value of a secret key string
//
// This strips non-digit values and divides the result by the number of
// spaces found.
var secretKeyValue = function(sk) {
var ns = 0;
var v = 0;
for (var i = 0; i < sk.length; i++) {
var cc = sk.charCodeAt(i);
if (cc == 0x20) {
ns++;
} else if (0x30 <= cc && cc <= 0x39) {
v = v * 10 + cc - 0x30;
}
}
return Math.floor(v / ns);
}
// Get the to-be-hashed value of a secret key string
//
// This takes the result of secretKeyValue() and encodes it in a big-endian
// byte string
var secretKeyHashValue = function(sk) {
var skv = secretKeyValue(sk);
var hv = '';
hv += String.fromCharCode((skv >> 24) & 0xff);
hv += String.fromCharCode((skv >> 16) & 0xff);
hv += String.fromCharCode((skv >> 8) & 0xff);
hv += String.fromCharCode((skv >> 0) & 0xff);
return hv;
};
// Compute the secret key signature based on two secret key strings and some
// handshaking data.
var computeSecretKeySignature = function(s1, s2, hs) {
assert.equal(hs.length, 8);
var hash = crypto.createHash('md5');
hash.update(secretKeyHashValue(s1));
hash.update(secretKeyHashValue(s2));
hash.update(hs);
return hash.digest('binary');
};
// Return a hex representation of the given binary string; used for debugging
var str2hex = function(str) {
var hexChars = [
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'a', 'b', 'c', 'd', 'e', 'f'
];
var out = '';
for (var i = 0; i < str.length; i++) {
var c = str.charCodeAt(i);
out += hexChars[(c & 0xf0) >>> 4];
out += hexChars[c & 0x0f];
out += ' ';
}
return out.trim();
};
// Set a constant on the given object
var setConstant = function(obj, name, value) {
Object.defineProperty(obj, name, {
get : function() {
return value;
}
});
};
// WebSocket object
//
// This is intended to conform (mostly) to http://dev.w3.org/html5/websockets/
//
// N.B. Arguments are parsed in the anonymous function at the bottom of the
// constructor.
var WebSocket = function(url, proto, opts) {
events.EventEmitter.call(this);
// Retain a reference to our object
var self = this;
// State of our end of the connection
var readyState = CONNECTING;
// Whether or not the server has sent a close handshake
var serverClosed = false;
// Our underlying net.Stream instance
var stream = undefined;
opts = opts || {
origin : 'http://www.example.com'
};
// Frame parsing functions
//
// These read data from the given buffer starting at the given offset,
// looking for the end of the current frame. If found, the current frame is
// emitted and the function returns. Only a single frame is processed at a
// time.
//
// The number of bytes read to complete a frame is returned, which the
// caller is to use to advance along its buffer. If 0 is returned, no
// completed frame bytes were found, and the caller should probably enqueue
// the buffer as a continuation of the current message. If a complete frame
// is read, the function is responsible for resting 'frameType'.
// Framing data
var frameType = FRAME_NO;
var bufs = [];
var bufsBytes = 0;
// Frame-parsing functions
var frameFuncs = [
// FRAME_NO
function(buf, off) {
if (buf[off] & 0x80) {
frameType = FRAME_HI;
} else {
frameType = FRAME_LO;
}
return 1;
},
// FRAME_LO
function(buf, off) {
debug('frame_lo(' + util.inspect(buf) + ', ' + off + ')');
// Find the first instance of 0xff, our terminating byte
for (var i = off; i < buf.length && buf[i] != 0xff; i++)
;
// We didn't find a terminating byte
if (i >= buf.length) {
return 0;
}
// We found a terminating byte; collect all bytes into a single buffer
// and emit it
var mb = null;
if (bufs.length == 0) {
mb = buf.slice(off, i);
} else {
mb = new buffer.Buffer(bufsBytes + i);
var mbOff = 0;
bufs.forEach(function(b) {
b.copy(mb, mbOff, 0, b.length);
mbOff += b.length;
});
assert.equal(mbOff, bufsBytes);
// Don't call Buffer.copy() if we're coping 0 bytes. Rather
// than being a no-op, this will trigger a range violation on
// the destination.
if (i > 0) {
buf.copy(mb, mbOff, off, i);
}
// We consumed all of the buffers that we'd been saving; clear
// things out
bufs = [];
bufsBytes = 0;
}
process.nextTick(function() {
var b = mb;
return function() {
var m = b.toString('utf8');
self.emit('data', b);
self.emit('message', m); // wss compat
if (self.onmessage) {
self.onmessage({data: m});
}
};
}());
frameType = FRAME_NO;
return i - off + 1;
},
// FRAME_HI
function(buf, off) {
debug('frame_hi(' + util.inspect(buf) + ', ' + off + ')');
if (buf[off] !== 0) {
throw new Error('High-byte framing not supported.');
}
serverClosed = true;
return 1;
}
];
// Handle data coming from our socket
var dataListener = function(buf) {
if (buf.length <= 0 || serverClosed) {
return;
}
debug('dataListener(' + util.inspect(buf) + ')');
var off = 0;
var consumed = 0;
do {
if (frameType < 0 || frameFuncs.length <= frameType) {
throw new Error('Unexpected frame type: ' + frameType);
}
assert.equal(bufs.length === 0, bufsBytes === 0);
assert.ok(off < buf.length);
consumed = frameFuncs[frameType](buf, off);
off += consumed;
} while (!serverClosed && consumed > 0 && off < buf.length);
if (serverClosed) {
serverCloseHandler();
}
if (consumed == 0) {
bufs.push(buf.slice(off, buf.length));
bufsBytes += buf.length - off;
}
};
// Handle incoming file descriptors
var fdListener = function(fd) {
self.emit('fd', fd);
};
// Handle errors from any source (HTTP client, stream, etc)
var errorListener = function(e) {
process.nextTick(function() {
self.emit('wserror', e);
if (self.onerror) {
self.onerror(e);
}
});
};
// Finish the closing process; destroy the socket and tell the application
// that we've closed.
var finishClose = function() {
readyState = CLOSED;
if (stream) {
stream.end();
stream.destroy();
stream = undefined;
}
process.nextTick(function() {
self.emit('close');
if (self.onclose) {
self.onclose();
}
});
};
// Send a close frame to the server
var sendClose = function() {
assert.equal(OPEN, readyState);
readyState = CLOSING;
stream.write('\xff\x00', 'binary');
};
// Handle a close packet sent from the server
var serverCloseHandler = function() {
assert.ok(serverClosed);
assert.ok(readyState === OPEN || readyState === CLOSING);
bufs = [];
bufsBytes = 0;
// Handle state transitions asynchronously so that we don't change
// readyState before the application has had a chance to process data
// events which are already in the delivery pipeline. For example, a
// 'data' event could be delivered with a readyState of CLOSING if we
// received both frames in the same packet.
process.nextTick(function() {
if (readyState === OPEN) {
sendClose();
}
finishClose();
});
};
// External API
self.close = function(timeout) {
if (readyState === CONNECTING) {
// If we're still in the process of connecting, the server is not
// in a position to understand our close frame. Just nuke the
// connection and call it a day.
finishClose();
} else if (readyState === OPEN) {
sendClose();
if (timeout) {
setTimeout(finishClose, timeout * 1000);
}
}
};
self.send = function(str, fd) {
if (readyState != OPEN) {
return;
}
stream.write('\x00', 'binary');
stream.write(str, 'utf8', fd);
stream.write('\xff', 'binary');
};
// wss compat
self.write = self.send;
setConstant(self, 'url', url);
Object.defineProperty(self, 'readyState', {
get : function() {
return readyState;
}
});
// Connect and perform handshaking with the server
(function() {
// Parse constructor arguments
if (!url) {
throw new Error('Url and must be specified.');
}
// Secrets used for handshaking
var key1 = createSecretKey();
var key2 = createSecretKey();
var challenge = createChallenge();
debug(
'key1=\'' + str2hex(key1) + '\'; ' +
'key2=\'' + str2hex(key2) + '\'; ' +
'challenge=\'' + str2hex(challenge) + '\''
);
var httpHeaders = {
'Connection' : 'Upgrade',
'Upgrade' : 'WebSocket',
'Sec-WebSocket-Key1' : key1,
'Sec-WebSocket-Key2' : key2
};
if (opts.origin) {
httpHeaders['Origin'] = opts.origin;
}
if (proto) {
httpHeaders['Sec-WebSocket-Protocol'] = proto;
}
var httpPath = '/';
// Create the HTTP client that we'll use for handshaking. We'll cannabalize
// its socket via the 'upgrade' event and leave it to rot.
//
// N.B. The ws+unix:// scheme makes use of the implementation detail
// that http.Client passes its constructor arguments through,
// un-inspected to net.Stream.connect(). The latter accepts a
// string as its first argument to connect to a UNIX socket.
var protocol, agent, port, u = urllib.parse(url);
if (u.protocol === 'ws:' || u.protocol === 'wss:') {
protocol = u.protocol === 'ws:' ? http : https;
port = u.protocol === 'ws:' ? 80 : 443;
agent = u.protocol === new protocol.Agent({
host: u.hostname,
port: u.port || port
});
httpPath = (u.pathname || '/') + (u.search || '');
httpHeaders.Host = u.hostname + (u.port ? (":" + u.port) : "");
}
else if (urlScheme === 'ws+unix') {
throw new Error('ws+unix is not implemented');
// var sockPath = url.substring('ws+unix://'.length, url.length);
// httpClient = http.createClient(sockPath);
// httpHeaders.Host = 'localhost';
}
else {
throw new Error('Invalid URL scheme \'' + urlScheme + '\' specified.');
}
var httpReq = protocol.request({
host: u.hostname,
method: 'GET',
agent: agent,
port: u.port,
path: httpPath,
headers: httpHeaders
});
httpReq.on('error', function (e) {
errorListener(e);
});
httpReq.on('upgrade', (function() {
var data = undefined;
return function(res, s, head) {
stream = s;
//
// Emit the `wsupgrade` event to inspect the raw
// arguments returned from the websocket request.
//
self.emit('wsupgrade', httpHeaders, res, s, head);
stream.on('data', function(d) {
if (d.length <= 0) {
return;
}
if (!data) {
data = d;
} else {
var data2 = new buffer.Buffer(data.length + d.length);
data.copy(data2, 0, 0, data.length);
d.copy(data2, data.length, 0, d.length);
data = data2;
}
if (data.length >= 16) {
var expected = computeSecretKeySignature(key1, key2, challenge);
var actual = data.slice(0, 16).toString('binary');
// Handshaking fails; we're donezo
if (actual != expected) {
debug(
'expected=\'' + str2hex(expected) + '\'; ' +
'actual=\'' + str2hex(actual) + '\''
);
process.nextTick(function() {
// N.B. Emit 'wserror' here, as 'error' is a reserved word in the
// EventEmitter world, and gets thrown.
self.emit(
'wserror',
new Error('Invalid handshake from server:' +
'expected \'' + str2hex(expected) + '\', ' +
'actual \'' + str2hex(actual) + '\''
)
);
if (self.onerror) {
self.onerror();
}
finishClose();
});
}
//
// Un-register our data handler and add the one to be used
// for the normal, non-handshaking case. If we have extra
// data left over, manually fire off the handler on
// whatever remains.
//
stream.removeAllListeners('data');
stream.on('data', dataListener);
readyState = OPEN;
process.nextTick(function() {
self.emit('open');
if (self.onopen) {
self.onopen();
}
});
// Consume any leftover data
if (data.length > 16) {
stream.emit('data', data.slice(16, data.length));
}
}
});
stream.on('fd', fdListener);
stream.on('error', errorListener);
stream.on('close', function() {
errorListener(new Error('Stream closed unexpectedly.'));
});
stream.emit('data', head);
};
})());
httpReq.write(challenge, 'binary');
httpReq.end();
})();
};
util.inherits(WebSocket, events.EventEmitter);
exports.WebSocket = WebSocket;
// Add some constants to the WebSocket object
setConstant(WebSocket.prototype, 'CONNECTING', CONNECTING);
setConstant(WebSocket.prototype, 'OPEN', OPEN);
setConstant(WebSocket.prototype, 'CLOSING', CLOSING);
setConstant(WebSocket.prototype, 'CLOSED', CLOSED);
// vim:ts=4 sw=4 et