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:
- one in Python (using web.py 0.3): webpy event-source.tgz
- and the other uses MochiWeb: mochiweb event-source.tgz.
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.