Announcing faye-websocket, a standards-compliant WebSocket library

While announcing last week’s release of Faye 0.7, I mentioned a couple of things: first, Faye now exposes its WebSocket APIs for everyone to use, and second that I planned to extract some components in order to slim Faye down for the 0.8 release.

Well the first step in that process is now done, and faye-websocket packages are now available for Node and Ruby, installable using npm install faye-websocket and gem install faye-websocket respectively. This process has already shrunk the Faye codebase by over 2,300 LOC but I suppose you’re still wondering why we need another WebSocket library.

It was never my intention to turn Faye into a plain-WebSocket project. I just implemented enough of the protocol to get Faye working, and left it at that. But recently I’ve taken a look around at the other packages for both Node and Ruby, and it turns out pretty much all of them suffer from at least one of the following problems:

The first is a simple matter of maintenance; I could contribute patches to projects to make them work. And it turns out you can’t take a piecemeal approach to implementing wire protocols, you need to deal with whatever might come your way. This is the Internet, after all. I found several libraries where sending them totally valid WebSocket data causes them to crash, simply because they are incomplete.

The second is somewhat trickier, depending on the nature of the project. Some projects are simple WebSocket implementations. Some are transport abstractions aiming to provide a socket-like interface on top of other protocols. And some are full-blown messaging systems with semantics over and above those provided by WebSockets. Faye falls into the latter camp, although its WebSocket code has always been decoupled from the rest of the codebase.

The third is harder to fix, though. If you change the API for a project, you affect all its users. Its author probably has reasons for the API they picked, reasons you weren’t privy to when examining potential use cases. Patches that change the user-facing API of a library are much less likely to be accepted.

But the problem posed by non-standard APIs is, to me, quite serious. The Web Standards movement came about because web developers wanted to write portable code, and have a reasonable expectation of that code working when delivered to users. The WebSocket API is part of that same effort, to provide Web authors with standard interfaces to code against in order to produce portable software. As I have ranted about at length before, ignorance of code portability constitutes a missed opportunity for anyone developing Node programs.

Every time somebody invents a new API for a standard piece of Web infrastructure, they make everyone else’s code less portable. The aim with faye-websocket is to keep the standard WebSocket API so that code developed for the browser can be reused on the server. For example, the Faye pub/sub messaging client uses a ‘transport’ object to send messages to the server; there’s a transport based on XMLHttpRequest, one based on JSON-P, one based on Node’s HTTP library, and one based on WebSockets. If using an HTTP transport, one must pick whether to use XMLHttpRequest or Node HTTP, based on the environment. With WebSockets, we can reuse the same transport class on the server and in the browser, because Faye’s WebSockets have the same API you get in the browser. This means less code to write, and more code you can automatically test from the command line.

And this doesn’t just apply to WebSocket clients. After the handshake, the WebSocket protocol is symmetric: both peers can send and receive messages with the same protocol operating in both directions. It seems to me that any code based on WebSockets ought to be equally happy handling a server-side connection or a client-side one, since both ends of the connection have exactly the same capabilities. For this reason, faye-websocket wraps server-side connections with the standard API:

var WebSocket = require('faye-websocket'),
    http      = require('http'),
    server    = http.createServer();

server.addListener('upgrade', function(request, socket, head) {
  var ws = new WebSocket(request, socket, head);
  
  ws.onmessage = function(event) {
    ws.send(event.data);
  };
  
  ws.onclose = function(event) {
    console.log('close', event.code, event.reason);
    ws = null;
  };
});

server.listen(8000);

Server-side connections transparently select between draft-75, draft-76 or hybi-07-compatible protocols and handle text, binary, ping/pong, close and fragmented messages. The client is hybi-08, but has been tested up to version 17.

Of course, nothing is ever perfect on the first release but faye-websocket does have one of the most complete protocol implementations around, and I’d like to see more implementations adopt the standard API. The standard interfaces might not be to your taste, but they benefit the ecosystem by giving everyone a predictable playing field. If you maintain a WebSocket project, please test it using the Autobahn test suite (here are Faye’s server and client results) and consider exposing the standard API to your users.