Skip to content

Commit

Permalink
Large refactor to websocket handler to implement some more appropriat…
Browse files Browse the repository at this point in the history
…e concurrency. Added options menu to filter by forum (also added support for filtering thread title via query). New shiny logo
  • Loading branch information
dcramer committed Mar 20, 2012
1 parent ec58d02 commit c6cd393
Show file tree
Hide file tree
Showing 6 changed files with 201 additions and 34 deletions.
2 changes: 1 addition & 1 deletion 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
Expand Down
95 changes: 66 additions & 29 deletions server.py
Expand Up @@ -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
Expand Down Expand Up @@ -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']
Expand Down
20 changes: 19 additions & 1 deletion site/index.html
Expand Up @@ -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>
Expand Down
Binary file modified 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.
46 changes: 44 additions & 2 deletions site/media/orbital.js
Expand Up @@ -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){
Expand Down Expand Up @@ -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;

Expand All @@ -158,6 +194,12 @@

orbital.watchActiveLayer();

orbital.initOptions();

window.onbeforeunload = function() {
orbital.disconnect();
};

orbital.connect(params);
};
})();
72 changes: 71 additions & 1 deletion site/media/screen.css
Expand Up @@ -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;
Expand Down Expand Up @@ -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.