Simon Hunt

GUI -- Cleaned up websocket code.

 - isolated new WebSocket() call, so we can mock.

Change-Id: Id1225e2c65732e750b289224e838a326c79f02a4
......@@ -21,32 +21,116 @@
'use strict';
// injected refs
var fs, $log;
var $log, $loc, fs, ufs, wsock, vs;
// internal state
var ws, sws, sid = 0,
handlers = {};
var ws = null, // web socket reference
wsUp = false, // web socket is good to go
sid = 0, // event sequence identifier
handlers = {}, // event handler bindings
pendingEvents = [], // events TX'd while socket not up
url; // web socket URL
// ==========================
// === Web socket callbacks
function handleOpen() {
$log.info('Web socket open');
$log.debug('Sending ' + pendingEvents.length + ' pending event(s)...');
pendingEvents.forEach(function (ev) {
_send(ev);
});
pendingEvents = [];
wsUp = true;
}
// Handles the specified (incoming) message using handler bindings.
function handleMessage(msgEvent) {
var ev, h;
try {
ev = JSON.parse(msgEvent.data);
$log.debug(' *Rx* >> ', ev.event, ev.payload);
if (h = handlers[ev.event]) {
h(ev.payload);
} else {
$log.warn('Unhandled event:', ev);
}
} catch (e) {
$log.error('Message.data is (probably) not valid JSON', msgEvent);
}
}
function handleClose() {
$log.info('Web socket closed');
wsUp = false;
// FIXME: implement controller failover logic
// If no controllers left to contact, show the Veil...
vs.show([
'Oops!',
'Web-socket connection to server closed...',
'Try refreshing the page.'
]);
}
// ==============================
// === Private Helper Functions
function _send(ev) {
$log.debug(' *Tx* >> ', ev.event, ev.payload);
ws.send(JSON.stringify(ev));
}
// ===================
// === API Functions
// Required for unit tests to set to known state
function resetSid() {
sid = 0;
}
// Currently supported opts:
// wsport: web socket port (other than default 8181)
function createWebSocket(opts) {
var wsport = (opts && opts.wsport) || null;
url = ufs.wsUrl('core', wsport);
$log.debug('Attempting to open websocket to: ' + url);
ws = wsock.newWebSocket(url);
if (ws) {
ws.onopen = handleOpen;
ws.onmessage = handleMessage;
ws.onclose = handleClose;
}
// Note: Wsock logs an error if the new WebSocket call fails
}
// Binds the specified message handlers.
// keys are the event IDs
// values are the API on which the handler function is a property
function bindHandlers(handlerMap) {
var m = d3.map(handlerMap),
dups = [];
m.forEach(function (key, value) {
var fn = fs.isF(value[key]);
m.forEach(function (eventId, api) {
var fn = fs.isF(api[eventId]);
if (!fn) {
$log.warn(key + ' binding not a function on ' + value);
$log.warn(eventId + ' handler not a function');
return;
}
if (handlers[key]) {
dups.push(key);
if (handlers[eventId]) {
dups.push(eventId);
} else {
handlers[key] = fn;
handlers[eventId] = fn;
}
});
if (dups.length) {
......@@ -55,116 +139,46 @@
}
// Unbinds the specified message handlers.
// Expected that the same map will be used, but we only care about keys
function unbindHandlers(handlerMap) {
var m = d3.map(handlerMap);
m.forEach(function (key) {
delete handlers[key];
m.forEach(function (eventId) {
delete handlers[eventId];
});
}
// Formulates an event message and sends it via the shared web-socket.
// Formulates an event message and sends it via the web-socket.
// If the websocket is not up yet, we store it in a pending list.
function sendEvent(evType, payload) {
var p = payload || {};
if (sws) {
$log.debug(' *Tx* >> ', evType, payload);
sws.send({
var ev = {
event: evType,
sid: ++sid,
payload: p
});
} else {
$log.warn('sendEvent: no websocket open:', evType, payload);
}
}
// Handles the specified message using handler bindings.
function handleMessage(msgEvent) {
var ev;
try {
ev = JSON.parse(msgEvent.data);
$log.debug(' *Rx* >> ', ev.event, ev.payload);
dispatchToHandler(ev);
} catch (e) {
$log.error('message is not valid JSON', msgEvent);
}
}
payload: payload || {}
};
// Dispatches the message to the appropriate handler.
function dispatchToHandler(event) {
var handler = handlers[event.event];
if (handler) {
handler(event.payload);
if (wsUp) {
_send(ev);
} else {
$log.warn('unhandled event:', event);
pendingEvents.push(ev);
}
}
function handleOpen() {
$log.info('web socket open');
// FIXME: implement calling external hooks
}
function handleClose() {
$log.info('web socket closed');
// FIXME: implement reconnect logic
}
// ============================
// ===== Definition of module
angular.module('onosRemote')
.factory('WebSocketService',
['$log', '$location', 'UrlFnService', 'FnService',
['$log', '$location', 'FnService', 'UrlFnService', 'WSock',
'VeilService',
function (_$log_, $loc, ufs, _fs_) {
fs = _fs_;
function (_$log_, _$loc_, _fs_, _ufs_, _wsock_, _vs_) {
$log = _$log_;
// Creates a web socket for the given path, returning a "handle".
// opts contains the event handler callbacks, etc.
function createWebSocket(path, opts) {
var o = opts || {},
wsport = opts && opts.wsport,
fullUrl = ufs.wsUrl(path, wsport),
api = {
meta: { path: fullUrl, ws: null },
send: send,
close: close
};
try {
ws = new WebSocket(fullUrl);
api.meta.ws = ws;
} catch (e) {
}
$log.debug('Attempting to open websocket to: ' + fullUrl);
if (ws) {
ws.onopen = handleOpen;
ws.onmessage = handleMessage;
ws.onclose = handleClose;
}
// Sends a formulated event message via the backing web-socket.
function send(ev) {
if (ev && ws) {
ws.send(JSON.stringify(ev));
} else if (!ws) {
$log.warn('ws.send() no web socket open!', fullUrl, ev);
}
}
// Closes the backing web-socket.
function close() {
if (ws) {
ws.close();
ws = null;
api.meta.ws = null;
}
}
sws = api; // Make the shared web-socket accessible
return api;
}
$loc = _$loc_;
fs = _fs_;
ufs = _ufs_;
wsock = _wsock_;
vs = _vs_;
return {
resetSid: resetSid,
......@@ -173,6 +187,7 @@
unbindHandlers: unbindHandlers,
sendEvent: sendEvent
};
}]);
}
]);
}());
......
/*
* Copyright 2015 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
ONOS GUI -- Remote -- Web Socket Wrapper Service
This service provided specifically so that it can be mocked in unit tests.
*/
(function () {
'use strict';
angular.module('onosRemote')
.factory('WSock', ['$log', function ($log) {
function newWebSocket(url) {
var ws = null;
try {
ws = new WebSocket(url);
} catch (e) {
$log.error('Unable to create web socket:', e);
}
return ws;
}
return {
newWebSocket: newWebSocket
};
}]);
}());
......@@ -27,15 +27,15 @@
'use strict';
// injected refs
var $log, vs, wss, tps, tis, tfs, tss, tts;
var $log, wss, tps, tis, tfs, tss, tts;
// internal state
var handlers;
var handlerMap;
// ==========================
function createHandlers() {
handlers = {
function createHandlerMap() {
handlerMap = {
showSummary: tps,
showDetails: tss,
......@@ -58,17 +58,14 @@
};
}
var nilApi = {};
angular.module('ovTopo')
.factory('TopoEventService',
['$log', '$location', 'VeilService', 'WebSocketService',
['$log', '$location', 'WebSocketService',
'TopoPanelService', 'TopoInstService', 'TopoForceService',
'TopoSelectService', 'TopoTrafficService',
function (_$log_, $loc, _vs_, _wss_, _tps_, _tis_, _tfs_, _tss_, _tts_) {
function (_$log_, $loc, _wss_, _tps_, _tis_, _tfs_, _tss_, _tts_) {
$log = _$log_;
vs = _vs_;
wss = _wss_;
tps = _tps_;
tis = _tis_;
......@@ -76,18 +73,18 @@
tss = _tss_;
tts = _tts_;
createHandlers();
createHandlerMap();
// FIXME: need to handle async socket open to avoid race
function start() {
wss.bindHandlers(handlers);
wss.bindHandlers(handlerMap);
wss.sendEvent('topoStart');
wss.sendEvent('requestSummary');
$log.debug('topo comms started');
}
function stop() {
wss.unbindHandlers();
wss.sendEvent('topoStop');
wss.unbindHandlers(handlerMap);
$log.debug('topo comms stopped');
}
......
......@@ -53,8 +53,8 @@
<script src="app/fw/remote/remote.js"></script>
<script src="app/fw/remote/urlfn.js"></script>
<script src="app/fw/remote/rest.js"></script>
<script src="app/fw/remote/wsock.js"></script>
<script src="app/fw/remote/websocket.js"></script>
<script src="app/fw/remote/wsevent.js"></script>
<script src="app/fw/widget/widget.js"></script>
<script src="app/fw/widget/table.js"></script>
......
......@@ -84,10 +84,9 @@
flash.initFlash();
qhs.initQuickHelp();
// TODO: register handlers for initial messages: instances, settings, etc.
// TODO: register handler for user settings, etc.
// TODO: opts?
wss.createWebSocket('core', {
wss.createWebSocket({
wsport: $location.search().wsport
});
......