1 /* 2 * Websock: high-performance binary WebSockets 3 * Copyright (C) 2011 Joel Martin 4 * Licensed under LGPL-3 (see LICENSE.txt) 5 * 6 * Websock is similar to the standard WebSocket object but Websock 7 * enables communication with raw TCP sockets (i.e. the binary stream) 8 * via websockify. This is accomplished by base64 encoding the data 9 * stream between Websock and websockify. 10 * 11 * Websock has built-in receive queue buffering; the message event 12 * does not contain actual data but is simply a notification that 13 * there is new data available. Several rQ* methods are available to 14 * read binary data off of the receive queue. 15 */ 16 17 /*jslint browser: true, bitwise: false, plusplus: false */ 18 /*global Util, Base64 */ 19 20 21 // Load Flash WebSocket emulator if needed 22 23 if (window.WebSocket && !window.WEB_SOCKET_FORCE_FLASH) { 24 Websock_native = true; 25 } else if (window.MozWebSocket && !window.WEB_SOCKET_FORCE_FLASH) { 26 Websock_native = true; 27 window.WebSocket = window.MozWebSocket; 28 } else { 29 /* no builtin WebSocket so load web_socket.js */ 30 31 // To enable debug: 32 // window.WEB_SOCKET_DEBUG=1; 33 34 Websock_native = false; 35 (function () { 36 function get_INCLUDE_URI() { 37 return (typeof INCLUDE_URI !== "undefined") ? 38 INCLUDE_URI : "include/"; 39 } 40 41 var start = "<script src='" + get_INCLUDE_URI(), 42 end = "'><\/script>", extra = ""; 43 44 window.WEB_SOCKET_SWF_LOCATION = get_INCLUDE_URI() + 45 "web-socket-js/WebSocketMain.swf"; 46 if (Util.Engine.trident) { 47 Util.Debug("Forcing uncached load of WebSocketMain.swf"); 48 window.WEB_SOCKET_SWF_LOCATION += "?" + Math.random(); 49 } 50 extra += start + "web-socket-js/swfobject.js" + end; 51 extra += start + "web-socket-js/web_socket.js" + end; 52 document.write(extra); 53 }()); 54 } 55 56 57 function Websock() { 58 "use strict"; 59 60 var api = {}, // Public API 61 websocket = null, // WebSocket object 62 rQ = [], // Receive queue 63 rQi = 0, // Receive queue index 64 rQmax = 10000, // Max receive queue size before compacting 65 sQ = [], // Send queue 66 67 eventHandlers = { 68 'message' : function() {}, 69 'open' : function() {}, 70 'close' : function() {}, 71 'error' : function() {} 72 }, 73 74 test_mode = false; 75 76 77 // 78 // Queue public functions 79 // 80 81 function get_sQ() { 82 return sQ; 83 } 84 85 function get_rQ() { 86 return rQ; 87 } 88 function get_rQi() { 89 return rQi; 90 } 91 function set_rQi(val) { 92 rQi = val; 93 } 94 95 function rQlen() { 96 return rQ.length - rQi; 97 } 98 99 function rQpeek8() { 100 return (rQ[rQi] ); 101 } 102 function rQshift8() { 103 return (rQ[rQi++] ); 104 } 105 function rQunshift8(num) { 106 if (rQi === 0) { 107 rQ.unshift(num); 108 } else { 109 rQi -= 1; 110 rQ[rQi] = num; 111 } 112 113 } 114 function rQshift16() { 115 return (rQ[rQi++] << 8) + 116 (rQ[rQi++] ); 117 } 118 function rQshift32() { 119 return (rQ[rQi++] << 24) + 120 (rQ[rQi++] << 16) + 121 (rQ[rQi++] << 8) + 122 (rQ[rQi++] ); 123 } 124 function rQshiftStr(len) { 125 if (typeof(len) === 'undefined') { len = rQlen(); } 126 var arr = rQ.slice(rQi, rQi + len); 127 rQi += len; 128 return arr.map(function (num) { 129 return String.fromCharCode(num); } ).join(''); 130 131 } 132 function rQshiftBytes(len) { 133 if (typeof(len) === 'undefined') { len = rQlen(); } 134 rQi += len; 135 return rQ.slice(rQi-len, rQi); 136 } 137 138 function rQslice(start, end) { 139 if (end) { 140 return rQ.slice(rQi + start, rQi + end); 141 } else { 142 return rQ.slice(rQi + start); 143 } 144 } 145 146 // Check to see if we must wait for 'num' bytes (default to FBU.bytes) 147 // to be available in the receive queue. Return true if we need to 148 // wait (and possibly print a debug message), otherwise false. 149 function rQwait(msg, num, goback) { 150 var rQlen = rQ.length - rQi; // Skip rQlen() function call 151 if (rQlen < num) { 152 if (goback) { 153 if (rQi < goback) { 154 throw("rQwait cannot backup " + goback + " bytes"); 155 } 156 rQi -= goback; 157 } 158 //Util.Debug(" waiting for " + (num-rQlen) + 159 // " " + msg + " byte(s)"); 160 return true; // true means need more data 161 } 162 return false; 163 } 164 165 // 166 // Private utility routines 167 // 168 169 function encode_message() { 170 /* base64 encode */ 171 return Base64.encode(sQ); 172 } 173 174 function decode_message(data) { 175 //Util.Debug(">> decode_message: " + data); 176 /* base64 decode */ 177 rQ = rQ.concat(Base64.decode(data, 0)); 178 //Util.Debug(">> decode_message, rQ: " + rQ); 179 } 180 181 182 // 183 // Public Send functions 184 // 185 186 function flush() { 187 if (websocket.bufferedAmount !== 0) { 188 Util.Debug("bufferedAmount: " + websocket.bufferedAmount); 189 } 190 if (websocket.bufferedAmount < api.maxBufferedAmount) { 191 //Util.Debug("arr: " + arr); 192 //Util.Debug("sQ: " + sQ); 193 if (sQ.length > 0) { 194 websocket.send(encode_message(sQ)); 195 sQ = []; 196 } 197 return true; 198 } else { 199 Util.Info("Delaying send, bufferedAmount: " + 200 websocket.bufferedAmount); 201 return false; 202 } 203 } 204 205 // overridable for testing 206 function send(arr) { 207 //Util.Debug(">> send_array: " + arr); 208 sQ = sQ.concat(arr); 209 return flush(); 210 } 211 212 function send_string(str) { 213 //Util.Debug(">> send_string: " + str); 214 api.send(str.split('').map( 215 function (chr) { return chr.charCodeAt(0); } ) ); 216 } 217 218 // 219 // Other public functions 220 221 function recv_message(e) { 222 //Util.Debug(">> recv_message: " + e.data.length); 223 224 try { 225 decode_message(e.data); 226 if (rQlen() > 0) { 227 eventHandlers.message(); 228 // Compact the receive queue 229 if (rQ.length > rQmax) { 230 //Util.Debug("Compacting receive queue"); 231 rQ = rQ.slice(rQi); 232 rQi = 0; 233 } 234 } else { 235 Util.Debug("Ignoring empty message"); 236 } 237 } catch (exc) { 238 if (typeof exc.stack !== 'undefined') { 239 Util.Warn("recv_message, caught exception: " + exc.stack); 240 } else if (typeof exc.description !== 'undefined') { 241 Util.Warn("recv_message, caught exception: " + exc.description); 242 } else { 243 Util.Warn("recv_message, caught exception:" + exc); 244 } 245 if (typeof exc.name !== 'undefined') { 246 eventHandlers.error(exc.name + ": " + exc.message); 247 } else { 248 eventHandlers.error(exc); 249 } 250 } 251 //Util.Debug("<< recv_message"); 252 } 253 254 255 // Set event handlers 256 function on(evt, handler) { 257 eventHandlers[evt] = handler; 258 } 259 260 function init() { 261 rQ = []; 262 rQi = 0; 263 sQ = []; 264 websocket = null; 265 } 266 267 function open(uri) { 268 init(); 269 270 if (test_mode) { 271 websocket = {}; 272 } else { 273 websocket = new WebSocket(uri, 'base64'); 274 // TODO: future native binary support 275 //websocket = new WebSocket(uri, ['binary', 'base64']); 276 } 277 278 websocket.onmessage = recv_message; 279 websocket.onopen = function() { 280 Util.Debug(">> WebSock.onopen"); 281 if (websocket.protocol) { 282 Util.Info("Server chose sub-protocol: " + websocket.protocol); 283 } 284 eventHandlers.open(); 285 Util.Debug("<< WebSock.onopen"); 286 }; 287 websocket.onclose = function(e) { 288 Util.Debug(">> WebSock.onclose"); 289 eventHandlers.close(e); 290 Util.Debug("<< WebSock.onclose"); 291 }; 292 websocket.onerror = function(e) { 293 Util.Debug(">> WebSock.onerror: " + e); 294 eventHandlers.error(e); 295 Util.Debug("<< WebSock.onerror"); 296 }; 297 } 298 299 function close() { 300 if (websocket) { 301 if ((websocket.readyState === WebSocket.OPEN) || 302 (websocket.readyState === WebSocket.CONNECTING)) { 303 Util.Info("Closing WebSocket connection"); 304 websocket.close(); 305 } 306 websocket.onmessage = function (e) { return; }; 307 } 308 } 309 310 // Override internal functions for testing 311 // Takes a send function, returns reference to recv function 312 function testMode(override_send) { 313 test_mode = true; 314 api.send = override_send; 315 api.close = function () {}; 316 return recv_message; 317 } 318 319 function constructor() { 320 // Configuration settings 321 api.maxBufferedAmount = 200; 322 323 // Direct access to send and receive queues 324 api.get_sQ = get_sQ; 325 api.get_rQ = get_rQ; 326 api.get_rQi = get_rQi; 327 api.set_rQi = set_rQi; 328 329 // Routines to read from the receive queue 330 api.rQlen = rQlen; 331 api.rQpeek8 = rQpeek8; 332 api.rQshift8 = rQshift8; 333 api.rQunshift8 = rQunshift8; 334 api.rQshift16 = rQshift16; 335 api.rQshift32 = rQshift32; 336 api.rQshiftStr = rQshiftStr; 337 api.rQshiftBytes = rQshiftBytes; 338 api.rQslice = rQslice; 339 api.rQwait = rQwait; 340 341 api.flush = flush; 342 api.send = send; 343 api.send_string = send_string; 344 345 api.on = on; 346 api.init = init; 347 api.open = open; 348 api.close = close; 349 api.testMode = testMode; 350 351 return api; 352 } 353 354 return constructor(); 355 356 } 357