Web Sockets in Tornado

By Bret Taylor · December 31, 2009

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.

OAuth WRAP support in FriendFeed for feedback

By Bret Taylor · December 21, 2009

As David Recordon mentioned on the Facebook Developer Blog, the Facebook Platform team is working to move Facebook Connect over to OAuth over the next year. As a part of that effort, we have been working with Google, Microsoft, Yahoo!, and a number of other engineers on the OAuth WRAP standard, which aims to be much simpler to implement than the existing OAuth standard.

As a part of this effort, I implemented OAuth WRAP support in FriendFeed so we could have a live implementation of the standard to play with. I am really interested in getting feedback about the standard from developers who are currently using OAuth or Facebook Connect or both. If you have never used WRAP before, try it out, and let me know what you think (the comments on this post are a great forum, or feel free to send feedback to the OAuth WRAP mailing list).

To get started:

The FriendFeed WRAP implementation does not support refreshing tokens; the tokens never expire.

An overview of OAuth WRAP

The main difference between OAuth and OAuth WRAP is that WRAP does not have elaborate token exchanges or signature schemes. Instead, all server-to-server WRAP calls happen via SSL. The "access token," which grants your client the ability to make API calls on a user's behalf, is protected by SSL rather than by a shared secret and signature scheme.

The browser-based authorization experience looks exactly the same to an end user. First, you redirect the user to the Authorize URL (https://friendfeed.com/account/wrap/authorize) with your Consumer Key and callback URL. After the user authorizes, the server redirects the user back to your callback URL with a verification code. You call the Access Token URL (https://friendfeed.com/account/wrap/access_token) with that verification code to get back the Access Token.

After that, you simply need to make all your API calls via HTTPS, and you include the Access Token in the URL or in an HTTP header. There are no signatures, and no additional token exchanges necessary. Your API calls will look like https://friendfeed-api.com/v2/feed/home?wrap_access_token=....

My initial experience with WRAP

It was really easy to implement OAuth WRAP support in FriendFeed. I was able to implement WRAP on top of our existing support for OAuth, using the same tokens for both. As a consequence, our existing user interfaces for revoking applications work whether an app is using OAuth or OAuth WRAP. If we hadn't implemented OAuth support, OAuth WRAP would have been much easier to implement on its own because it is stateless; the verification code / access token exchange is so much simpler than the OAuth token exchange protocol.

On the client side, it was also much easier because of the lack of signatures (HTTPS calls are just as easy as HTTP with most HTTP client libraries). Using HTTPS for all requests seemed a bit weird at first, but in practice I realized I was simply moving signature calculation one level lower in the stack. I am curious how you all feel about it when you try the API out.

My tech talk on Tornado (video and slides)

By Bret Taylor · September 25, 2009

I gave a tech talk on Tornado yesterday evening at Facebook's offices. My slides and a video of the talk are below. If you have any questions, feel free to comment below, or join the Tornado discussion group to chat with other Tornado developers.

Archive