Skip to content
Browse files

Large refactor to websocket handler to implement some more appropriat…

…e concurrency. Added options menu to filter by forum (also added support for filtering thread title via query). New shiny logo
  • Loading branch information...
1 parent ec58d02 commit c6cd393ba0364cd10bac80c1ea3bc6cbc58faed8 @dcramer dcramer committed Mar 19, 2012
Showing with 201 additions and 34 deletions.
  1. +1 −1 requirements.txt
  2. +66 −29 server.py
  3. +19 −1 site/index.html
  4. BIN site/media/logo_dark.png
  5. +44 −2 site/media/orbital.js
  6. +71 −1 site/media/screen.css
View
2 requirements.txt
@@ -1,5 +1,5 @@
gevent==0.13.6
-gevent-websocket==0.3.1
+gevent-websocket==0.3.4
gevent_zeromq==0.2.2
greenlet==0.3.3
pygeoip==0.2.2
View
95 server.py
@@ -10,9 +10,10 @@
import gevent
import mimetypes
import os.path
+import uuid
from multiprocessing import Process
-from gevent import pywsgi, monkey
+from gevent import pywsgi, monkey, Greenlet
from geventwebsocket.handler import WebSocketHandler
from gevent_zeromq import zmq
@@ -43,61 +44,97 @@ def run_publisher():
publisher.close()
+class WebsocketPublisher(object):
+ def __init__(self, subscriber, ws, params=None):
+ self.subscriber = subscriber
+ self.ws = ws
+ self.params = params
+
+ def run(self):
+ ws = self.ws
+ subscriber = self.subscriber
+ while True:
+ params = self.params
+
+ message = subscriber.recv()
+
+ if params is None:
+ continue
+
+ try:
+ data = json.loads(message)['post']
+ except KeyError:
+ print 'Invalid data', message
+ continue
+
+ if params.get('forum') and data['forum_id'] not in params['forum']:
+ continue
+
+ if params.get('query') and not any(data['thread_title'].startswith(t) for t in params['query']):
+ continue
+
+ ws.send(message)
+
+
def run_websockets():
+ clients = set()
+
def handle_ws(ws, environ):
context = zmq.Context()
subscriber = context.socket(zmq.SUB)
subscriber.connect("tcp://127.0.0.1:5555")
subscriber.setsockopt(zmq.SUBSCRIBE, "")
- print "[%s] Client connected" % environ['REMOTE_ADDR']
+ client_id = uuid.uuid4().hex
+ clients.add(client_id)
+ publisher = WebsocketPublisher(subscriber, ws)
+ publisher_thread = gevent.spawn(publisher.run)
- params = None
+ print "[%s] Client connected %r (%s clients total)" % (client_id, environ['REMOTE_ADDR'], len(clients), )
try:
while True:
- if params is None:
- print '[%s] Waiting on subscription params' % environ['REMOTE_ADDR']
- m = ws.receive()
- if not m:
- return
- cmd, args = m.split(' ', 1)
+ message = ws.receive()
+ if not message:
+ return
+
+ message_bits = message.split(' ', 1)
+
+ cmd = message_bits[0]
+
+ if cmd == 'OK':
+ continue
+
+ if cmd == 'SUB':
+ if len(message_bits) == 2:
+ args = message_bits[-1]
+ else:
+ args = ''
+
args = args.strip()
if args == '':
args = '*'
- if cmd != 'SUB':
- return
-
if args == '*':
params = {}
else:
params = dict(a.split('=') for a in args.split('&'))
for key, value in params.iteritems():
- params[key] = map(lambda x: x.strip().lower(), value.split(' OR '))
-
- print "[%s] Subscription established %s" % (environ['REMOTE_ADDR'], params)
+ params[key] = filter(bool, map(lambda x: x.strip().lower(), value.split(' OR ')))
- message = subscriber.recv()
+ publisher.params = params
- try:
- data = json.loads(message)['post']
- except KeyError:
- print 'Invalid data', message
- continue
+ print "[%s] Subscription established %s" % (client_id, params)
- if 'forum' in params and data['forum_id'] not in params['forum']:
- continue
-
- if 'query' in params and data['thread_title'] not in params['query']:
- continue
-
- ws.send(message)
finally:
+ publisher_thread.kill()
subscriber.close()
- print "[%s] Client disconnected" % environ['REMOTE_ADDR']
+
+ clients.remove(client_id)
+
+ print "[%s] Client disconnected" % client_id
def handle(environ, start_response):
path_info = environ['PATH_INFO']
View
20 site/index.html
@@ -16,11 +16,29 @@
</div>
</section>
<section id="header">
- <img src="media/logo_dark.png" id="logo">
+ <div id="logo">
+ <img src="media/logo_dark.png">
+ </div>
<div id="copy">
<strong>Conversations</strong> around the world on the <strong><a href="http://disqus.com/about">DISQUS network</a></strong>
+ | <a href="javascript:void(0)" class="options">Map Options</a>
</div>
</section>
+ <section id="options">
+ <a href="javascript:void(0)" class="close">Close</a>
+ <div class="title">Map Options</div>
+ <form method="POST">
+ <fieldset class="body">
+ <div class="field">
+ <label>Limit data to forum:</label>
+ <input type="text" name="forum" placeholder="e.g. cnn">
+ </div>
+ <div class="submit">
+ <button type="submit">Apply</button>
+ </div>
+ </fieldset>
+ </form>
+ </section>
</div>
<script src="media/jCanvaScript.1.5.14.min.js"></script>
<script src="media/jquery.js"></script>
View
BIN site/media/logo_dark.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
46 site/media/orbital.js
@@ -90,22 +90,43 @@
};
orbital.connect = function(params){
+ // if params are empty, set them to * (all results)
if (params === undefined || params === '') {
params = '*';
}
+
+ orbital.params = params;
+
+ // close the open socket first
+ orbital.disconnect();
+
+ console.log('Opening websocket connection');
orbital.socket = new WebSocket(host);
orbital.socket.onclose = function(e){
setTimeout(function(){
- orbital.connect(params);
+ orbital.connect(orbital.params);
}, 3000);
};
orbital.socket.onopen = function(){
- this.send('SUB ' + params);
+ this.send('SUB ' + orbital.params);
};
orbital.socket.onmessage = function(msg){
var data = JSON.parse(msg.data);
orbital.addData(data);
+ this.send('OK');
};
+
+ };
+
+ orbital.disconnect = function(){
+ if (orbital.socket) {
+ console.log('Closing websocket connection');
+ orbital.socket.onclose = function(){
+ console.log('Socket closed');
+ };
+ orbital.socket.close();
+ orbital.socket = null;
+ }
};
orbital.createLayer = function(dateKey){
@@ -149,6 +170,21 @@
setTimeout(orbital.watchActiveLayer, 3000);
};
+ orbital.initOptions = function() {
+ var $options = $('#options');
+ $('.close', $options).click(function(){
+ $('#options').hide();
+ });
+ $('#header .options').click(function(){
+ $options.show();
+ });
+ $('form', $options).submit(function(){
+ orbital.connect($(this).serialize());
+
+ return false;
+ });
+ };
+
orbital.init = function(el, params) {
element = el;
@@ -158,6 +194,12 @@
orbital.watchActiveLayer();
+ orbital.initOptions();
+
+ window.onbeforeunload = function() {
+ orbital.disconnect();
+ };
+
orbital.connect(params);
};
})();
View
72 site/media/screen.css
@@ -61,6 +61,9 @@ body, html {
color: white;
overflow: hidden;
font-family: "Helvetica Neue", arial;
+ font-size: 13px;
+ background-image: -moz-radial-gradient(center -5% 45deg, #badfef, #4b94d0 20%, #2e70b4 30%, #002a77 60%);
+ background-image: -webkit-gradient(radial, 50% -5%, 1, 50% -5%, 1800, from(#badfef), color-stop(20%, #4b94d0), color-stop(30%, #2e70b4), color-stop(60%, #002a77));
}
#map_container {
overflow: hidden;
@@ -154,20 +157,87 @@ polygon {
border-bottom: 1px solid #4b78a1;
}
#header #logo {
+ line-height: 16px;
+ vertical-align: middle;
+}
+#header #logo img {
+ float: left;
+ display: inline-block;
+ width: 164px;
+ height: 16px;
max-height: 100%;
}
#header #copy {
- color: #ddd;
+ color: #ccc;
position: absolute;
right: 10px;
top: 10px;
line-height: 16px;
font-size: 0.7em;
}
+#header #copy a.options {
+ color: #fff;
+ font-weight: bold;
+}
#header #copy strong {
font-weight: bold;
}
#header #copy a {
color: inherit;
text-decoration: none;
+}
+#options {
+ position: absolute;
+ margin: 40px 10px;
+ width: 300px;
+ right: 0;
+ top: 0;
+ background: #222;
+ border: 1px solid #4b78a1;
+ display: none;
+}
+#options .title {
+ padding: 10px;
+ font-weight: bold;
+ background: #222; /* Old browsers */
+ background: -moz-linear-gradient(top, #333333 0%, #222222 100%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#333333), color-stop(100%,#222222)); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, #333333 0%,#222222 100%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, #333333 0%,#222222 100%); /* Opera 11.10+ */
+ background: -ms-linear-gradient(top, #333333 0%,#222222 100%); /* IE10+ */
+ background: linear-gradient(top, #333333 0%,#222222 100%); /* W3C */
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#333333', endColorstr='#222222',GradientType=0 ); /* IE6-9 */
+ border-bottom: 1px solid #333;
+}
+#options .close {
+ position: absolute;
+ right: 10px;
+ top: 10px;
+ color: #fff;
+ line-height: 14px;
+ font-size: 11px;
+ display: block;
+}
+#options .body {
+ padding: 10px;
+ line-height: 16px;
+}
+#options .body .field {
+ margin-bottom: 10px;
+}
+#options .body label {
+ display: block;
+ margin-bottom: 5px;
+}
+#options .body input {
+ color: #fff;
+ display: block;
+ border: none;
+ background: #000;
+ padding: 4px 3px;
+ margin: 0;
+ width: 272px;
+}
+#options .body .submit {
+
}

0 comments on commit c6cd393

Please sign in to comment.
Something went wrong with that request. Please try again.