I had a long chat this morning with David about the post of yesterday and apart his question about using Comet instead of Ajax polling, he made me realizing that it wasn’t that clear. So, there the big picture of the final architecture:
In green: the web Server, Twootr and the Database are using web.py; in red the bot which is composed of the Jabber bot and a RPC bot that are using Twisted. And in blue you from your any web browsers or IM clients (such as GTalk, Gaim, Adium, ...). The communication protocols are HTTP between you and the web server (like usual) and XMPP between your IM client and the JabberBot (called TwootrBot). The web server can ask the bot to performs some actions using XML-RPC. The TwootrBot is using directly twootr to access to the data inside the database (which isn’t perfect, but useful).
We also discussed why XML-RPC and not XMPP, which would also make sense if we consider the web site as an agent that want to talk to the bot. XMPP is somehow heavier than XML-RPC but offer many advantages like the authentification, the status of the agent you may want to talk to is known and it’s extensible by nature. I decided that both are part of a same thing and thus thought that a simple IPC would be enough. You to decide.
And last but not least, Comet, also known as the opposite of Ajax or “the server PUSH”. In short, the client isn’t polling every n seconds for an update but with different mechanism, will wait that the server gives it the information it need. I did a previous experiment with web.py (as well) and the “callback-polling” trick that involves an iframe. This time, I used the “long-polling” which is a more for system when you don’t know when an event will occurs. The “callback-polling” is perfect for highly updated information like quotes on financial websites.
On the server side, it’s quite easy:
class LongPolling(object): def GET(self): # the time the long poll # will run at max seconds = 60 # here the wait loop while True: seconds -= 1 # look for fresh data data = self.getData() if data or not(seconds): break # wait a sec time.sleep(1) # do something with your data pass
Of course, you may imagine that you’ll have some POST parameters and so one. For twootr, I’ve putted the number of seconds inside the arguments that are given by the XMLHttpRequest (XHR) call which makes it usual either in Ajax or Comet mode. And that’s cool.
Now, on the Ajax side (reading here JavaScript): I had to upgrade the Ajax implementation I’ve made for Base2 to have a timeout property, just in the case the server won’t answer in the time we asked it to. A basic polling call can look like this (if we assume it exists a class called XHR that behave like that):
function update() { new XHR("POST", "/ajax/", function(response) { document.body.innerHTML = response.responseText; }); } setInterval(update, 30000);
Every 30 seconds, this script will retreive the HTML from /ajax/
and update the body of the page with it. The important thing is the setInterval
which says that it’s a normal polling behaviour. Then, a long-polling will look this way.
function update() { new XHR("POST", "/ajax/", function(response) { document.body.innerHTML = response.responseText; # this does a chaining # similar to setInterval setTimeout(update, 1000); }); } update();
Do you see the different? As we expect that the XHR call to be long (or very short), the next call to it is done after the last one is finished. This code is very simplified because, you have to handle the mentioned timeout thing as well, which this time become very important.
A long-polling system built this way is more sensitive to errors because the calls are chained and not sent at regular interval. So either your code his perfect or I was wondering about putting a similar mechanism to a watchdog. A function that is executed every n seconds and checks that an Ajax call is currently running, if their isn’t for a certain amount of time it will re-call update.
The issue with Comet is that your server has to handle very well a large amount of parallel connections. Servers, like Apache for example are built to deliver pages as fast as possible and not really to have many people waiting for something to happen. This is way, most of the time, smaller servers are choosen like lighttpd, Jetty, Yaws, …
From a JavaScript point of view, it would be easily doable to offer the ability of a client to switch from Comet to Ajax, from long-polling to simple polling in order to reduce the server load. But if the server is configured to support many simultaneous connection, it might better by doing more calls, very frequently.
The final impression, with long-polling instead of simple polling is a great reactivity. From hitting enter in my IM client to seeing the page updating appears to be as fast as an eyeblink (maybe one from someone who’s tired, but still). That speed is very enjoyable, as a user. A web application that offers directly of feedback of any performed action will appear as faster (even if it’s not the case).
You can find an updated version of twootr and if you want to chat about it, drop a comment bellow (in the “most non-accessible commenting system I’ve ever seen”
according to David, but spammers are blind) or ping me. And if you like Comet, please take a look at the Bayeux protocol which is client server JSON protocol that aims to be used for Comet things.
Ce matin, une longue discussion avec David m’a fait réaliser que, un, le post de hier n’était peut-être pas forcément clair sur comment les différents éléments sont imbriqués et leurs rôles, sur l’éventualité d’utiliser Comet plutôt qu’un traditionnel polling Ajax, et du choix de XML-RPC ? Ce fut très enrichissant au point de me donner l’envie de m’y remettre avec cœur.
Donc l’architecture, une image valant mille (et un) mots, je n’ai plus qu’à en faire la légende. En haut, les utilisateurs, twooteurs du dimanche, qui intéragissent indifféremment avec un navigateur internet (HTTP) ou un client de messagerie instantanée (XMPP aka Jabber). En vert: le serveur internet et toute l’infrastructure backend avec la base de donnée. Cette partie là fonctionne grâce à web.py. Le bot (qui se compose de deux éléments), utilisant Twisted, va recevoir des commandes (représentant des actions à effectuer) de la part du serveur Web, en XML-RPC. Pour les informations entrantes, il utilise le code de Twootr afin d’attaquer directement la base de donnée. Le besoin de synchronisation n’existe que dans le sens du serveur web au bot.
Second point intéressant à discuter est : « pourquoi XML-RPC ? » « Parce que ! » Non, plus sérieusement, le but ici est d’avoir de la communication inter-processus, il semble tout à fait séduisant de se tourner vers XMPP, dont on se sert déjà d’ailleurs. À mon sens, XMPP se destine à faire communiquer des agents : vous, moi ou un bot; entre eux. Il offre de nombreux avantages comme l’authentification, la connaissance du status de ces congénères en plus d’un système extensible à souhait. Le revers de la médaille est sa relative lourdeur en comparaison avec XML-RPC qui est d’une (très) grande simplicité. Et pour continuer à découpler le bot du serveur, puisque c’est ça l’optique d’avoir un langage entre eux pour qu’il puisse communiquer, il pourrait être séduisant que le bot s’adresse au site web via une interface REST par exemple. Ainsi l’un et l’autre pourraient vivre en ne vivant que sur le web, et sans partager des resources communes (comme une base de donnée). J’ai plus abordé cette problématique considérant que il faisait partie d’un tout. Ce qui est bien mais pas top.
Et le meilleur pour la fin : Comet. Il se définit comme étant l’opposé d’Ajax. En pratique, ils reposent sur une même idée : récupérer du contenu qui sera mis à jour dynamiquement. Ajax est du côté du client, qui décide quand il va chercher du contenu et Comet se positionne du côté du serveur, qui sait quand il a de l’information à envoyer au client. J’espère devenir plus clair avec la suite. J’ai eu fait ici un essai avec web.py et Comet également. Ce dernier utilisait une iframe dans laquelle le serveur écrivait des informations à intervales réguliers. Cette méthode-ci se nomme “callback-polling” à cause de l’utilisation d’un callback JavaScript. Ici, j’ai utilisé l’alternative utilisant XMLHttpRequest et faisant ce qu’on appelle du “long-polling” qui consiste à faire polling normal mais le serveur ne va - à priori - répondre que lorsqu’il aura du contenu, des données. Le type parfait pour du chat par exemple, alors que le premier sera parfait si la page requiert des mises à jour très fréquentes, type quotes boursières.
Trouvez dans la zone anglophone, des exemples d’un polling usuel (Ajax) et d’un long-polling (dit Comet), avec le détail serveur appliqué à web.py. La grande différence étant l’usage d’un setInterval pour Ajax qui va temporisé les requêtes et le chaînage pour le long-polling, donc dès qu’une donnée a été reçue, on va relancer une demande en attente de la suite.
Au niveau du code développé pour Base2, j’ai du ajouté la notion de timeout, qui peut tout à fait servir pour un appel normal, mais devient plus important ici. Il faut s’assurer que si le serveur n’a pas donné signe de vie dans un certain temps qu’on renouvelle notre demande. Afin de permettre de synchroniser les deux temps, le code JavaScript de twootr donne son timeout au serveur qui va s’adapter. Je ne connais pas l’éventuelle limite supérieure de temps, et serais curieux de la connaître pour autant qu’il y en ait une.
Après question déploiement, Comet demande que le serveur gère un grand nombre de connexions simultanées. Un serveur web, type Apache, est plutôt conçu pour servir le plus rapidement les pages et pas avoir n pages ouvertes en même temps. Vous verrez donc plus couramment d’autres nom comme lighttpd, Jetty ou Yaws associés à Comet.
L’impression finale de cette relativement petite (à l’échelle de ce projet) modification est relativement impressionnante. L’interface réagit quasiment instantanément à une donnée arrivant en base. Une bonne réactivité donne une impression de vitesse fortement plaisante et c’est un point (l’impression de réactivité) à prendre en compte pour chaque site/application web. Et ça commence par HTML/CSS évidemment.
Trouvez le twootr nouveau, testez-le, envoyez-moi un message pour lancer une autre discussion prolifique ou commencez-la ci-dessous dans les commentaires (NB: votre adresse email n’est conservée que sous forme de son md5 ce qui sert à gravatar uniquement). Et plongez seulement dans le protocole Bayeux qui se base sur JSON et sert parfaitement un usage de Comet.