Home | History | Annotate | Download | only in include
      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