Simon Hunt

GUI -- Cleaned up websocket code.

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

Change-Id: Id1225e2c65732e750b289224e838a326c79f02a4
...@@ -21,32 +21,116 @@ ...@@ -21,32 +21,116 @@
21 'use strict'; 21 'use strict';
22 22
23 // injected refs 23 // injected refs
24 - var fs, $log; 24 + var $log, $loc, fs, ufs, wsock, vs;
25 25
26 // internal state 26 // internal state
27 - var ws, sws, sid = 0, 27 + var ws = null, // web socket reference
28 - handlers = {}; 28 + wsUp = false, // web socket is good to go
29 + sid = 0, // event sequence identifier
30 + handlers = {}, // event handler bindings
31 + pendingEvents = [], // events TX'd while socket not up
32 + url; // web socket URL
29 33
34 +
35 + // ==========================
36 + // === Web socket callbacks
37 +
38 + function handleOpen() {
39 + $log.info('Web socket open');
40 + $log.debug('Sending ' + pendingEvents.length + ' pending event(s)...');
41 + pendingEvents.forEach(function (ev) {
42 + _send(ev);
43 + });
44 + pendingEvents = [];
45 + wsUp = true;
46 + }
47 +
48 + // Handles the specified (incoming) message using handler bindings.
49 + function handleMessage(msgEvent) {
50 + var ev, h;
51 +
52 + try {
53 + ev = JSON.parse(msgEvent.data);
54 + $log.debug(' *Rx* >> ', ev.event, ev.payload);
55 +
56 + if (h = handlers[ev.event]) {
57 + h(ev.payload);
58 + } else {
59 + $log.warn('Unhandled event:', ev);
60 + }
61 +
62 + } catch (e) {
63 + $log.error('Message.data is (probably) not valid JSON', msgEvent);
64 + }
65 + }
66 +
67 + function handleClose() {
68 + $log.info('Web socket closed');
69 + wsUp = false;
70 +
71 + // FIXME: implement controller failover logic
72 +
73 + // If no controllers left to contact, show the Veil...
74 + vs.show([
75 + 'Oops!',
76 + 'Web-socket connection to server closed...',
77 + 'Try refreshing the page.'
78 + ]);
79 + }
80 +
81 +
82 + // ==============================
83 + // === Private Helper Functions
84 +
85 + function _send(ev) {
86 + $log.debug(' *Tx* >> ', ev.event, ev.payload);
87 + ws.send(JSON.stringify(ev));
88 + }
89 +
90 +
91 + // ===================
92 + // === API Functions
93 +
94 + // Required for unit tests to set to known state
30 function resetSid() { 95 function resetSid() {
31 sid = 0; 96 sid = 0;
32 } 97 }
33 98
99 + // Currently supported opts:
100 + // wsport: web socket port (other than default 8181)
101 + function createWebSocket(opts) {
102 + var wsport = (opts && opts.wsport) || null;
103 +
104 + url = ufs.wsUrl('core', wsport);
105 +
106 + $log.debug('Attempting to open websocket to: ' + url);
107 + ws = wsock.newWebSocket(url);
108 + if (ws) {
109 + ws.onopen = handleOpen;
110 + ws.onmessage = handleMessage;
111 + ws.onclose = handleClose;
112 + }
113 + // Note: Wsock logs an error if the new WebSocket call fails
114 + }
115 +
34 // Binds the specified message handlers. 116 // Binds the specified message handlers.
117 + // keys are the event IDs
118 + // values are the API on which the handler function is a property
35 function bindHandlers(handlerMap) { 119 function bindHandlers(handlerMap) {
36 var m = d3.map(handlerMap), 120 var m = d3.map(handlerMap),
37 dups = []; 121 dups = [];
38 122
39 - m.forEach(function (key, value) { 123 + m.forEach(function (eventId, api) {
40 - var fn = fs.isF(value[key]); 124 + var fn = fs.isF(api[eventId]);
41 if (!fn) { 125 if (!fn) {
42 - $log.warn(key + ' binding not a function on ' + value); 126 + $log.warn(eventId + ' handler not a function');
43 return; 127 return;
44 } 128 }
45 129
46 - if (handlers[key]) { 130 + if (handlers[eventId]) {
47 - dups.push(key); 131 + dups.push(eventId);
48 } else { 132 } else {
49 - handlers[key] = fn; 133 + handlers[eventId] = fn;
50 } 134 }
51 }); 135 });
52 if (dups.length) { 136 if (dups.length) {
...@@ -55,116 +139,46 @@ ...@@ -55,116 +139,46 @@
55 } 139 }
56 140
57 // Unbinds the specified message handlers. 141 // Unbinds the specified message handlers.
142 + // Expected that the same map will be used, but we only care about keys
58 function unbindHandlers(handlerMap) { 143 function unbindHandlers(handlerMap) {
59 var m = d3.map(handlerMap); 144 var m = d3.map(handlerMap);
60 - m.forEach(function (key) { 145 +
61 - delete handlers[key]; 146 + m.forEach(function (eventId) {
147 + delete handlers[eventId];
62 }); 148 });
63 } 149 }
64 150
65 - // Formulates an event message and sends it via the shared web-socket. 151 + // Formulates an event message and sends it via the web-socket.
152 + // If the websocket is not up yet, we store it in a pending list.
66 function sendEvent(evType, payload) { 153 function sendEvent(evType, payload) {
67 - var p = payload || {}; 154 + var ev = {
68 - if (sws) {
69 - $log.debug(' *Tx* >> ', evType, payload);
70 - sws.send({
71 event: evType, 155 event: evType,
72 sid: ++sid, 156 sid: ++sid,
73 - payload: p 157 + payload: payload || {}
74 - }); 158 + };
75 - } else {
76 - $log.warn('sendEvent: no websocket open:', evType, payload);
77 - }
78 - }
79 -
80 -
81 - // Handles the specified message using handler bindings.
82 - function handleMessage(msgEvent) {
83 - var ev;
84 - try {
85 - ev = JSON.parse(msgEvent.data);
86 - $log.debug(' *Rx* >> ', ev.event, ev.payload);
87 - dispatchToHandler(ev);
88 - } catch (e) {
89 - $log.error('message is not valid JSON', msgEvent);
90 - }
91 - }
92 159
93 - // Dispatches the message to the appropriate handler. 160 + if (wsUp) {
94 - function dispatchToHandler(event) { 161 + _send(ev);
95 - var handler = handlers[event.event];
96 - if (handler) {
97 - handler(event.payload);
98 } else { 162 } else {
99 - $log.warn('unhandled event:', event); 163 + pendingEvents.push(ev);
100 - }
101 } 164 }
102 -
103 - function handleOpen() {
104 - $log.info('web socket open');
105 - // FIXME: implement calling external hooks
106 } 165 }
107 166
108 - function handleClose() {
109 - $log.info('web socket closed');
110 - // FIXME: implement reconnect logic
111 - }
112 167
168 + // ============================
169 + // ===== Definition of module
113 angular.module('onosRemote') 170 angular.module('onosRemote')
114 .factory('WebSocketService', 171 .factory('WebSocketService',
115 - ['$log', '$location', 'UrlFnService', 'FnService', 172 + ['$log', '$location', 'FnService', 'UrlFnService', 'WSock',
173 + 'VeilService',
116 174
117 - function (_$log_, $loc, ufs, _fs_) { 175 + function (_$log_, _$loc_, _fs_, _ufs_, _wsock_, _vs_) {
118 - fs = _fs_;
119 $log = _$log_; 176 $log = _$log_;
120 - 177 + $loc = _$loc_;
121 - // Creates a web socket for the given path, returning a "handle". 178 + fs = _fs_;
122 - // opts contains the event handler callbacks, etc. 179 + ufs = _ufs_;
123 - function createWebSocket(path, opts) { 180 + wsock = _wsock_;
124 - var o = opts || {}, 181 + vs = _vs_;
125 - wsport = opts && opts.wsport,
126 - fullUrl = ufs.wsUrl(path, wsport),
127 - api = {
128 - meta: { path: fullUrl, ws: null },
129 - send: send,
130 - close: close
131 - };
132 -
133 - try {
134 - ws = new WebSocket(fullUrl);
135 - api.meta.ws = ws;
136 - } catch (e) {
137 - }
138 -
139 - $log.debug('Attempting to open websocket to: ' + fullUrl);
140 -
141 - if (ws) {
142 - ws.onopen = handleOpen;
143 - ws.onmessage = handleMessage;
144 - ws.onclose = handleClose;
145 - }
146 -
147 - // Sends a formulated event message via the backing web-socket.
148 - function send(ev) {
149 - if (ev && ws) {
150 - ws.send(JSON.stringify(ev));
151 - } else if (!ws) {
152 - $log.warn('ws.send() no web socket open!', fullUrl, ev);
153 - }
154 - }
155 -
156 - // Closes the backing web-socket.
157 - function close() {
158 - if (ws) {
159 - ws.close();
160 - ws = null;
161 - api.meta.ws = null;
162 - }
163 - }
164 -
165 - sws = api; // Make the shared web-socket accessible
166 - return api;
167 - }
168 182
169 return { 183 return {
170 resetSid: resetSid, 184 resetSid: resetSid,
...@@ -173,6 +187,7 @@ ...@@ -173,6 +187,7 @@
173 unbindHandlers: unbindHandlers, 187 unbindHandlers: unbindHandlers,
174 sendEvent: sendEvent 188 sendEvent: sendEvent
175 }; 189 };
176 - }]); 190 + }
191 + ]);
177 192
178 }()); 193 }());
......
1 +/*
2 + * Copyright 2015 Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +
17 +/*
18 + ONOS GUI -- Remote -- Web Socket Wrapper Service
19 +
20 + This service provided specifically so that it can be mocked in unit tests.
21 + */
22 +(function () {
23 + 'use strict';
24 +
25 + angular.module('onosRemote')
26 + .factory('WSock', ['$log', function ($log) {
27 +
28 + function newWebSocket(url) {
29 + var ws = null;
30 + try {
31 + ws = new WebSocket(url);
32 + } catch (e) {
33 + $log.error('Unable to create web socket:', e);
34 + }
35 + return ws;
36 + }
37 +
38 + return {
39 + newWebSocket: newWebSocket
40 + };
41 + }]);
42 +}());
...@@ -27,15 +27,15 @@ ...@@ -27,15 +27,15 @@
27 'use strict'; 27 'use strict';
28 28
29 // injected refs 29 // injected refs
30 - var $log, vs, wss, tps, tis, tfs, tss, tts; 30 + var $log, wss, tps, tis, tfs, tss, tts;
31 31
32 // internal state 32 // internal state
33 - var handlers; 33 + var handlerMap;
34 34
35 // ========================== 35 // ==========================
36 36
37 - function createHandlers() { 37 + function createHandlerMap() {
38 - handlers = { 38 + handlerMap = {
39 showSummary: tps, 39 showSummary: tps,
40 40
41 showDetails: tss, 41 showDetails: tss,
...@@ -58,17 +58,14 @@ ...@@ -58,17 +58,14 @@
58 }; 58 };
59 } 59 }
60 60
61 - var nilApi = {};
62 -
63 angular.module('ovTopo') 61 angular.module('ovTopo')
64 .factory('TopoEventService', 62 .factory('TopoEventService',
65 - ['$log', '$location', 'VeilService', 'WebSocketService', 63 + ['$log', '$location', 'WebSocketService',
66 'TopoPanelService', 'TopoInstService', 'TopoForceService', 64 'TopoPanelService', 'TopoInstService', 'TopoForceService',
67 'TopoSelectService', 'TopoTrafficService', 65 'TopoSelectService', 'TopoTrafficService',
68 66
69 - function (_$log_, $loc, _vs_, _wss_, _tps_, _tis_, _tfs_, _tss_, _tts_) { 67 + function (_$log_, $loc, _wss_, _tps_, _tis_, _tfs_, _tss_, _tts_) {
70 $log = _$log_; 68 $log = _$log_;
71 - vs = _vs_;
72 wss = _wss_; 69 wss = _wss_;
73 tps = _tps_; 70 tps = _tps_;
74 tis = _tis_; 71 tis = _tis_;
...@@ -76,18 +73,18 @@ ...@@ -76,18 +73,18 @@
76 tss = _tss_; 73 tss = _tss_;
77 tts = _tts_; 74 tts = _tts_;
78 75
79 - createHandlers(); 76 + createHandlerMap();
80 77
81 - // FIXME: need to handle async socket open to avoid race
82 function start() { 78 function start() {
83 - wss.bindHandlers(handlers); 79 + wss.bindHandlers(handlerMap);
84 wss.sendEvent('topoStart'); 80 wss.sendEvent('topoStart');
81 + wss.sendEvent('requestSummary');
85 $log.debug('topo comms started'); 82 $log.debug('topo comms started');
86 } 83 }
87 84
88 function stop() { 85 function stop() {
89 - wss.unbindHandlers();
90 wss.sendEvent('topoStop'); 86 wss.sendEvent('topoStop');
87 + wss.unbindHandlers(handlerMap);
91 $log.debug('topo comms stopped'); 88 $log.debug('topo comms stopped');
92 } 89 }
93 90
......
...@@ -53,8 +53,8 @@ ...@@ -53,8 +53,8 @@
53 <script src="app/fw/remote/remote.js"></script> 53 <script src="app/fw/remote/remote.js"></script>
54 <script src="app/fw/remote/urlfn.js"></script> 54 <script src="app/fw/remote/urlfn.js"></script>
55 <script src="app/fw/remote/rest.js"></script> 55 <script src="app/fw/remote/rest.js"></script>
56 + <script src="app/fw/remote/wsock.js"></script>
56 <script src="app/fw/remote/websocket.js"></script> 57 <script src="app/fw/remote/websocket.js"></script>
57 - <script src="app/fw/remote/wsevent.js"></script>
58 58
59 <script src="app/fw/widget/widget.js"></script> 59 <script src="app/fw/widget/widget.js"></script>
60 <script src="app/fw/widget/table.js"></script> 60 <script src="app/fw/widget/table.js"></script>
......
...@@ -84,10 +84,9 @@ ...@@ -84,10 +84,9 @@
84 flash.initFlash(); 84 flash.initFlash();
85 qhs.initQuickHelp(); 85 qhs.initQuickHelp();
86 86
87 - // TODO: register handlers for initial messages: instances, settings, etc. 87 + // TODO: register handler for user settings, etc.
88 88
89 - // TODO: opts? 89 + wss.createWebSocket({
90 - wss.createWebSocket('core', {
91 wsport: $location.search().wsport 90 wsport: $location.search().wsport
92 }); 91 });
93 92
......