Yoan Blanc’s weblog

Another swiss guy lost in Paris

Ajax is everywhere, everyone loves it. You can play with it easily with any popular JavaScript libraries, in the Firebug console or even from Spidermonkey (a JavaScript shell). Ajax the giant has a little brother called Comet, unlike Ajax Comet works but pushing the data from the server to the client and not by polling the server. It’s used in (i.e. financial) websites to get the page updated every couple of seconds.

There are many tools to do this like: cometd, mod_pubsub, Nevow Athena (built upon Twisted)… but most of them are productions tools and, for a proof of concept or a little try, sound overkill. The idea of Comet is to have an hidden iframe in which the server will write every n seconds. It’s the same file, so the same connection, so for example PHP will kill it after the 30 seconds limit. The iframe contains a JavaScript call to the parent page to give it the data. Finally the parent page display it.

I’ll build a little financial-like page to display every seconds the load average or my box. NB: it’s completely useless.

Web.py is a very simple Python web framework created for Reddit when they rewrote their code from LISP to Python. Be sure to do the tutorial (install web.py from easy_install).

First of all, the url handling, we’ll have an index page and a js file:

urls = (
 '/(.*).js$', 'js',
 '/(.*)', 'index' )

js is the class that will handle JavaScript files, and index the HTML one. Next, the data for the load average ((1.111, 2.222, 3.333)): load of the last minute, load of the past 5 minutes and the load of the 15 last minutes.

def getloadavg():
 a,b,c = os.getloadavg()
 format = "%.3f"
 return (format % a, format % b, format % c)

The index handler is quite trivial. Give the load average data to the template and display it.

class index(object):
 def GET(self, name):
  web.header('Content-type', 'text/html')

  load = getloadavg()
  print render.index(*load)

Then the js handler that will show every couple of seconds a piece of JavaScript, here the cheetah template:

$def with (a, b, c)
<script type="text/javascript">
 window.parent.update($a, $b, $c);
</script>

The trick is how the server (CherryPy in this case) will handle the connection by keeping it open and flushing it.

class js(object):
 def GET(self, name):
  web.header('Content-type', 'text/html')
  yield web.flush()
  print "<html><body>"
  for i in xrange(1000):
   time.sleep(1)
   load = getloadavg()
   print render.js(*load)
  print "</html></body>"

Everything is done by the flush call. Then the system will loop for a thousand operations and yielding every second the above piece of JavaScript. (see code.py)

In the main template the job is to care the iframe, and handle the data send via the update call. Download the full source tarball and run it: python code.py.

Application of this code are pretty nothing because the main issue of Comet is the server load, the JavaScript side is quite easy (but interesting still). The application of Comet are with financial quotes, chats like GTalk inside GMail (aka Google Mail). The Flash player offers XMLsocket which can be an alternative to the iframe (i.e. Juggernaut for Ruby on Rails). And I’m sure that Twisted can do a better web server for Comet things.

Si tous le monde joue ou a déjà joué avec Ajax (via XMLHttpRequest) via les nombreuses bibliothèques JavaScript, depuis la console de FireBug ou même avec SpiderMonkey (un shell JavaScript tout droit sorti de Gecko). Comet, le petit frère d’Ajax (le titan), partage la même idée de mettre à jour un petit bout de contenu dans une page, sans avoir à la recharger. Ajax fonctionne sur le principe du client demandant l’information au serveur et Comet fait exactement l’inverse, le serveur envoyant l’information au client dès que possible (ou de manière temporisée dans l’idée que ce temps est relativement cours).

Comet with Web.py

Il y a presque autant d’outils pour faire du Comet côté serveur que de célèbres bibliothèques JavaScript pour Ajax comme: cometd, mod_pubsub, Nevow Athena (utilisant Twisted)… De mon point de vue, ils sont essentiellement orientés production et cela ne facilite pas leur approche, juste pour tester.

Donc, comme j’aime faire des choses juste pour tester, à partir de web.py, j’ai tenté de contrefaire le système utilisé sur les sites financiers en mettant à jouer les valeurs concernant la charge de ma machine (load average). Oui, c’est absolument inutile, je confirme.

Comme d’hab. la partie intéressante est en anglais (on ne se refait pas), je ne peux que vous conseiller d’attraper les sources et de tester tout ça. Faire le tutoriel de web.py avant peu servir, c'est rapide et simple.

In fine, pour parler un peu de Comet qui a tout de même un usage très marginal, il est intéressant de connaître ces usages. GMail s’en sert pour gérer les chats au sein du client mail. Il est également possible d’éviter l’usage de l’iframe en se servant d’une animation Flash. Flash permet de se servir d’XMLSocket maintenant un canal de communication ouvert entre le serveur et client. Ce dernier à le grand avantage d’être bi-directionnel (voir Juggernaut pour Ruby on Rails).

Pour ne pas frustrer mon lectorat francophone aussi puissant soit-il, voici une petite astuce JavaScript concernant les expressions régulières qui n’est que trop peu documentée. Le but étant d’avoir le preg_match_all si apprécié en PHP. Échauffement, (servez-vous de Firebug pour tester):

>>> "ab cd ef cb".search(/\b(c.)\b/)
3

En effet, la première occurence d’un c suivi d’une lettre est à la position 3. Avoir une expression régulier comme motif de recherche est intéressant. Maintenant, comment trouver toutes le occurence d’un coup? Étape intermédiaire:

>>> "ab cd ef cb".replace(/\bc(.)\b/, "Z$1")
"ab Zd ef Zb"

Donc il est possible de faire un rechercher remplacer en réutilisant l’expression trouvée. La dernière étape est d'extraire l'expression trouvée.

>>> var matches = [];
>>> "ab cd ef cb".replace(/\bc(.)\b/g,
 function(str, match){
  matches.push([str, match]);
  return str;
 })
"ab cd ef cb"
>>> // then display matches
>>> matches
[["cd", "d"], ["cb", "b"]]

La fonction replace ne fait rien sur la chaîne elle-même (retournant l’entrée sans la modifier, return str) mais a comme effet de bord de remplir un tableau matches des éléments trouvés. Le but initial étant de faire un traitement plus avant qu’un simple rechercher remplacer, mais qu’on peut détourner à d’autre fins.

En espèrant que ça puisse vous servir.

About

meYoan Blanc is a web developer that lives in Paris (19, rue Bichat75010France) works for Yahoo! and comes from Switzerland ( La Chaux-de-Fonds). This web site is for this weblog, a playground for random stuffs and can help keepingconnected with some friends out there.

Get my vCard or contact me by phone (+33 1 74 30 12 92) or email ().

Misc

RSS, list.blogug.ch

This site reflects only my opinion and is not affiliated with my employer.

copyright 2006-2007 — doSimple.ch