Commits

Marcin Lulek committed 9d893e9

updates

Comments (0)

Files changed (11)

gevent_cometd/app_views.py

 from gevent import monkey; monkey.patch_all()
 import gevent
+import geventwebsocket
 import logging
 import datetime
 import gevent_cometd
                            content_type='application/json')
             util.add_cors_headers(res)
             return res
-        print "NON-WEBSOCKET"
         def yield_response():
             # for chrome issues
             # yield ' ' * 1024
         util.add_cors_headers(res)
         return res
     else:
-        print "WEBSOCKET"
-        while True:
-            connection.event.wait()
-            gevent.sleep(0.2)
-            websocket.send(json.dumps(connection.messages, cls=util.DateTimeEncoder))
-            connection.messages = []
-
+        connection.websocket = websocket
+        try:
+            while True:
+                connection.event.wait(10)
+                # mark as last active
+                # ensures they dont get GC'd even if we are not sending anything
+                to_send = connection.messages
+                connection.messages = []
+                websocket.send(json.dumps(to_send, cls=util.DateTimeEncoder))
+                user_inst = user.User.by_name(connection.user)
+                user_inst.last_active = datetime.datetime.utcnow()
+                connection.last_active = datetime.datetime.utcnow()
+        except geventwebsocket.WebSocketError as e:
+            print 'PROBLEM'
+            connection.mark_for_gc()
+        return Response('[]', request=request)
 
 def static(request, *args):
     """serve static stuff"""

gevent_cometd/start.py

     config['allow_posting_from'] = [ip.strip() for ip in options.allow_posting_from.split(',')]
     print 'Serving on 0.0.0.0:%s' % config['port']
     app = WSGIRouter()
-    pywsgi.WSGIServer(('', config['port']), app, log=None, 
+    pywsgi.WSGIServer(('', config['port']), app, log=None,
                       handler_class=WebSocketHandler).serve_forever()

gevent_cometd/static/WebSocketMain.swf

Binary file added.

gevent_cometd/static/client.js

-define([ "dojo/request", "dojox/socket", "dojox/socket/Reconnect", 'dojo/json',
-        'dojo/query', 'dojox/io/xhrPlugins', 'dojo/_base/unload', 'dojo/topic',
-        'dojo/request/script' ], function(Request, Socket, Reconnect, JSON,
-Query, xhrPlugin, Unload, Topic, JSONP) {
-    xhrPlugin.addCrossSiteXhr("");
-    
-    var jsonp_call = function(args, message) {
-        // The parameters to pass to xhrGet, the url, how to handle it, and the
-        // callbacks.
-        var jsonpArgs = {
-            url : args.url,
-            callbackParamName : "callback",
-            content : {}
-        };
-        JSONP(jsonpArgs);
-    };
-
+define([ "dojo/request", 'dojo/json', 'dojo/query', 'dojo/_base/unload',
+        'dojo/topic' ], function(Request, JSON, Query, Unload, Topic, JSONP) {
     var state = {
         webapp_url : null,
         server_url : null,
         socket : null,
         connection_request : null,
         on_connect_callback : null,
-        is_websocket : false
+        reconnect_timout : null,
+        check_connection : null,
+        last_message : Date.now()
     };
 
-    var message_event = function(event) {
-        var data = event.data;
-        if (state.is_websocket) {
-            data = JSON.parse(event.data);
-        }
-        for ( var i = 0; i < data.length; i++) {
-            console.log('publishing: ' + 'gevent_cometd/' + data[i].type);
-            Topic.publish('gevent_cometd/' + data[i].type, data[i]);
-        }
-    }
-
-    var error_event = function(event) {
-        if (state.socket.reconnect_timout) {
-            clearTimeout(socket.reconnect_timout);
-        }
-        state.socket.reconnect_timout = setTimeout(function() {
-            /* reconnect requires fetching new id and updating socket */
-            get_conn_id(false);
-            console.log('got new connection id');
-        }, 2500); // try to collapse multiple reconnects into 1
-    }
-
-    var create_socket = function(data) {
-        console.log('attempting to create socket');
-        var socket_url = state.server_url + "/listen?conn_id=" + state.conn_id;
-        var socket_conf = {
-            url : socket_url,
-            handleAs : 'json',
-            headers : {
-                "Accept" : "application/json",
-                "Content-Type" : "application/json"
-            }
-        }
-
-        var ws = typeof WebSocket != "undefined";
-        ws = false;
-        if (ws) {
-            console.log('Websocket capable');
-            socket_conf.url = socket_conf.url.replace('https', 'ws');
-            socket_conf.url = socket_conf.url.replace('http', 'ws');
-            state.socket = new Socket(socket_conf);
-            state.is_websocket = true;
-        } else {
-            console.log('NOT Websocket capable');
-            if (dojo.isOpera || (dojo.isIE && dojo.isIE < 9)) {
-                socket_conf.transport = jsonp_call;
-            }
-            state.socket = Socket.LongPoll(socket_conf);
-        }
-        // Create socket instance
-        state.socket = Reconnect(state.socket, {
-            reconnectTime : 500
-        });
-        state.socket.reconnect_timout = null;
-        console.log('created socket with reconnect capability');
-        console.log('adding message event');
-        state.socket.on("message", message_event);
-        console.log('adding error event');
-        state.socket.on("error", error_event);
-        console.log('added events to socket');
-        if (state.on_connect_callback) {
-            state.on_connect_callback(state)
-        }
-    }
-
     var get_conn_id = function(create_new_socket) {
         Request.post(state.webapp_url + "/connect", {
             handleAs : 'json',
         function(data) {
             state.conn_id = data.conn_id;
             if (state.socket) {
-                state.socket.args.url = state.server_url + "/listen?conn_id="
+                console.log(state.socket);
+                var socket_url = state.server_url + "/listen?conn_id="
                 + state.conn_id;
+                socket_url = socket_url.replace('https', 'wss');
+                socket_url = socket_url.replace('http', 'ws');
+                state.socket.url = socket_url;
+                state.socket.URL = socket_url;
             }
             if (create_new_socket) {
                 create_socket();
             }
+
         });
     }
 
+    var message_event = function(event) {
+        var data = JSON.parse(event.data);
+        console.log('got event', data);
+        state.last_message = Date.now();
+        for ( var i = 0; i < data.length; i++) {
+            console.log('publishing: ' + 'gevent_cometd/' + data[i].type);
+            Topic.publish('gevent_cometd/' + data[i].type, data[i]);
+        }
+    }
+
+    var close_event = function(event) {
+        if (state.reconnect_timout) {
+            clearTimeout(state.reconnect_timout);
+        }
+        state.reconnect_timout = setTimeout(function() {
+            /* reconnect requires fetching new id and updating socket */
+            get_conn_id(true);
+            console.log('Got new connection id');
+        }, 500); // try to collapse multiple reconnects into 1
+    }
+    var error_event = function(event) {
+        console.log('Error event', event);
+    }
+
+    var create_socket = function() {
+        if (state.socket) {
+            console.log('socket present, delete old one');
+            state.socket.close();
+        }
+        state.last_message = Date.now();
+        console.log('attempting to create socket');
+        if (state.check_connection) {
+            clearInterval(state.check_connection);
+        }
+        var socket_url = state.server_url + "/listen?conn_id=" + state.conn_id;
+        socket_url = socket_url.replace('https', 'wss');
+        socket_url = socket_url.replace('http', 'ws');
+        state.socket = new WebSocket(socket_url);
+        state.socket.onmessage = message_event;
+        state.socket.onclose = close_event;
+        state.socket.onerror = error_event;
+        if (state.on_connect_callback) {
+            state.on_connect_callback(state)
+        }
+        state.check_connection = setInterval(function() {
+            var last_update = (Date.now() - state.last_message) / 1000.0;
+            if (last_update > 20) {
+                close_event();
+            }
+        }, 5000);
+    }
+
     var connect = function(webapp_url, server_url, connection_request,
     on_connect_callback) {
-        console.log('connecting to get UID of connection');
         state.connection_request = connection_request;
         state.webapp_url = webapp_url;
         state.server_url = server_url;

gevent_cometd/static/jq_client.js

+var state = {
+    webapp_url : null,
+    server_url : null,
+    conn_id : null,
+    socket : null,
+    connection_request : null,
+    on_connect_callback : null,
+    reconnect_timout : null,
+    heartbeat : null
+};
+
+// form submit event
+$('#msg_form').submit(function() {
+
+    var form_array = $(this).serializeArray();
+    var post = {};
+    for ( var int = 0; int < form_array.length; int++) {
+        post[form_array[int].name] = form_array[int].value;
+    }
+
+    $.ajax({
+        url : $(this).attr("action"),
+        type : "POST",
+        contentType : "application/json",
+        data : JSON.stringify(post),
+        dataType : "json",
+    }).done(function() {
+        $(this).addClass("done");
+    });
+    return false;
+});
+
+var create_socket = function() {
+    console.log('attempting to create socket');
+    var socket_url = state.server_url + "/listen?conn_id=" + state.conn_id;
+    var socket_conf = {
+        url : socket_url,
+        handleAs : 'json',
+        headers : {
+            "Accept" : "application/json",
+            "Content-Type" : "application/json"
+        }
+    }
+    socket_conf.url = socket_conf.url.replace('https', 'ws');
+    socket_conf.url = socket_conf.url.replace('http', 'ws');
+    state.socket = new WebSocket(socket_conf.url);
+
+    state.socket.onopen = function(event) {
+        console.log('open');
+    };
+    state.socket.onmessage = function(event) {
+        var data = $.parseJSON(event.data);
+        for ( var i = 0; i < data.length; i++) {
+            console.log('publishing: ' + 'gevent_cometd/' + data[i].type);
+        }
+    };
+    state.socket.onclose = function(event) {
+        console.log('closed');
+    };
+
+    state.socket.onerror = function(event) {
+        console.log('error');
+    };
+
+}
+
+var get_conn_id = function(create_new_socket) {
+    $.ajax({
+        url : state.webapp_url + "/connect",
+        type : "POST",
+        contentType : "application/json",
+        data : JSON.stringify(state.connection_request),
+        dataType : "json",
+    }).done(function(data) {
+        state.conn_id = data.conn_id;
+        if (create_new_socket) {
+            create_socket();
+        }
+    });
+}
+
+var demo_start = function(webapp_url, server_url, connection_request) {
+    console.log('demo start');
+
+    console.log('connecting to get UID of connection');
+    state.connection_request = connection_request;
+    state.webapp_url = webapp_url;
+    state.server_url = server_url;
+    state.on_connect_callback = null;
+    get_conn_id(true);
+
+}

gevent_cometd/static/main.js

                 data : DomForm.toJson(this),
                 handleAs : "text"
             });
-            Query('textarea[name=message]')[0].value = '';
+            Query('input[name=message]')[0].value = '';
             console.log('sent message');
         });
 
         }
 
         var channel_message_callback = function(entry) {
-            console.log('received message !');
             var li = DomConstruct.create('li', null, messages_node);
             DomConstruct.create('strong', {
                 innerHTML : 'channel:' + entry.channel + ' '

gevent_cometd/static/reconnecting_websocket.js

+// MIT License:
+//
+// Copyright (c) 2010-2012, Joe Walnes
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+/**
+ * This behaves like a WebSocket in every way, except if it fails to connect,
+ * or it gets disconnected, it will repeatedly poll until it succesfully connects
+ * again.
+ *
+ * It is API compatible, so when you have:
+ *   ws = new WebSocket('ws://....');
+ * you can replace with:
+ *   ws = new ReconnectingWebSocket('ws://....');
+ *
+ * The event stream will typically look like:
+ *  onopen
+ *  onmessage
+ *  onmessage
+ *  onclose // lost connection
+ *  onopen  // sometime later...
+ *  onmessage
+ *  onmessage
+ *  etc... 
+ *
+ * It is API compatible with the standard WebSocket API.
+ *
+ * Latest version: https://github.com/joewalnes/reconnecting-websocket/
+ * - Joe Walnes
+ */
+function ReconnectingWebSocket(url, protocols) {
+
+    // These can be altered by calling code.
+    this.debug = false;
+    this.reconnectInterval = 1000;
+    this.timeoutInterval = 2000;
+
+    var self = this;
+    var ws;
+    var forcedClose = false;
+    var timedOut = false;
+    
+    this.url = url;
+    this.protocols = protocols;
+    this.readyState = WebSocket.CONNECTING;
+    this.URL = url; // Public API
+
+    this.onopen = function(event) {
+    };
+
+    this.onclose = function(event) {
+    };
+
+    this.onmessage = function(event) {
+    };
+
+    this.onerror = function(event) {
+    };
+
+    this.connect = function(reconnectAttempt) {
+        ws = new WebSocket(this.URL, protocols);
+        if (self.debug || ReconnectingWebSocket.debugAll) {
+            console.debug('ReconnectingWebSocket', 'attempt-connect', this.url);
+        }
+        
+        var localWs = ws;
+        var timeout = setTimeout(function() {
+            if (self.debug || ReconnectingWebSocket.debugAll) {
+                console.debug('ReconnectingWebSocket', 'connection-timeout', this.url);
+            }
+            timedOut = true;
+            localWs.close();
+            timedOut = false;
+        }, self.timeoutInterval);
+        
+        ws.onopen = function(event) {
+            clearTimeout(timeout);
+            if (self.debug || ReconnectingWebSocket.debugAll) {
+                console.debug('ReconnectingWebSocket', 'onopen', this.url);
+            }
+            self.readyState = WebSocket.OPEN;
+            reconnectAttempt = false;
+            self.onopen(event);
+        };
+        
+        ws.onclose = function(event) {
+            clearTimeout(timeout);
+            ws = null;
+            if (forcedClose) {
+                self.readyState = WebSocket.CLOSED;
+                self.onclose(event);
+            } else {
+                self.readyState = WebSocket.CONNECTING;
+                if (!reconnectAttempt && !timedOut) {
+                    if (self.debug || ReconnectingWebSocket.debugAll) {
+                        console.debug('ReconnectingWebSocket', 'onclose', this.url);
+                    }
+                    self.onclose(event);
+                }
+                setTimeout(function() {
+                    self.connect(true);
+                }, self.reconnectInterval);
+            }
+        };
+        ws.onmessage = function(event) {
+            if (self.debug || ReconnectingWebSocket.debugAll) {
+                console.debug('ReconnectingWebSocket', 'onmessage', this.url, event.data);
+            }
+            self.onmessage(event);
+        };
+        ws.onerror = function(event) {
+            if (self.debug || ReconnectingWebSocket.debugAll) {
+                console.debug('ReconnectingWebSocket', 'onerror', this.url, event);
+            }
+            self.onerror(event);
+        };
+    }
+    this.connect(url);
+
+    this.send = function(data) {
+        if (ws) {
+            if (self.debug || ReconnectingWebSocket.debugAll) {
+                console.debug('ReconnectingWebSocket', 'send', this.url, data);
+            }
+            return ws.send(data);
+        } else {
+            throw 'INVALID_STATE_ERR : Pausing to reconnect websocket';
+        }
+    };
+
+    this.close = function() {
+        if (ws) {
+            forcedClose = true;
+            ws.close();
+        }
+    };
+
+    /**
+     * Additional public API method to refresh the connection if still open (close, re-open).
+     * For example, if the app suspects bad data / missed heart beats, it can try to refresh.
+     */
+    this.refresh = function() {
+        if (ws) {
+            ws.close();
+        }
+    };
+}
+
+/**
+ * Setting this to true is the equivalent of setting all instances of ReconnectingWebSocket.debug to true.
+ */
+ReconnectingWebSocket.debugAll = false;

gevent_cometd/static/swfobject.js

+/*	SWFObject v2.2 <http://code.google.com/p/swfobject/> 
+	is released under the MIT License <http://www.opensource.org/licenses/mit-license.php> 
+*/
+var swfobject=function(){var D="undefined",r="object",S="Shockwave Flash",W="ShockwaveFlash.ShockwaveFlash",q="application/x-shockwave-flash",R="SWFObjectExprInst",x="onreadystatechange",O=window,j=document,t=navigator,T=false,U=[h],o=[],N=[],I=[],l,Q,E,B,J=false,a=false,n,G,m=true,M=function(){var aa=typeof j.getElementById!=D&&typeof j.getElementsByTagName!=D&&typeof j.createElement!=D,ah=t.userAgent.toLowerCase(),Y=t.platform.toLowerCase(),ae=Y?/win/.test(Y):/win/.test(ah),ac=Y?/mac/.test(Y):/mac/.test(ah),af=/webkit/.test(ah)?parseFloat(ah.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,X=!+"\v1",ag=[0,0,0],ab=null;if(typeof t.plugins!=D&&typeof t.plugins[S]==r){ab=t.plugins[S].description;if(ab&&!(typeof t.mimeTypes!=D&&t.mimeTypes[q]&&!t.mimeTypes[q].enabledPlugin)){T=true;X=false;ab=ab.replace(/^.*\s+(\S+\s+\S+$)/,"$1");ag[0]=parseInt(ab.replace(/^(.*)\..*$/,"$1"),10);ag[1]=parseInt(ab.replace(/^.*\.(.*)\s.*$/,"$1"),10);ag[2]=/[a-zA-Z]/.test(ab)?parseInt(ab.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0}}else{if(typeof O.ActiveXObject!=D){try{var ad=new ActiveXObject(W);if(ad){ab=ad.GetVariable("$version");if(ab){X=true;ab=ab.split(" ")[1].split(",");ag=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}}catch(Z){}}}return{w3:aa,pv:ag,wk:af,ie:X,win:ae,mac:ac}}(),k=function(){if(!M.w3){return}if((typeof j.readyState!=D&&j.readyState=="complete")||(typeof j.readyState==D&&(j.getElementsByTagName("body")[0]||j.body))){f()}if(!J){if(typeof j.addEventListener!=D){j.addEventListener("DOMContentLoaded",f,false)}if(M.ie&&M.win){j.attachEvent(x,function(){if(j.readyState=="complete"){j.detachEvent(x,arguments.callee);f()}});if(O==top){(function(){if(J){return}try{j.documentElement.doScroll("left")}catch(X){setTimeout(arguments.callee,0);return}f()})()}}if(M.wk){(function(){if(J){return}if(!/loaded|complete/.test(j.readyState)){setTimeout(arguments.callee,0);return}f()})()}s(f)}}();function f(){if(J){return}try{var Z=j.getElementsByTagName("body")[0].appendChild(C("span"));Z.parentNode.removeChild(Z)}catch(aa){return}J=true;var X=U.length;for(var Y=0;Y<X;Y++){U[Y]()}}function K(X){if(J){X()}else{U[U.length]=X}}function s(Y){if(typeof O.addEventListener!=D){O.addEventListener("load",Y,false)}else{if(typeof j.addEventListener!=D){j.addEventListener("load",Y,false)}else{if(typeof O.attachEvent!=D){i(O,"onload",Y)}else{if(typeof O.onload=="function"){var X=O.onload;O.onload=function(){X();Y()}}else{O.onload=Y}}}}}function h(){if(T){V()}else{H()}}function V(){var X=j.getElementsByTagName("body")[0];var aa=C(r);aa.setAttribute("type",q);var Z=X.appendChild(aa);if(Z){var Y=0;(function(){if(typeof Z.GetVariable!=D){var ab=Z.GetVariable("$version");if(ab){ab=ab.split(" ")[1].split(",");M.pv=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}else{if(Y<10){Y++;setTimeout(arguments.callee,10);return}}X.removeChild(aa);Z=null;H()})()}else{H()}}function H(){var ag=o.length;if(ag>0){for(var af=0;af<ag;af++){var Y=o[af].id;var ab=o[af].callbackFn;var aa={success:false,id:Y};if(M.pv[0]>0){var ae=c(Y);if(ae){if(F(o[af].swfVersion)&&!(M.wk&&M.wk<312)){w(Y,true);if(ab){aa.success=true;aa.ref=z(Y);ab(aa)}}else{if(o[af].expressInstall&&A()){var ai={};ai.data=o[af].expressInstall;ai.width=ae.getAttribute("width")||"0";ai.height=ae.getAttribute("height")||"0";if(ae.getAttribute("class")){ai.styleclass=ae.getAttribute("class")}if(ae.getAttribute("align")){ai.align=ae.getAttribute("align")}var ah={};var X=ae.getElementsByTagName("param");var ac=X.length;for(var ad=0;ad<ac;ad++){if(X[ad].getAttribute("name").toLowerCase()!="movie"){ah[X[ad].getAttribute("name")]=X[ad].getAttribute("value")}}P(ai,ah,Y,ab)}else{p(ae);if(ab){ab(aa)}}}}}else{w(Y,true);if(ab){var Z=z(Y);if(Z&&typeof Z.SetVariable!=D){aa.success=true;aa.ref=Z}ab(aa)}}}}}function z(aa){var X=null;var Y=c(aa);if(Y&&Y.nodeName=="OBJECT"){if(typeof Y.SetVariable!=D){X=Y}else{var Z=Y.getElementsByTagName(r)[0];if(Z){X=Z}}}return X}function A(){return !a&&F("6.0.65")&&(M.win||M.mac)&&!(M.wk&&M.wk<312)}function P(aa,ab,X,Z){a=true;E=Z||null;B={success:false,id:X};var ae=c(X);if(ae){if(ae.nodeName=="OBJECT"){l=g(ae);Q=null}else{l=ae;Q=X}aa.id=R;if(typeof aa.width==D||(!/%$/.test(aa.width)&&parseInt(aa.width,10)<310)){aa.width="310"}if(typeof aa.height==D||(!/%$/.test(aa.height)&&parseInt(aa.height,10)<137)){aa.height="137"}j.title=j.title.slice(0,47)+" - Flash Player Installation";var ad=M.ie&&M.win?"ActiveX":"PlugIn",ac="MMredirectURL="+O.location.toString().replace(/&/g,"%26")+"&MMplayerType="+ad+"&MMdoctitle="+j.title;if(typeof ab.flashvars!=D){ab.flashvars+="&"+ac}else{ab.flashvars=ac}if(M.ie&&M.win&&ae.readyState!=4){var Y=C("div");X+="SWFObjectNew";Y.setAttribute("id",X);ae.parentNode.insertBefore(Y,ae);ae.style.display="none";(function(){if(ae.readyState==4){ae.parentNode.removeChild(ae)}else{setTimeout(arguments.callee,10)}})()}u(aa,ab,X)}}function p(Y){if(M.ie&&M.win&&Y.readyState!=4){var X=C("div");Y.parentNode.insertBefore(X,Y);X.parentNode.replaceChild(g(Y),X);Y.style.display="none";(function(){if(Y.readyState==4){Y.parentNode.removeChild(Y)}else{setTimeout(arguments.callee,10)}})()}else{Y.parentNode.replaceChild(g(Y),Y)}}function g(ab){var aa=C("div");if(M.win&&M.ie){aa.innerHTML=ab.innerHTML}else{var Y=ab.getElementsByTagName(r)[0];if(Y){var ad=Y.childNodes;if(ad){var X=ad.length;for(var Z=0;Z<X;Z++){if(!(ad[Z].nodeType==1&&ad[Z].nodeName=="PARAM")&&!(ad[Z].nodeType==8)){aa.appendChild(ad[Z].cloneNode(true))}}}}}return aa}function u(ai,ag,Y){var X,aa=c(Y);if(M.wk&&M.wk<312){return X}if(aa){if(typeof ai.id==D){ai.id=Y}if(M.ie&&M.win){var ah="";for(var ae in ai){if(ai[ae]!=Object.prototype[ae]){if(ae.toLowerCase()=="data"){ag.movie=ai[ae]}else{if(ae.toLowerCase()=="styleclass"){ah+=' class="'+ai[ae]+'"'}else{if(ae.toLowerCase()!="classid"){ah+=" "+ae+'="'+ai[ae]+'"'}}}}}var af="";for(var ad in ag){if(ag[ad]!=Object.prototype[ad]){af+='<param name="'+ad+'" value="'+ag[ad]+'" />'}}aa.outerHTML='<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"'+ah+">"+af+"</object>";N[N.length]=ai.id;X=c(ai.id)}else{var Z=C(r);Z.setAttribute("type",q);for(var ac in ai){if(ai[ac]!=Object.prototype[ac]){if(ac.toLowerCase()=="styleclass"){Z.setAttribute("class",ai[ac])}else{if(ac.toLowerCase()!="classid"){Z.setAttribute(ac,ai[ac])}}}}for(var ab in ag){if(ag[ab]!=Object.prototype[ab]&&ab.toLowerCase()!="movie"){e(Z,ab,ag[ab])}}aa.parentNode.replaceChild(Z,aa);X=Z}}return X}function e(Z,X,Y){var aa=C("param");aa.setAttribute("name",X);aa.setAttribute("value",Y);Z.appendChild(aa)}function y(Y){var X=c(Y);if(X&&X.nodeName=="OBJECT"){if(M.ie&&M.win){X.style.display="none";(function(){if(X.readyState==4){b(Y)}else{setTimeout(arguments.callee,10)}})()}else{X.parentNode.removeChild(X)}}}function b(Z){var Y=c(Z);if(Y){for(var X in Y){if(typeof Y[X]=="function"){Y[X]=null}}Y.parentNode.removeChild(Y)}}function c(Z){var X=null;try{X=j.getElementById(Z)}catch(Y){}return X}function C(X){return j.createElement(X)}function i(Z,X,Y){Z.attachEvent(X,Y);I[I.length]=[Z,X,Y]}function F(Z){var Y=M.pv,X=Z.split(".");X[0]=parseInt(X[0],10);X[1]=parseInt(X[1],10)||0;X[2]=parseInt(X[2],10)||0;return(Y[0]>X[0]||(Y[0]==X[0]&&Y[1]>X[1])||(Y[0]==X[0]&&Y[1]==X[1]&&Y[2]>=X[2]))?true:false}function v(ac,Y,ad,ab){if(M.ie&&M.mac){return}var aa=j.getElementsByTagName("head")[0];if(!aa){return}var X=(ad&&typeof ad=="string")?ad:"screen";if(ab){n=null;G=null}if(!n||G!=X){var Z=C("style");Z.setAttribute("type","text/css");Z.setAttribute("media",X);n=aa.appendChild(Z);if(M.ie&&M.win&&typeof j.styleSheets!=D&&j.styleSheets.length>0){n=j.styleSheets[j.styleSheets.length-1]}G=X}if(M.ie&&M.win){if(n&&typeof n.addRule==r){n.addRule(ac,Y)}}else{if(n&&typeof j.createTextNode!=D){n.appendChild(j.createTextNode(ac+" {"+Y+"}"))}}}function w(Z,X){if(!m){return}var Y=X?"visible":"hidden";if(J&&c(Z)){c(Z).style.visibility=Y}else{v("#"+Z,"visibility:"+Y)}}function L(Y){var Z=/[\\\"<>\.;]/;var X=Z.exec(Y)!=null;return X&&typeof encodeURIComponent!=D?encodeURIComponent(Y):Y}var d=function(){if(M.ie&&M.win){window.attachEvent("onunload",function(){var ac=I.length;for(var ab=0;ab<ac;ab++){I[ab][0].detachEvent(I[ab][1],I[ab][2])}var Z=N.length;for(var aa=0;aa<Z;aa++){y(N[aa])}for(var Y in M){M[Y]=null}M=null;for(var X in swfobject){swfobject[X]=null}swfobject=null})}}();return{registerObject:function(ab,X,aa,Z){if(M.w3&&ab&&X){var Y={};Y.id=ab;Y.swfVersion=X;Y.expressInstall=aa;Y.callbackFn=Z;o[o.length]=Y;w(ab,false)}else{if(Z){Z({success:false,id:ab})}}},getObjectById:function(X){if(M.w3){return z(X)}},embedSWF:function(ab,ah,ae,ag,Y,aa,Z,ad,af,ac){var X={success:false,id:ah};if(M.w3&&!(M.wk&&M.wk<312)&&ab&&ah&&ae&&ag&&Y){w(ah,false);K(function(){ae+="";ag+="";var aj={};if(af&&typeof af===r){for(var al in af){aj[al]=af[al]}}aj.data=ab;aj.width=ae;aj.height=ag;var am={};if(ad&&typeof ad===r){for(var ak in ad){am[ak]=ad[ak]}}if(Z&&typeof Z===r){for(var ai in Z){if(typeof am.flashvars!=D){am.flashvars+="&"+ai+"="+Z[ai]}else{am.flashvars=ai+"="+Z[ai]}}}if(F(Y)){var an=u(aj,am,ah);if(aj.id==ah){w(ah,true)}X.success=true;X.ref=an}else{if(aa&&A()){aj.data=aa;P(aj,am,ah,ac);return}else{w(ah,true)}}if(ac){ac(X)}})}else{if(ac){ac(X)}}},switchOffAutoHideShow:function(){m=false},ua:M,getFlashPlayerVersion:function(){return{major:M.pv[0],minor:M.pv[1],release:M.pv[2]}},hasFlashPlayerVersion:F,createSWF:function(Z,Y,X){if(M.w3){return u(Z,Y,X)}else{return undefined}},showExpressInstall:function(Z,aa,X,Y){if(M.w3&&A()){P(Z,aa,X,Y)}},removeSWF:function(X){if(M.w3){y(X)}},createCSS:function(aa,Z,Y,X){if(M.w3){v(aa,Z,Y,X)}},addDomLoadEvent:K,addLoadEvent:s,getQueryParamValue:function(aa){var Z=j.location.search||j.location.hash;if(Z){if(/\?/.test(Z)){Z=Z.split("?")[1]}if(aa==null){return L(Z)}var Y=Z.split("&");for(var X=0;X<Y.length;X++){if(Y[X].substring(0,Y[X].indexOf("="))==aa){return L(Y[X].substring((Y[X].indexOf("=")+1)))}}}return""},expressInstallCallback:function(){if(a){var X=c(R);if(X&&l){X.parentNode.replaceChild(l,X);if(Q){w(Q,true);if(M.ie&&M.win){l.style.display="block"}}if(E){E(B)}}a=false}}}}();

gevent_cometd/static/websocket.js

+// Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
+// License: New BSD License
+// Reference: http://dev.w3.org/html5/websockets/
+// Reference: http://tools.ietf.org/html/rfc6455
+
+(function() {
+  
+  if (window.WEB_SOCKET_FORCE_FLASH) {
+    // Keeps going.
+  } else if (window.WebSocket) {
+    return;
+  } else if (window.MozWebSocket) {
+    // Firefox.
+    window.WebSocket = MozWebSocket;
+    return;
+  }
+  
+  var logger;
+  if (window.WEB_SOCKET_LOGGER) {
+    logger = WEB_SOCKET_LOGGER;
+  } else if (window.console && window.console.log && window.console.error) {
+    // In some environment, console is defined but console.log or console.error is missing.
+    logger = window.console;
+  } else {
+    logger = {log: function(){ }, error: function(){ }};
+  }
+  
+  // swfobject.hasFlashPlayerVersion("10.0.0") doesn't work with Gnash.
+  if (swfobject.getFlashPlayerVersion().major < 10) {
+    logger.error("Flash Player >= 10.0.0 is required.");
+    return;
+  }
+  if (location.protocol == "file:") {
+    logger.error(
+      "WARNING: web-socket-js doesn't work in file:///... URL " +
+      "unless you set Flash Security Settings properly. " +
+      "Open the page via Web server i.e. http://...");
+  }
+
+  /**
+   * Our own implementation of WebSocket class using Flash.
+   * @param {string} url
+   * @param {array or string} protocols
+   * @param {string} proxyHost
+   * @param {int} proxyPort
+   * @param {string} headers
+   */
+  window.WebSocket = function(url, protocols, proxyHost, proxyPort, headers) {
+    var self = this;
+    self.__id = WebSocket.__nextId++;
+    WebSocket.__instances[self.__id] = self;
+    self.readyState = WebSocket.CONNECTING;
+    self.bufferedAmount = 0;
+    self.__events = {};
+    if (!protocols) {
+      protocols = [];
+    } else if (typeof protocols == "string") {
+      protocols = [protocols];
+    }
+    // Uses setTimeout() to make sure __createFlash() runs after the caller sets ws.onopen etc.
+    // Otherwise, when onopen fires immediately, onopen is called before it is set.
+    self.__createTask = setTimeout(function() {
+      WebSocket.__addTask(function() {
+        self.__createTask = null;
+        WebSocket.__flash.create(
+            self.__id, url, protocols, proxyHost || null, proxyPort || 0, headers || null);
+      });
+    }, 0);
+  };
+
+  /**
+   * Send data to the web socket.
+   * @param {string} data  The data to send to the socket.
+   * @return {boolean}  True for success, false for failure.
+   */
+  WebSocket.prototype.send = function(data) {
+    if (this.readyState == WebSocket.CONNECTING) {
+      throw "INVALID_STATE_ERR: Web Socket connection has not been established";
+    }
+    // We use encodeURIComponent() here, because FABridge doesn't work if
+    // the argument includes some characters. We don't use escape() here
+    // because of this:
+    // https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Functions#escape_and_unescape_Functions
+    // But it looks decodeURIComponent(encodeURIComponent(s)) doesn't
+    // preserve all Unicode characters either e.g. "\uffff" in Firefox.
+    // Note by wtritch: Hopefully this will not be necessary using ExternalInterface.  Will require
+    // additional testing.
+    var result = WebSocket.__flash.send(this.__id, encodeURIComponent(data));
+    if (result < 0) { // success
+      return true;
+    } else {
+      this.bufferedAmount += result;
+      return false;
+    }
+  };
+
+  /**
+   * Close this web socket gracefully.
+   */
+  WebSocket.prototype.close = function() {
+    if (this.__createTask) {
+      clearTimeout(this.__createTask);
+      this.__createTask = null;
+      this.readyState = WebSocket.CLOSED;
+      return;
+    }
+    if (this.readyState == WebSocket.CLOSED || this.readyState == WebSocket.CLOSING) {
+      return;
+    }
+    this.readyState = WebSocket.CLOSING;
+    WebSocket.__flash.close(this.__id);
+  };
+
+  /**
+   * Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
+   *
+   * @param {string} type
+   * @param {function} listener
+   * @param {boolean} useCapture
+   * @return void
+   */
+  WebSocket.prototype.addEventListener = function(type, listener, useCapture) {
+    if (!(type in this.__events)) {
+      this.__events[type] = [];
+    }
+    this.__events[type].push(listener);
+  };
+
+  /**
+   * Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
+   *
+   * @param {string} type
+   * @param {function} listener
+   * @param {boolean} useCapture
+   * @return void
+   */
+  WebSocket.prototype.removeEventListener = function(type, listener, useCapture) {
+    if (!(type in this.__events)) return;
+    var events = this.__events[type];
+    for (var i = events.length - 1; i >= 0; --i) {
+      if (events[i] === listener) {
+        events.splice(i, 1);
+        break;
+      }
+    }
+  };
+
+  /**
+   * Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
+   *
+   * @param {Event} event
+   * @return void
+   */
+  WebSocket.prototype.dispatchEvent = function(event) {
+    var events = this.__events[event.type] || [];
+    for (var i = 0; i < events.length; ++i) {
+      events[i](event);
+    }
+    var handler = this["on" + event.type];
+    if (handler) handler.apply(this, [event]);
+  };
+
+  /**
+   * Handles an event from Flash.
+   * @param {Object} flashEvent
+   */
+  WebSocket.prototype.__handleEvent = function(flashEvent) {
+    
+    if ("readyState" in flashEvent) {
+      this.readyState = flashEvent.readyState;
+    }
+    if ("protocol" in flashEvent) {
+      this.protocol = flashEvent.protocol;
+    }
+    
+    var jsEvent;
+    if (flashEvent.type == "open" || flashEvent.type == "error") {
+      jsEvent = this.__createSimpleEvent(flashEvent.type);
+    } else if (flashEvent.type == "close") {
+      jsEvent = this.__createSimpleEvent("close");
+      jsEvent.wasClean = flashEvent.wasClean ? true : false;
+      jsEvent.code = flashEvent.code;
+      jsEvent.reason = flashEvent.reason;
+    } else if (flashEvent.type == "message") {
+      var data = decodeURIComponent(flashEvent.message);
+      jsEvent = this.__createMessageEvent("message", data);
+    } else {
+      throw "unknown event type: " + flashEvent.type;
+    }
+    
+    this.dispatchEvent(jsEvent);
+    
+  };
+  
+  WebSocket.prototype.__createSimpleEvent = function(type) {
+    if (document.createEvent && window.Event) {
+      var event = document.createEvent("Event");
+      event.initEvent(type, false, false);
+      return event;
+    } else {
+      return {type: type, bubbles: false, cancelable: false};
+    }
+  };
+  
+  WebSocket.prototype.__createMessageEvent = function(type, data) {
+    if (document.createEvent && window.MessageEvent && !window.opera) {
+      var event = document.createEvent("MessageEvent");
+      event.initMessageEvent("message", false, false, data, null, null, window, null);
+      return event;
+    } else {
+      // IE and Opera, the latter one truncates the data parameter after any 0x00 bytes.
+      return {type: type, data: data, bubbles: false, cancelable: false};
+    }
+  };
+  
+  /**
+   * Define the WebSocket readyState enumeration.
+   */
+  WebSocket.CONNECTING = 0;
+  WebSocket.OPEN = 1;
+  WebSocket.CLOSING = 2;
+  WebSocket.CLOSED = 3;
+
+  // Field to check implementation of WebSocket.
+  WebSocket.__isFlashImplementation = true;
+  WebSocket.__initialized = false;
+  WebSocket.__flash = null;
+  WebSocket.__instances = {};
+  WebSocket.__tasks = [];
+  WebSocket.__nextId = 0;
+  
+  /**
+   * Load a new flash security policy file.
+   * @param {string} url
+   */
+  WebSocket.loadFlashPolicyFile = function(url){
+    WebSocket.__addTask(function() {
+      WebSocket.__flash.loadManualPolicyFile(url);
+    });
+  };
+
+  /**
+   * Loads WebSocketMain.swf and creates WebSocketMain object in Flash.
+   */
+  WebSocket.__initialize = function() {
+    
+    if (WebSocket.__initialized) return;
+    WebSocket.__initialized = true;
+    
+    if (WebSocket.__swfLocation) {
+      // For backword compatibility.
+      window.WEB_SOCKET_SWF_LOCATION = WebSocket.__swfLocation;
+    }
+    if (!window.WEB_SOCKET_SWF_LOCATION) {
+      logger.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf");
+      return;
+    }
+    if (!window.WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR &&
+        !WEB_SOCKET_SWF_LOCATION.match(/(^|\/)WebSocketMainInsecure\.swf(\?.*)?$/) &&
+        WEB_SOCKET_SWF_LOCATION.match(/^\w+:\/\/([^\/]+)/)) {
+      var swfHost = RegExp.$1;
+      if (location.host != swfHost) {
+        logger.error(
+            "[WebSocket] You must host HTML and WebSocketMain.swf in the same host " +
+            "('" + location.host + "' != '" + swfHost + "'). " +
+            "See also 'How to host HTML file and SWF file in different domains' section " +
+            "in README.md. If you use WebSocketMainInsecure.swf, you can suppress this message " +
+            "by WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR = true;");
+      }
+    }
+    var container = document.createElement("div");
+    container.id = "webSocketContainer";
+    // Hides Flash box. We cannot use display: none or visibility: hidden because it prevents
+    // Flash from loading at least in IE. So we move it out of the screen at (-100, -100).
+    // But this even doesn't work with Flash Lite (e.g. in Droid Incredible). So with Flash
+    // Lite, we put it at (0, 0). This shows 1x1 box visible at left-top corner but this is
+    // the best we can do as far as we know now.
+    container.style.position = "absolute";
+    if (WebSocket.__isFlashLite()) {
+      container.style.left = "0px";
+      container.style.top = "0px";
+    } else {
+      container.style.left = "-100px";
+      container.style.top = "-100px";
+    }
+    var holder = document.createElement("div");
+    holder.id = "webSocketFlash";
+    container.appendChild(holder);
+    document.body.appendChild(container);
+    // See this article for hasPriority:
+    // http://help.adobe.com/en_US/as3/mobile/WS4bebcd66a74275c36cfb8137124318eebc6-7ffd.html
+    swfobject.embedSWF(
+      WEB_SOCKET_SWF_LOCATION,
+      "webSocketFlash",
+      "1" /* width */,
+      "1" /* height */,
+      "10.0.0" /* SWF version */,
+      null,
+      null,
+      {hasPriority: true, swliveconnect : true, allowScriptAccess: "always"},
+      null,
+      function(e) {
+        if (!e.success) {
+          logger.error("[WebSocket] swfobject.embedSWF failed");
+        }
+      }
+    );
+    
+  };
+  
+  /**
+   * Called by Flash to notify JS that it's fully loaded and ready
+   * for communication.
+   */
+  WebSocket.__onFlashInitialized = function() {
+    // We need to set a timeout here to avoid round-trip calls
+    // to flash during the initialization process.
+    setTimeout(function() {
+      WebSocket.__flash = document.getElementById("webSocketFlash");
+      WebSocket.__flash.setCallerUrl(location.href);
+      WebSocket.__flash.setDebug(!!window.WEB_SOCKET_DEBUG);
+      for (var i = 0; i < WebSocket.__tasks.length; ++i) {
+        WebSocket.__tasks[i]();
+      }
+      WebSocket.__tasks = [];
+    }, 0);
+  };
+  
+  /**
+   * Called by Flash to notify WebSockets events are fired.
+   */
+  WebSocket.__onFlashEvent = function() {
+    setTimeout(function() {
+      try {
+        // Gets events using receiveEvents() instead of getting it from event object
+        // of Flash event. This is to make sure to keep message order.
+        // It seems sometimes Flash events don't arrive in the same order as they are sent.
+        var events = WebSocket.__flash.receiveEvents();
+        for (var i = 0; i < events.length; ++i) {
+          WebSocket.__instances[events[i].webSocketId].__handleEvent(events[i]);
+        }
+      } catch (e) {
+        logger.error(e);
+      }
+    }, 0);
+    return true;
+  };
+  
+  // Called by Flash.
+  WebSocket.__log = function(message) {
+    logger.log(decodeURIComponent(message));
+  };
+  
+  // Called by Flash.
+  WebSocket.__error = function(message) {
+    logger.error(decodeURIComponent(message));
+  };
+  
+  WebSocket.__addTask = function(task) {
+    if (WebSocket.__flash) {
+      task();
+    } else {
+      WebSocket.__tasks.push(task);
+    }
+  };
+  
+  /**
+   * Test if the browser is running flash lite.
+   * @return {boolean} True if flash lite is running, false otherwise.
+   */
+  WebSocket.__isFlashLite = function() {
+    if (!window.navigator || !window.navigator.mimeTypes) {
+      return false;
+    }
+    var mimeType = window.navigator.mimeTypes["application/x-shockwave-flash"];
+    if (!mimeType || !mimeType.enabledPlugin || !mimeType.enabledPlugin.filename) {
+      return false;
+    }
+    return mimeType.enabledPlugin.filename.match(/flashlite/i) ? true : false;
+  };
+  
+  if (!window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION) {
+    // NOTE:
+    //   This fires immediately if web_socket.js is dynamically loaded after
+    //   the document is loaded.
+    swfobject.addDomLoadEvent(function() {
+      WebSocket.__initialize();
+    });
+  }
+  
+})();

gevent_cometd/templates/demo.mak

 <head>
 <meta charset="UTF-8">
 <title>Chat demo</title>
+<script type="text/javascript" src="/static/swfobject.js"></script>
+<script type="text/javascript" src="/static/websocket.js"></script>
+<script type="text/javascript" src="/static/reconnecting_websocket.js"></script>
+
 % if request.params.get('dojo'):
 	<script type="text/javascript">
 	var dojoConfig = {
 user <input type="text" name="user" value="User">
 </p>
 <p>
-<textarea name="message" style="width:300px"></textarea>
+<input type="text" name="message" style="width:300px"/>
 </p>
 <p>
 <input type="submit" value="send message">
 });
 </script>
 %else:
-<script type="text/javascript">
-$('#msg_form').submit(function() {
-  alert('Handler for .submit() called.');
-  return false;
-});
+<script src="${config['webapp_url']}/static/jq_client.js"></script>
+<script>
+WEB_SOCKET_SWF_LOCATION = "${config['webapp_url']}/static/WebSocketMain.swf";
+demo_start('${config['webapp_url']}',
+    '${config['cometd.server']['server']}',
+    {'user' : Math.random(),
+        'channels' : [ 'pub_chan', 'pub_chan2' ]
+    })
 </script>
 %endif
 

gevent_cometd/user.py

 import gevent
 import logging
 import uuid
+import json
 
 log = logging.getLogger(__name__)
 
     def __init__(self, user, status):
         self.user = user
         self.status = status
-        self.connections = [] #holds ids of connections
-        self.channels = [] #holds ids of channels
+        self.connections = []  # holds ids of connections
+        self.channels = []  # holds ids of channels
         self.last_active = datetime.datetime.utcnow()
 
-    #TODO currently broken
+    # TODO currently broken
     def notify_presence(self, user, status):
         # notify channel new user connected
         # (if there are no other connections of user present in this channel)
                    'type': 'join',
                    'timestamp': datetime.datetime.utcnow()
                    }
-                #todo presence
+                # todo presence
                 self.add_message(message, exclude_user=connection.user)
 
     @classmethod
         for conn_id in self.connections:
             connection = Connection.by_id(conn_id)
             if connection:
-                connection.add_message(message)                
-            
+                connection.add_message(message)
+
 
     def __repr__(self):
         return '<User:%s, status:%s, connections:%s>' % (self.user, self.status, len(self.connections))
 class Connection(object):
     """ Represents a browser connection"""
     def __init__(self, user, conn_id=None):
-        self.user = user #hold user id/name of connection
+        self.user = user  # hold user id/name of connection
         self.last_active = datetime.datetime.utcnow()
         self.id = conn_id or unicode(uuid.uuid4())
         self.messages = []
         return '<Connection: id:%s, owner:%s>' % (self.id, self.user)
 
     def add_message(self, message, stats=None):
+        import gevent_cometd.util as util
         gevent_cometd.stats['total_messages'] += 1
+        self.last_update = datetime.datetime.utcnow()
         self.messages.append(message)
-        self.last_update = datetime.datetime.utcnow()
         self.event.set()
         self.event.clear()
 
                 # wake up connection
                 conn.event.set()
                 conn.event.clear()
-                #delete conn references from users
+                # delete conn references from users
                 user = User.by_name(conn.user)
                 if user:
                     user.connections.remove(key)
         return collected
 
     def mark_for_gc(self):
-        #set last active time for connection 1 hour in past for GC
+        # set last active time for connection 1 hour in past for GC
         self.last_active -= datetime.timedelta(minutes=60)