Inaugural: Twitter streaming with node.js and HTML5 WebSockets

The idea of having a full javascript stack with code reuse and the removal of language impedance between client and server has always interested me.

It seemed that a full javascript stack was an utopia, waiting in the distant future, but not anymore. Now there are a few powerful javascript solutions for the server and with CouchDB I can aim for a full javascript stack.

One of those solutions is node.js. It’s an high performance non-blocking event driven framework. It is brilliantly designed, it feels right, and the performance is impressive to say the least. I’ve not been so excited with a new technology since Rails appeared on my radar a few years ago.

If you’re unfamiliar with it, go watch Ryan Dahl presentation at JSConf.

WebSockets and Twitter Real Time Streaming

Yesterday I stumbled upon Ruben Fonseca excellent blog post on a Ruby Twitter Streaming example using AMQP, EventMachine and HTML5 WebSockets (basically it streams real time tweets to the browser without the use of polling).
Ruben’s experiment was inspired by Ilya Grigorik post on WebSockets and Ruby.

I decided to follow on Ruben’s post and do the exact same thing with node.js, only IMO much simpler. Ruben’s architecture was based on the following:

Twitter Stream → Filter → RabbitMQ → AMQP → Eventmachine → WebSocket → Browser

The node.js version:

Twitter Stream → node.js → WebSocket → HTML5 Browser

Let’s take a look at the code:

The Server

var sys    = require('sys'),
    http   = require('http'),
    ws     = require("./vendor/ws"),
    base64 = require('./vendor/base64'),
    arrays = require('./vendor/arrays');

// Command line args
var USERNAME = process.ARGV[2];
var PASSWORD = process.ARGV[3];
var KEYWORD  = process.ARGV[4] || "iphone";

if (!USERNAME || !PASSWORD)
  return sys.puts("Usage: node server.js   ");

// Authentication Headers for Twitter
var headers = [];
var auth = base64.encode(USERNAME + ':' + PASSWORD);
headers['Authorization'] = "Basic " + auth;
headers['Host'] = "stream.twitter.com";

var clients = [];

// Connection to Twitter's streaming API
var twitter = http.createClient(80, "stream.twitter.com");
var request = twitter.request("GET", "/1/statuses/filter.json?track=" + KEYWORD, headers);
request.finish(function (response) {
  response.setBodyEncoding("utf8");
  response.addListener("body", function (chunk) {
    // Send response to all connected clients
    clients.each(function(c) {
      c.send(chunk);
    });
  });
});

// Websocket TCP server
ws.createServer(function (websocket) {
  clients.push(websocket);

  websocket.addListener("connect", function (resource) {
    // emitted after handshake
    sys.debug("connect: " + resource);
  }).addListener("close", function () {
    // emitted when server or client closes connection
    clients.remove(websocket);
    sys.debug("close");
  });
}).listen(8080);

Notice how everything looks familiar, and at first glance it seems we’re writing typical client side javascript.
I find it to be very elegant and simple solution, and with a lot less dependencies. The javascript style callbacks and closures are well suited for an evented non-blocking framework like node.js.

I’m using Jacek Becela very cool minimal web sockets library that creates the websocket server with a similar interface to the native tcp.createServer.

The Client

$(document).ready(function(){
  if(!("WebSocket" in window)) {
    alert("Sorry, the build of your browser does not support WebSockets");
    return;
  }

  ws = new WebSocket("ws://localhost:8080/");
  ws.onmessage = function(evt) {
    data = eval("(" + evt.data + ")");
    var p = $("");
    if($('#tweets div.tweet').size() > 15) {
      $('#tweets div.tweet:last').slideDown(100, function() {
        $(this).remove();
	  });
    }
    $('#tweets').prepend(p);
    p.slideDown(140);
  }
});

Here for comparison sake (and because I’m lazy) I copied Ruben’s client side script.

You can check out the project and installation instructions on the github repo.

How to run it yourself

  • Download and install node.js (instructions here)
  • Download the project from github.
  • Start the server: node server.js <twitter_username> <twitter_password>
  • Open index.html with a WebSocket compatible browser (Chrome or Webkit nightly)