GUI -- Cleaned up websocket code.
- isolated new WebSocket() call, so we can mock. Change-Id: Id1225e2c65732e750b289224e838a326c79f02a4
Showing
5 changed files
with
172 additions
and
119 deletions
... | @@ -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 | } | 164 | } |
101 | } | 165 | } |
102 | 166 | ||
103 | - function handleOpen() { | ||
104 | - $log.info('web socket open'); | ||
105 | - // FIXME: implement calling external hooks | ||
106 | - } | ||
107 | - | ||
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 | ... | ... |
-
Please register or login to post a comment