Server Side Javascript Continued – Node.js (plus example)

1 Jul

Update: Node’s APIs have change quite a bit since this post was made. Check out the latest stuff at nodejs.org!

In my previous post on server-side javascript (SSJ) I took a quick look at Jack, a project that aims to implement the Rack/WSGI pattern for javascript. I still think this approach is great as it opens the door for more traditional Rails/Django-esque frameworks for SSJ.

But, lets face it, the next gen web is all about real-time interactivity, and current popular environments and servers just aren’t ideal for that. It’s not their fault, up until recently we only cared about getting that request handled and out the door as quickly as possible with nothing shared between requests. Unfortunately, it’s no longer just about number of requests/sec — we now need high concurrency, long-lasting connections, and shared persistence over these connections.

Node.js

Enter node.js – a high performance javascript project built ontop of Google’s V8 runtime. From the author’s description:

Node’s goal is to provide an easy way to build scalable network programs. … Each connection is only a small heap allocation. This is in contrast to today’s more common model where OS threads are employed for concurrency.

Nice, but does this pan out in implementation? After spending a few days with Node, I truly believe that this will be the go-to project for the future of the real-time web.

A Simple Game Lobby

Let’s take a look at a simple example I put together. The following is a very basic game lobby that is based on a more complex project I’m working on with node. (You can checkout this script from github as well).

The script accepts new players through a url like /join?player=joebob. Then, the client can long-poll the URL /wait and receive a notification in real-time when new players join!

First, lets define a couple of Arrays that will hold our persistence in-memory.

// our in-memory list of player
var players = [];

// our in-memory list of players waiting
var waiting = [];

Next, lets define a set of URLs our server will respond to. Notice that the /wait response does not take place immediately. Instead, the response is captured in a callback function that is held in-memory until it is called. These callbacks are called whenever a new player hits the /join URL.

// define a set of paths that respond to requests
var paths = {

  /**
   * Requests to /join add players to our
   * player list, and fire off a notification
   * to all our waiting players
   **/
  "/join": function(req, res) {

    // extract the player from the request
    var newPlayer = req.uri.params.player;

    // respond to this request with a list of players
    // already in the lobby
    server.respond(200, players);

    // add this player to list of players
    players.push(newPlayer);

    // notify all of our waiting players that
    // a new player has joined
    while(waiting.length > 0) {
      waiting.shift().callback.apply(this, [newPlayer]);
    }
  },

  /**
   * Requests to /wait holds the connection
   * open until another player joins
   **/
  "/wait": function(req, res) {

    // define our waiting player and the notification
    // callback to trigger when another player joins
    var waitingPlayer = {
      "player": req.uri.params.player,
      "callback": function(newPlayer) {
         server.respond(200, newPlayer);
       }
    };

    waiting.push(waitingPlayer);
  }
};

Finally, we define our server. We tell the server to map requests to the paths we defined above, and to listen on port 8000.

// Define a new HTTP Server
var server = node.http.createServer(function (req, res) {

  // tell our server to look at the paths definition above
  // for a responder to the request
  paths[req.uri.path].apply(this, [req, res]);

  // respond to a request
  function respond(status, obj) {
    var body = JSON.stringify(obj);
    res.sendHeader(status, [ ["Content-Type", "text/json"]
                         , ["Content-Length", body.length]
                         ]);
    res.sendBody(body);
    res.finish();
  }
});
server.listen(8000);
puts("The game lobby has started!");

To run the script, first download and build node, and then download this script from my repo. Execute the script with:

> node gamelobby.js

  • Ron
    Here is a nice screencast: http://www.videorolls.com/watch/node-js-screencast . I believe it may be of some help.
  • gracybonsu
    Thanks for giving me this useful information
    http://www.shakespearefinance.co.uk/
  • john
    Hi! This example does not seem to be working with 2009.06.30 node-0.1.0.tar.gz

    When I call /join?player=john then

    The game lobby has started!
    example.js:28: TypeError: Object [object Object] has no method 'respond'
    server.respond(200, players);
    ^


    But THANKS for putting this blog post out there. Really exciting!
    Best,
    John
  • Hey John - from your console output, it looks like you are making requests to the wrong script.

    "example.js:28 ..."

    The name of the script, if you used the same copy from the github repo, should be 'gamelobby.js'

    Let me know if that helps!
  • john
    Sweet! That works :)

    Thanks britg, very fun stuff! I'm planning to build a small comet server using node.js

    Best,
    John
  • check out http://github.com/visionmedia/express/tree/master
    might like ;), I just started it but contribution would be great, building a framework on node.js is sexxxxy
  • Very cool, will watch its progress on github :) I like the Sinatra-esque lightweight approach.
  • Nice article!

    But a very bad idea to show HTTP example code completely ignoring request verbs. People will copy that :-/ And don't make people join on GET, use PUT to join, DELETE to unjoin.
  • True, I plan to make some updates, the first of which is to use POST on the /join URL and enforce it. Also, I will implement a reaper process to clear stale joins.
blog comments powered by Disqus