Quantcast
Channel: Planet Plone - Where Developers And Integrators Write
Viewing all articles
Browse latest Browse all 3535

Dylan Jay: Node Schmode! pythonic realtime web. Part I

$
0
0

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 this

Picture_8

If 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.

When I first wanted to make a app like this I found tutorials with lots of different libraries, technologies etc. They normally had a chunk of code that made it seem really easy. Here is my such chunk of code (app.pt) minus the map and location javascript.

<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&amp;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.

Permalink | Leave a comment  »


Viewing all articles
Browse latest Browse all 3535

Trending Articles