#!/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(); }); }