Jeff Kreeftmeijer

Things I learned from my Node.js experiment

2010-08-03

Last week, I published an article about my very first experiment with Node.js. Being completely new to the whole thing, I decided to create a simple demo with web sockets.

As you can read in last week’s article, I created a page that checks your mouse cursor’s location and sends it to the web socket. The web socket broadcasts your mouse location to every other user on the page, so everyone will see your cursor moving around. That means you will see everybody else’s cursor moving around on your screen as well.






After letting my experiment run for a week, I learned a lot and improved the code. In this article I’ll go over some things I learned and how I fixed some issues.

Oh, and I’ve updated the Gist, in case you want to use the code.

People love fancy tricks with web sockets

After posting the article, it jumped to the Hacker News front page within minutes and stayed there for over 26 hours, got retweeted like crazy and got featured on episode #17 of the Dev show.

A lot of people commented or sent me replies via twitter and it was fun to see how cursors chased each other around, felt the urge to hump or bang other cursors, draw shapes, or make sure nobody could read the text by constantly hovering over it.

Thanks for the enthusiasm everybody, it was a lot of fun.

Node.js handled everything perfectly

Since this was my first experiment with Node.js, I expected cursors to start moving slow and the server to eventually crash. Especially because of this unexpected stream of visitors.

I added a rate limit of forty milliseconds — mousemove() gets fired a lot when you move your mouse — to take some load off the server and I got God to monitor it all and restart the server if it crashed.

However, except for some mistakes I made which caused the server to crash when somebody injected some javascript, Node.js handled everything perfectly. On a 256 MB VPS. Amazing.

You can fall back to flash

I posted a big notice above my article telling everyone to use Safari or Chrome, since they have support for web sockets. I didn’t know you could fall back to flash at the time.

Right now, the example runs on @rauchg‘s Socket.IO instead of web-socket-server, which automatically switches to flash when your browser doesn’t support web sockets.

When you do this for yourself, please start the server with sudo. I couldn’t get it to work properly and eventually got help from Guillermo himself in the #node.js IRC channel.

The demo is still online, if you want to take it for a spin in your web socket-less browser.

Injecting data into web sockets is easy

Since I needed to send cursor locations on mousemove(), I had to create a connection to the web socket using Javascipt. This made it quite easy to send anything you want by injecting some Javascript into the page and calling conn.send. The web socket server would simply broadcast it to the other clients.

I was amazed to see @einaros took the time to inject multiple cursors into the page and rotate them in 3D space. He even made a video.

How would we prevent users from doing something like this? I haven’t come up with a solution yet, since I think it’s impossible to find out if the messages get sent on mousemove() or if they’re simply called using a javascript console. I’d love to hear your thoughts on this.

I should have sanitized the messages

Although I ignored evil messages on the client side — I checked if the “action” was “move” or “close” — the Node.js server would still broadcast any JSON message it received. That meant injecting something like this would result in it being sent to every other client:

  conn.send({'injected!': 'haha!'});

Again, nothing would happen if a client would receive this message because it wouldn’t contain anything it would understand. Still, not very nice to just broadcast this to everyone.

Right now, the message listener catches syntax errors when trying to parse the message and checks if the action is “close” or “move”:

server.addListener("connection", function(conn){
  conn.addListener("message", function(message){
    
    try {
      request = JSON.parse(message);
    } catch (SyntaxError) {
      log('Invalid JSON:');
      log(message);
      return false;
    }
    
    if(request.action != 'close' && request.action != 'move') {
      log('Ivalid request:');
      log(message);
      return false;
    }
    
    request.id = conn.id
    conn.broadcast(JSON.stringify(request));    
  });
});

More?

If you have any more feedback on the code sample, be sure to let me know. The gist is updated and be sure to check out the new and improved version of the experiment.