Thomas Vachuska

Deprecating old web-socket stuff and adding ability for client-side message hand…

…ler registration. Failover still to be done and same for the async hooks.

Change-Id: I6029c91eb1a04e01401e495b9673ddaea728e215
......@@ -31,6 +31,7 @@ import java.util.TimerTask;
/**
* Web socket servlet capable of creating various sockets for the user interface.
*/
@Deprecated
public class GuiWebSocketServlet extends WebSocketServlet {
private static final long PING_DELAY_MS = 5000;
......
......@@ -98,6 +98,7 @@ import static org.onosproject.net.link.LinkEvent.Type.LINK_REMOVED;
/**
* Facility for creating messages bound for the topology viewer.
*/
@Deprecated
public abstract class TopologyViewMessages {
protected static final Logger log = LoggerFactory.getLogger(TopologyViewMessages.class);
......
......@@ -77,6 +77,7 @@ import static org.onosproject.net.link.LinkEvent.Type.LINK_ADDED;
/**
* Web socket capable of interacting with the GUI topology view.
*/
@Deprecated
public class TopologyViewWebSocket
extends TopologyViewMessages
implements WebSocket.OnTextMessage, WebSocket.OnControl {
......
......@@ -59,7 +59,7 @@ public class UiExtensionManager implements UiExtensionService {
List<UiView> coreViews = of(new UiView("sample", "Sample"),
new UiView("topo", "Topology View"),
new UiView("device", "Devices"));
UiMessageHandlerFactory messageHandlerFactory = null;
UiMessageHandlerFactory messageHandlerFactory = () -> ImmutableList.of(new TopologyViewMessageHandler());
return new UiExtension(coreViews, messageHandlerFactory, "core",
UiExtensionManager.class.getClassLoader());
}
......
......@@ -22,6 +22,7 @@ import org.onlab.osgi.ServiceDirectory;
import org.onosproject.ui.UiConnection;
import org.onosproject.ui.UiExtensionService;
import org.onosproject.ui.UiMessageHandler;
import org.onosproject.ui.UiMessageHandlerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -117,7 +118,7 @@ public class UiWebSocket
lastActive = System.currentTimeMillis();
try {
ObjectNode message = (ObjectNode) mapper.reader().readTree(data);
String type = message.path("type").asText("unknown");
String type = message.path("event").asText("unknown");
UiMessageHandler handler = handlers.get(type);
if (handler != null) {
handler.process(message);
......@@ -146,10 +147,15 @@ public class UiWebSocket
private void createHandlers() {
handlers = new HashMap<>();
UiExtensionService service = directory.get(UiExtensionService.class);
service.getExtensions().forEach(ext -> ext.messageHandlerFactory().newHandlers().forEach(handler -> {
handler.init(this, directory);
handler.messageTypes().forEach(type -> handlers.put(type, handler));
}));
service.getExtensions().forEach(ext -> {
UiMessageHandlerFactory factory = ext.messageHandlerFactory();
if (factory != null) {
factory.newHandlers().forEach(handler -> {
handler.init(this, directory);
handler.messageTypes().forEach(type -> handlers.put(type, handler));
});
}
});
}
// Destroys message handlers.
......
......@@ -151,12 +151,24 @@
<servlet>
<servlet-name>Web Socket Service</servlet-name>
<servlet-class>org.onosproject.ui.impl.GuiWebSocketServlet</servlet-class>
<servlet-class>org.onosproject.ui.impl.UiWebSocketServlet</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Web Socket Service</servlet-name>
<url-pattern>/websock/*</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>Legacy Web Socket Service</servlet-name>
<servlet-class>org.onosproject.ui.impl.GuiWebSocketServlet</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Legacy Web Socket Service</servlet-name>
<url-pattern>/ws/*</url-pattern>
</servlet-mapping>
......
......@@ -22,7 +22,7 @@
var uiContext = '/onos/ui/',
rsSuffix = uiContext + 'rs/',
wsSuffix = uiContext + 'ws/';
wsSuffix = uiContext + 'websock/';
angular.module('onosRemote')
.factory('UrlFnService', ['$location', function ($loc) {
......
......@@ -20,62 +20,105 @@
(function () {
'use strict';
var fs;
// injected refs
var fs, $log;
function fnOpen(f) {
// wrap the onOpen function; we will handle any housekeeping here...
if (!fs.isF(f)) {
return null;
// internal state
var ws, sws, sid = 0,
handlers = {};
function resetSid() {
sid = 0;
}
// Binds the specified message handlers.
function bindHandlers(handlerMap) {
var m = d3.map(handlerMap),
dups = [];
m.forEach(function (key, value) {
var fn = fs.isF(value[key]);
if (!fn) {
$log.warn(key + ' binding not a function on ' + value);
return;
}
if (handlers[key]) {
dups.push(key);
} else {
handlers[key] = fn;
}
});
if (dups.length) {
$log.warn('duplicate bindings ignored:', dups);
}
}
return function (openEvent) {
// NOTE: nothing worth passing to the caller?
f();
};
// Unbinds the specified message handlers.
function unbindHandlers(handlerMap) {
var m = d3.map(handlerMap);
m.forEach(function (key) {
delete handlers[key];
});
}
function fnMessage(f) {
// wrap the onMessage function; we will attempt to decode the
// message event payload as JSON and pass that in...
if (!fs.isF(f)) {
return null;
// Formulates an event message and sends it via the shared web-socket.
function sendEvent(evType, payload) {
var p = payload || {};
if (sws) {
$log.debug(' *Tx* >> ', evType, payload);
sws.send({
event: evType,
sid: ++sid,
payload: p
});
} else {
$log.warn('sendEvent: no websocket open:', evType, payload);
}
}
return function (msgEvent) {
var ev;
try {
ev = JSON.parse(msgEvent.data);
} catch (e) {
ev = {
error: 'Failed to parse JSON',
e: e
};
}
f(ev);
};
// 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);
}
}
function fnClose(f) {
// wrap the onClose function; we will handle any parameters to the
// close event here...
if (!fs.isF(f)) {
return null;
// Dispatches the message to the appropriate handler.
function dispatchToHandler(event) {
var handler = handlers[event.event];
if (handler) {
handler(event.payload);
} else {
$log.warn('unhandled event:', event);
}
}
function handleOpen() {
$log.info('web socket open');
// FIXME: implement calling external hooks
}
return function (closeEvent) {
// NOTE: only seen {reason == ""} so far, nevertheless...
f(closeEvent.reason);
};
function handleClose() {
$log.info('web socket closed');
// FIXME: implement reconnect logic
}
angular.module('onosRemote')
.factory('WebSocketService',
['$log', '$location', 'UrlFnService', 'FnService',
function ($log, $loc, ufs, _fs_) {
function (_$log_, $loc, ufs, _fs_) {
fs = _fs_;
$log = _$log_;
// creates a web socket for the given path, returning a "handle".
// 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 || {},
......@@ -85,8 +128,7 @@
meta: { path: fullUrl, ws: null },
send: send,
close: close
},
ws;
};
try {
ws = new WebSocket(fullUrl);
......@@ -97,23 +139,21 @@
$log.debug('Attempting to open websocket to: ' + fullUrl);
if (ws) {
ws.onopen = fnOpen(o.onOpen);
ws.onmessage = fnMessage(o.onMessage);
ws.onclose = fnClose(o.onClose);
ws.onopen = handleOpen;
ws.onmessage = handleMessage;
ws.onclose = handleClose;
}
// messages are expected to be event objects..
// Sends a formulated event message via the backing web-socket.
function send(ev) {
if (ev) {
if (ws) {
ws.send(JSON.stringify(ev));
} else {
$log.warn('ws.send() no web socket open!',
fullUrl, 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();
......@@ -122,11 +162,16 @@
}
}
sws = api; // Make the shared web-socket accessible
return api;
}
return {
createWebSocket: createWebSocket
resetSid: resetSid,
createWebSocket: createWebSocket,
bindHandlers: bindHandlers,
unbindHandlers: unbindHandlers,
sendEvent: sendEvent
};
}]);
......
......@@ -15,6 +15,7 @@
*/
/*
DEPRECATED: to be deleted
ONOS GUI -- Remote -- Web Socket Event Service
*/
(function () {
......
......@@ -263,7 +263,7 @@
// Cleanup on destroyed scope..
$scope.$on('$destroy', function () {
$log.log('OvTopoCtrl is saying Buh-Bye!');
tes.closeSock();
tes.stop();
tps.destroyPanels();
tis.destroyInst();
tfs.destroyForce();
......@@ -291,7 +291,7 @@
tfs.initForce(svg, forceG, uplink, dim);
tis.initInst({ showMastership: tfs.showMastership });
tps.initPanels({ sendEvent: tes.sendEvent });
tes.openSock();
tes.start();
$log.log('OvTopoCtrl has been created');
}]);
......
......@@ -27,15 +27,15 @@
'use strict';
// injected refs
var $log, wss, wes, vs, tps, tis, tfs, tss, tts;
var $log, vs, wss, tps, tis, tfs, tss, tts;
// internal state
var wsock, evApis;
var handlers;
// ==========================
function bindApis() {
evApis = {
function createHandlers() {
handlers = {
showSummary: tps,
showDetails: tss,
......@@ -58,103 +58,43 @@
};
}
var nilApi = {},
dispatcher = {
handleEvent: function (ev) {
var eid = ev.event,
api = evApis[eid] || nilApi,
eh = api[eid];
if (eh) {
$log.debug(' << *Rx* ', eid, ev.payload);
eh(ev.payload);
} else {
$log.warn('Unknown event (ignored):', ev);
}
},
sendEvent: function (evType, payload) {
if (wsock) {
$log.debug(' *Tx* >> ', evType, payload);
wes.sendEvent(wsock, evType, payload);
} else {
$log.warn('sendEvent: no websocket open:', evType, payload);
}
}
};
// === Web Socket functions ===
function onWsOpen() {
$log.debug('web socket opened...');
// start by requesting periodic summary data...
dispatcher.sendEvent('requestSummary');
vs.hide();
}
function onWsMessage(ev) {
dispatcher.handleEvent(ev);
}
function onWsClose(reason) {
$log.log('web socket closed; reason=', reason);
wsock = null;
vs.lostServer('OvTopoCtrl', [
'Oops!',
'Web-socket connection to server closed...',
'Try refreshing the page.'
]);
}
// ==========================
var nilApi = {};
angular.module('ovTopo')
.factory('TopoEventService',
['$log', '$location', 'WebSocketService', 'WsEventService', 'VeilService',
['$log', '$location', 'VeilService', 'WebSocketService',
'TopoPanelService', 'TopoInstService', 'TopoForceService',
'TopoSelectService', 'TopoTrafficService',
function (_$log_, $loc, _wss_, _wes_, _vs_,
_tps_, _tis_, _tfs_, _tss_, _tts_) {
function (_$log_, $loc, _vs_, _wss_, _tps_, _tis_, _tfs_, _tss_, _tts_) {
$log = _$log_;
wss = _wss_;
wes = _wes_;
vs = _vs_;
wss = _wss_;
tps = _tps_;
tis = _tis_;
tfs = _tfs_;
tss = _tss_;
tts = _tts_;
bindApis();
// TODO: handle "guiSuccessor" functionality (replace host)
// TODO: implement retry on close functionality
createHandlers();
function openSock() {
wsock = wss.createWebSocket('topology', {
onOpen: onWsOpen,
onMessage: onWsMessage,
onClose: onWsClose,
wsport: $loc.search().wsport
});
$log.debug('web socket opened:', wsock);
// FIXME: need to handle async socket open to avoid race
function start() {
wss.bindHandlers(handlers);
wss.sendEvent('topoStart');
$log.debug('topo comms started');
}
function closeSock() {
var path;
if (wsock) {
path = wsock.meta.path;
wsock.close();
wsock = null;
$log.debug('web socket closed. path:', path);
}
function stop() {
wss.unbindHandlers();
wss.sendEvent('topoStop');
$log.debug('topo comms stopped');
}
return {
openSock: openSock,
closeSock: closeSock,
sendEvent: dispatcher.sendEvent
start: start,
stop: stop,
sendEvent: wss.sendEvent
};
}]);
}());
......
......@@ -64,10 +64,10 @@
.controller('OnosCtrl', [
'$log', '$route', '$routeParams', '$location',
'KeyService', 'ThemeService', 'GlyphService', 'PanelService',
'FlashService', 'QuickHelpService',
'FlashService', 'QuickHelpService', 'WebSocketService',
function ($log, $route, $routeParams, $location,
ks, ts, gs, ps, flash, qhs) {
ks, ts, gs, ps, flash, qhs, wss) {
var self = this;
self.$route = $route;
......@@ -84,6 +84,13 @@
flash.initFlash();
qhs.initQuickHelp();
// TODO: register handlers for initial messages: instances, settings, etc.
// TODO: opts?
wss.createWebSocket('core', {
wsport: $location.search().wsport
});
$log.log('OnosCtrl has been created');
$log.debug('route: ', self.$route);
......