232 lines
6.5 KiB
JavaScript

/*
* ws.js: Macros for proxying Websocket requests
*
* (C) 2010 Nodejitsu Inc.
* MIT LICENCE
*
*/
var assert = require('assert'),
fs = require('fs'),
async = require('async'),
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;
};