blob: 23b468b0611b3a20d64710d6ba91025f17772146 [file] [log] [blame]
#!/usr/bin/env node
// === Mock Web Socket Server - for testing the topology view
var fs = require('fs'),
readline = require('readline'),
http = require('http'),
WebSocketServer = require('websocket').server,
port = 8123,
scenarioRoot = 'ev/',
verbose = false, // show received messages from client
extraVerbose = false; // show ALL received messages from client
var lastcmd, // last command executed
lastargs, // arguments to last command
connection, // ws connection
origin, // origin of connection
scid, // scenario ID
scdata, // scenario data
scdone, // shows when scenario is over
eventsById, // map of event file names
maxEvno, // highest loaded event number
autoLast, // last event number for auto-advance
evno, // next event number
evdata; // event data
process.argv.forEach(function (val) {
switch (val) {
case '-v': verbose = true; break;
case '-v!': extraVerbose = true; break;
}
});
var scFiles = fs.readdirSync(scenarioRoot);
console.log();
console.log('Mock Server v1.0');
if (verbose || extraVerbose) {
console.log('Verbose=' + verbose, 'ExtraVerbose=' + extraVerbose);
}
console.log('================');
listScenarios();
var rl = readline.createInterface(process.stdin, process.stdout);
rl.setPrompt('ws> ');
var server = http.createServer(function(request, response) {
console.log((new Date()) + ' Received request for ' + request.url);
response.writeHead(404);
response.end();
});
server.listen(port, function() {
console.log((new Date()) + ' Server is listening on port ' + port);
});
server.on('listening', function () {
console.log('OK, server is running');
console.log('(? for help)');
});
var wsServer = new WebSocketServer({
httpServer: server,
// You should not use autoAcceptConnections for production
// applications, as it defeats all standard cross-origin protection
// facilities built into the protocol and the browser. You should
// *always* verify the connection's origin and decide whether or not
// to accept it.
autoAcceptConnections: false
});
function originIsAllowed(origin) {
// put logic here to detect whether the specified origin is allowed.
return true;
}
// displays the message if our arguments say we should
function displayMsg(msg) {
var ev = JSON.parse(msg);
switch (ev.event) {
case 'topoHeartbeat': return extraVerbose;
default: return true;
}
}
wsServer.on('request', function(request) {
console.log(); // newline after prompt
console.log("Origin: ", request.origin);
if (!originIsAllowed(request.origin)) {
// Make sure we only accept requests from an allowed origin
request.reject();
console.log((new Date()) + ' Connection from origin ' + request.origin + ' rejected.');
return;
}
origin = request.origin;
connection = request.accept(null, origin);
console.log((new Date()) + ' Connection accepted.');
rl.prompt();
connection.on('message', function(message) {
if (verbose || extraVerbose) {
if (message.type === 'utf8') {
if (displayMsg(message.utf8Data)) {
console.log(); // newline after prompt
console.log('Received Message: ' + message.utf8Data);
}
//connection.sendUTF(message.utf8Data);
rl.prompt();
}
else if (message.type === 'binary') {
console.log('Received Binary Message of ' + message.binaryData.length + ' bytes');
//connection.sendBytes(message.binaryData);
}
}
});
connection.on('close', function(reasonCode, description) {
console.log((new Date()) + ' Peer ' + connection.remoteAddress + ' disconnected.');
connection = null;
origin = null;
});
});
setTimeout(doCli, 10); // allow async processes to write to stdout first
function doCli() {
rl.prompt();
rl.on('line', function (line) {
var words = line.trim().split(' '),
cmd = words.shift(),
str = words.join(' ');
if (!cmd) {
// repeat last command
cmd = lastcmd;
str = lastargs;
}
switch(cmd) {
case 'l': listScenarios(); break;
case 'c': connStatus(); break;
case 'm': customMessage(str); break;
case 's': setScenario(str); break;
case 'a': autoAdvance(); break;
case 'n': nextEvent(); break;
case 'r': restartScenario(); break;
case 'q': quit(); break;
case '?': showHelp(); break;
default: console.log('Say what?! (? for help)'); break;
}
lastcmd = cmd;
lastargs = str;
rl.prompt();
}).on('close', function () {
quit();
});
}
var helptext = '\n' +
'l - list scenarios\n' +
'c - show connection status\n' +
'm {text} - send custom message to client\n' +
's {id} - load scenario {id}\n' +
's - show scenario status\n' +
'a - auto-send events\n' +
'n - send next event\n' +
'r - restart the scenario\n' +
'q - exit the server\n' +
'? - display this help text\n';
function showHelp() {
console.log(helptext);
}
function listScenarios() {
console.log('Scenarios ...');
console.log(scFiles.join(', '));
console.log();
}
function connStatus() {
if (connection) {
console.log('Connection from ' + origin + ' established.');
} else {
console.log('No connection.');
}
}
function quit() {
console.log('Quitting...');
process.exit(0);
}
function customMessage(m) {
if (connection) {
console.log('Sending message: ' + m);
connection.sendUTF(m);
} else {
console.warn('No current connection.');
}
}
function showScenarioStatus() {
var msg;
if (!scid) {
console.log('No scenario loaded.');
} else {
msg = 'Scenario: "' + scid + '", ' +
(scdone ? 'DONE' : 'next event: ' + evno);
console.log(msg);
}
}
function scenarioPath(evno) {
var file = evno ? ('/' + eventsById[evno].fname) : '/scenario.json';
return scenarioRoot + scid + file;
}
function initScenario(verb) {
console.log(); // get past prompt
console.log(verb + ' scenario "' + scid + '"');
console.log(scdata.title);
scdata.description.forEach(function (d) {
console.log(' ' + d);
});
autoLast = (scdata.params && scdata.params.lastAuto) || 0;
if (autoLast) {
console.log('[auto-advance: ' + autoLast + ']');
}
evno = 1;
scdone = false;
readEventFilenames();
}
function readEventFilenames() {
var files = fs.readdirSync(scenarioRoot + scid),
eventCount = 0,
match, id, tag;
maxEvno = 0;
eventsById = {};
files.forEach(function (f) {
match = /^ev_(\d+)_(.*)\.json$/.exec(f);
if (match) {
eventCount++;
id = match[1];
tag = match[2];
eventsById[id] = {
fname: f,
num: id,
tag: tag
};
if (Number(id) > Number(maxEvno)) {
maxEvno = id;
}
}
});
console.log('[' + eventCount + ' events loaded, (max=' + maxEvno + ')]');
}
function setScenario(id) {
if (!id) {
return showScenarioStatus();
}
evdata = null;
scid = id;
fs.readFile(scenarioPath(), 'utf8', function (err, data) {
if (err) {
console.warn('No scenario named "' + id + '"', err);
scid = null;
} else {
scdata = JSON.parse(data);
initScenario('Loading');
}
rl.prompt();
});
}
function restartScenario() {
if (!scid) {
console.log('No scenario loaded.');
} else {
initScenario('Restarting');
}
rl.prompt();
}
function eventAvailable() {
if (!scid) {
console.log('No scenario loaded.');
rl.prompt();
return false;
}
if (!connection) {
console.log('No current connection.');
rl.prompt();
return false;
}
if (Number(evno) > Number(maxEvno)) {
scdone = true;
console.log('Scenario DONE.');
return false;
}
return true;
}
function autoAdvance() {
if (evno > autoLast) {
console.log('[auto done]');
return;
}
// need to recurse with a callback, since each event send relies
// on an async load of event data...
function callback() {
if (eventAvailable() && evno <= autoLast) {
_nextEvent(callback);
}
}
callback();
}
function nextEvent() {
if (eventAvailable()) {
_nextEvent();
}
}
function _nextEvent(callback) {
var path = scenarioPath(evno);
fs.readFile(path, 'utf8', function (err, data) {
if (err) {
console.error('Oops error: ' + err);
} else {
evdata = JSON.parse(data);
console.log(); // get past prompt
console.log('Sending event #' + evno + ' [' + evdata.event +
'] from ' + eventsById[evno].fname);
connection.sendUTF(data);
evno++;
if (callback) {
callback();
}
}
rl.prompt();
});
}