One month ago, I painfully managed to show the possibilities that offers the upcoming html5 tag: event-source (Server-sent event), during the Zürich Webtuesday of August. But it ended up being more confusing than really interesting. I need to practice hardly my presentation skills. I’ll try to fix that here.
Event-source is a way to have a continuous streaming data from the server with the minimum overhead for the front-end developer. It’s not new at all but the motivations of having Comet-based applications and the growing interests around HTML5 can influence us to follow that direction. Event-source is part of the many communication capabilities that will potentially (reading “one day”) offer HTML5.
First of all, test it (the Opera browser is required), they are two examples:
Both of them are adaptations of old plays, the first one was using an hidden iframe and the second the XHR long-polling.
Beside the fact that using event-source is very trivial, basically listening to a DOM event. It has other advantages over the existing solutions:
- Latency, using XHR long-polling forces you to reconnect once an event has been sent. It means that if you have events that occurs very quickly some of them will have to wait and come as a batch with the next call. I cannot measure the implication from a server perspective, but can imagine that opening a connection has a cost that isn’t negligible and something simply not feasible due to a high flow of message.
- It’s not a hack, so you can have forward compatibility.
- Simplicity, the language is quite simple, you’ve got a key (event name) and value (message or data). Which is how many web application works these days with Memcached, CouchDB or Amazon’s Dynamo.
Its only (and huge) disadvantage is that it isn’t part of your browser yet. Opera, who created the first specification for this, supports it; but — as the specs aren’t finished — this is afaik their early version that is implemented.
Opera has an example using Python that is two years old now and still working. The code in the example shows a time.sleep and this is something — I think — is bad. I’d like to be able to do that with Twisted or Eventlet, but at the moment, I’ll stick with Mochiweb and Erlang. That introduces outputting chunked content from Mochiweb that is quite interesting too.
Basically, the main code remains the same (here simplified).
case Req:get(method) of
Method when Method =:= ’GET’; Method =:= ’HEAD’ ->
case Path of
"chat" ->
Room = get_the_room(),
Room ! {self(), subscribe},
receive
subscribed ->
Response = Req:respond({
200, [{"Content-Type", "application/x-dom-event-stream; charset=utf-8"}], chunked}),
wait_for_a_message(Response, ?TIMEOUT),
after 1000 ->
true
end,
Room ! {self(), unsubscribe};
% [...]
Room
is the process that handles the messages. With this very simple chat, the system is stateless regarding to the clients, the client don’t live through reconnection which can lead to losing some messages, but I don’t care for now. So, you subscribe to the Room, wait for being correctly subscribed and starts waiting for messages.
Starting means here, displaying the HTTP headers, so starting the response with Req:respond
, status is 200, one specific header for event-source and chunked because this is what we want. And then let’s wait for incoming messages.
wait_for_a_message(Response, Timer) when Timer < 0 ->
Response:write_chunk("");
wait_for_a_message(Response, Timer) ->
receive Message ->
Response:write_chunk(lists:flatten(io_lib:format("Event: ~s~ndata: ~s~n~n", ["message", Message]))),
wait_for_a_message(Response, Timer)
after ?PROXY_TIMEOUT ->
%% write a comment (every 15s.)
Response:write_chunk(lists:flatten(io_lib:format(":~n", []))),
wait_for_a_message(Response, Timer - ?PROXY_TIMEOUT)
end.
Basically, wait for a message, display it or display a comment to avoid being disconnected by a web proxy and start waiting again. To avoid waiting forever, there is also another timeout. I’m sure we are awake of the client closing the connection. And when the timeout is over, we can close the connection by sending an empty message. If I know that the one for the proxy is highly recommended (or mandatory), the other one is more a magical number I put here.
% Global Timeout
-define(TIMEOUT, 150000).
% Apache Timeout is 15s,
% many proxies act the same way.
-define(PROXY_TIMEOUT, 15000).
The output looks like you can find on the examples:
Event: message
data: foo
Event: message
data: bar
I’d like to see a cross-browser compatible implementation of event-source using the existing tools we have, like XHR (I don’t anything else actually). It’ll be a hack anyway, but maybe a necessary one to make the transition possible. The example I made had two entry points, one for event-source to get the events and the other for XHR to send the events.
Il y a un peu plus d’un mois, j’ai difficilement tenté d’introduire lors du Webtuesday zürichois le tag event-source (Source-sent event). Existant au sein du navigateur Opera, où Ian Hickson y a esquissé la première spécification, il est possible de s’en servir dès aujourd’hui (depuis un peu plus de deux en réalité).
Event-source est une balise HTML (plus des points d’entrée JavaScript) qui permet de récupérer en continu des données depuis un serveur, une page qui se chargerait de manière infinie. C’est tout simplement l’idée de pousser du contenu vers le client, nommé Comet par opposition à Ajax où le client (navigateur internet) demande au besoin de l’information au serveur web. Si l’application exemple de facto, unique et omniprésente est le chat, les besoins de transférer de l’information de manière continue par rapport à en faire la demande de manière temporisée se répandent de plus en plus.
À mon sens, il y a de nombreux avantage de partir avec un système fonctionnant sur cette architecture (le tag et la logique sous-jacente) :
- latence, les données arrivent au fur et à mesure. Le long-polling, à plusieurs reprise présenté ici implique une reconnexion dès qu’une donnée est arrivée. Ce qui est une latence parfois non désirée, pour un site boursier nécessitant des mises à jour très rapide, tout comme, j’imagine, provoque un travail supplémentaire pour le serveur de réouvrir n connexions et réinitialiser autant d’état lors des reconnexions;
- compatilibilité ascendante, si HTML5 est prévu pour 2022 (en souhaitant que ça soit une blague), autant bâtir un système sur une solution vouée à devenir standard plutôt qu’un hack destiné à s’effacer;
- simplicité, oui, c’est très simple. Un système clé/valeur (comme memcache) auquel on souscrit. On s’inscrit à une clé et récupère la valeur sous forme de texte (qui peut représenter du JSON par exemple).
Le problème avec ceci actuellement est double : seul Opera supporte cet balise, et les spécifications ne sont pas terminées donc, c’est une version non-finale qui est actuellement disponible. Avec des pincettes donc.
Je repris les expérimentations faites autour de Comet et les ai adaptées pour utiliser le tag event-source. La première utilise web.py 0.3 et la seconde utilise MochiWeb. Je vous laisse le soin de passer à la version anglaise qui contient des détails plus techniquement croustillants.
Imaginons maintenant les possibilités de ce type de streaming. Des sites comme FriendFeed semblent arriver aux limites des APIs existantes, et cherchent des alternatives comme PubSub de XMPP (utilisé par Twitter ) et Sup (FriendFeed). Il y a également le système de SixApart : Stream Update, basé sur Atom. Ces systèmes ont des détracteurs car apparemment trop éloignés de HTTP et des architectures à saveurs REST. Si le chat est bien évidemment l’exemple plus que répandu, il peut être étendu à du chat plus complexe comme IRC, du jeu en ligne, système affichant des informations en temps réel. Partout là où manquer un message n’est pas important. Il reste à faire un système de confirmation de message si chaque message est important. Le “protocole” Bayeux a peut-être ce méchanisme là, qui n’est pas imposé d’ailleurs.
Quelqu’un a une idée de projet à réaliser ? Il ne manque que la volonté, au fond.