mirror of
https://github.com/http-party/node-http-proxy.git
synced 2025-12-08 20:59:18 +00:00
Merge branch '0.8.2'
This commit is contained in:
commit
4999b20b84
@ -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)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
14
package.json
14
package.json
@ -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": {
|
||||
|
||||
407
test/helpers.js
407
test/helpers.js
@ -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
165
test/helpers/http.js
Normal 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
105
test/helpers/index.js
Normal 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
112
test/helpers/ws.js
Normal 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);
|
||||
});
|
||||
});
|
||||
};
|
||||
@ -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
57
test/http/http-test.js
Normal 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);
|
||||
@ -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);
|
||||
89
test/http/routing-table-test.js
Normal file
89
test/http/routing-table-test.js
Normal 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
323
test/macros/http.js
Normal 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
10
test/macros/index.js
Normal 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
230
test/macros/ws.js
Normal 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;
|
||||
};
|
||||
@ -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);
|
||||
@ -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);
|
||||
25
test/ws/routing-table-test.js
Normal file
25
test/ws/routing-table-test.js
Normal 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
20
test/ws/socket.io-test.js
Normal 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
23
test/ws/ws-test.js
Normal 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
636
vendor/websocket.js
vendored
@ -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
|
||||
Loading…
x
Reference in New Issue
Block a user