December 31, 2009

Web Sockets in Tornado

I have been playing around with HTML 5 Web Sockets for a personal project. The Web Sockets API enables web browsers to maintain a bi-directional communication channel to a server, which in turn makes implementing real-time web sites about 1000% easier than it is today.

Currently, the only reasonable technical facility available to browsers to communicate to web servers is XMLHttpRequest. Sites that update in real-time like FriendFeed use a number of horrible hacks on top of XMLHttpRequest like long-polling to get data in real-time. (If you are interested, Tornado ships with a chat demo application that uses this long-polling technique - here is the JavaScript in all its hacky glory).

Web Sockets support a much simpler interface that enables both the client and the server send messages to each other asynchronously:

var ws = new WebSocket("ws://friendfeed.com/websocket");
ws.onopen = function() {
    ws.send("This is a message from the browser to the server");
};
ws.onmessage = function(event) {
    alert("The server sent a message: " + event.data);
};

Google Chrome just added support for Web Sockets, and most major browsers will deploy Web Socket support in their next major release. Chrome's release inspired me to play around with the protocol, which is extremely compatible with the design and goals of the Tornado web server we released a few months ago.

I implemented a Web Socket module for Tornado. Here is an example handler that echos back every message the client sends:

class EchoWebSocket(tornado.websocket.WebSocketHandler):
    def open(self):
        self.receive_message(self.on_message)

    def on_message(self, message):
       self.write_message(u"You said: " + message)

You map WebSocketHandlers to URLs the same as all of your other handlers in your application. However, since the Web Socket protocol is message-based and not really related to HTTP, all of the standard Tornado read and write methods have been replaced with the two methods send_message() and receive_message().

You can download the module on Github. Let me know if you encounter any issues if you start using it.

With this Tornado module, Web Sockets listen on the same host and port as all of your other request handlers. Once the initial HTTP connection is made, the connection protocol "switches" (or "upgrades" in the language of the Web Socket spec) from HTTP to the Web Socket protocol. I am not sure how most load balancers or proxies would respond to connections like this (most would probably close the connection or puke on the response). I plan on playing around a bit with nginx this weekend, but if you have had any anecdotal experience getting Web Sockets working with production load balancers, I would love to hear about it in the comments.