This is summary of a talk I gave recently at #pycon-au (19th Aug, 2012). By realtime web we're talking about ways get quick two communication happening between a browser page and the server.
First off, this post isn't really a criticism of Node.js. Instead the aim is to demonstrate that it is just as easy to create scalable "realtime web" type apps in python, only you tend to find more options and terminology so much of the talk was on demystifying what's available in python. The "Node Schmode!" dig is really aimed at the current hype surrounding node.js and the misconception that some seem to have that node is make it vastly more simple to create asynchronous IO based network apps using node than other alternatives. For a more in depth look at node and the challenges of making "scaling" simple I thouraghly recommend reading Alex Payne's blog post “Node and Scaling in the Small vs Scaling in the Large”. Instead for this talk I'll concentrate on making it simple with python. The short version of the talk is that I created a demo app using gevent, pyramid and SockJS. Since everyone creates chat apps to demonstrate socket like apps I decided to try something different. GPyS show both a users current GPS location and the gyrposcopic orientation of their device and they can see everyone else's location and orientation. The result looked a little like thisIf I asked everyone to tilt their macbook or smartphone to the left, all the little yellow guys would be pointing left. Tilt right they point right and so on. It's a pity I didn;t take the time during the talk to instigate the worlds first virtual browser based mexican wave.
The red pins are those with no accelerometer or gyroscope in their device. The purple pin is the where the talk took place. The guy across the road either ducked out for something to drink or has a crap GPS. Anyway, I digress.
<html>
<head>
<title>GPyS</title>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.6.4.min.js"></script>
<script type="text/javascript" src="http://cdn.sockjs.org/sockjs-0.3.1.min.js"></script>
<script type="text/javascript" src="http://maps.googleapis.com/maps/api/js?libraries=places&sensor=true"></script>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="http://code.jquery.com/mobile/1.1.1/jquery.mobile-1.1.1.min.css" />
<script type="text/javascript" src="http://code.jquery.com/mobile/1.1.1/jquery.mobile-1.1.1.min.js"></script>
<script type="text/javascript" src="${request.application_url}/static/app.js"></script>
<style type="text/css">
html,body{ margin:0; padding:0; height:100%; width:100%; }
#map_canvas {height: 100%;}
#full-size{ height:100%; width:100%; position:absolute; padding: 0px; overflow:hidden;}
@media print { html, body { height: auto;} #map_canvas {height: 650px;} }
</style>
<script type="text/javascript">
$(document).ready(function()
{
var conn;
var last_move;
var sendMove = function(loc, dir) {
if (loc && conn.readyState) {
var new_move = JSON.stringify([loc.lat(),loc.lng(),dir]);
if (last_move != new_move) {
console.log("sent: "+[loc.lat(),loc.lng(),dir]);
conn.send(new_move);
last_move = new_move;
}
}
}
setupMotionHandlers(sendMove);
var connect = function() {
conn = new SockJS('http://' + window.location.host + '/__sockjs__');
console.log('Connecting...');
conn.onopen = function() {
console.log('Connected');
sendMove();
};
conn.onmessage = function (e) {
console.log("received: "+e.data);
showPin(eval(e.data));
};
conn.onclose = function(e) {
clearPins();
setTimeout(connect,2000);
}
}
connect();
});
</script>
</head>
<body>
<div data-role="page" id="searchmap" class="searchmodel">
<div data-role="header" data-add-back-btn="true">
<h1>GPyS (<span id="count">1</span> online) </h1>
</div>
<div data-role="content" id="full-size">
<div id="map_canvas" ></div>
</div>
</div>
</body>
</html>
The code sets up a socket like object using SockJS which has three handlers, onopen, onmessage and onclose. Each message is a json list of any changes in users location or direction. In some additional js we subscribe to some html5 events to tell us when on location or direction changes, turn that into json and "send" that to the server. The server is python (app.py) and looks like this
from pyramid.configuration import Configurator
from pyramid.view import view_config
from pyramid_sockjs.session import Session
import uuid
import json
@view_config(renderer='gpys:app.pt')
def index(request):
return {}
# our data model
people = set([])
class MapSession(Session):
uuid = None
loc = None
def on_open(self):
if self.uuid is None:
self.uuid = str(uuid.uuid4())
print "Open: %s "%self.uuid
people.add(self)
self.send_all()
def on_message(self, loc):
lat,lng,_dir = json.loads(loc)
self.loc = [lat, lng, self.uuid, _dir]
self.manager.broadcast(json.dumps([self.loc]))
def send_all(self):
locs = [person.loc for person in people if person.loc]
self.manager.broadcast(json.dumps(locs))
def on_close(self):
if self in people:
print "Closed: %s "%self.uuid
people.remove(self)
self.manager.broadcast(json.dumps([[None,None,self.uuid,0]]))
def on_closed(self):
self.on_close()
def on_remove(self):
self.on_close()
# our app configuration
def application(global_config, **settings):
config = Configurator(settings=settings)
config.include('pyramid_sockjs')
config.add_static_view(name="static",path="static", cache_max_age=3600)
config.add_sockjs_route(session=MapSession)
config.scan()
return config.make_wsgi_app()
if __name__ == '__main__':
from pyramid_sockjs.paster import gevent_server_runner
gevent_server_runner(application(None), {}, host='0.0.0.0')
<div></div>
<span style="line-height: 1.4em; font-family: 'Bitstream Vera Sans Mono', Courier, monospace; font-size: 12px;">
Also reasonably easy to follow. Along with our static resources and the template for the html (app.pt) we register a session class to be created whenever a new connection is established. The session class as 3 handlers for 'on_open', 'on_message' and 'on_close'. When we get an 'on_open' we send all the locations we know about. When we get an updated location from this user we send everyone this changed location.
This is using python, gevent, pyramid, and SockJS. However why I made those choices and what the other options are was the real subject of the talk, which we'll look at next post.