New Bamboo Web Development

Bamboo blog. Our thoughts on web technology.

Real time online activity monitor example with node.js and WebSocket

by Makoto Inoue

Update 23rd March 2010: Today we're announcing Pusher, a new realtime client push service. Sign up for the beta now

Here at New Bamboo, we specialise on Ruby On Rails web development. However, we started talking more and more about exciting things happening around HTML5 and javascript during morning standup (where we talk about all the cool things we are working/have discovered) , lunch time and our company hack day.

The latest hot topic is node.js. Some of us went to Full Frontal Javascript Conference and were very excited by the power and potential of node.js which Simon Willison (Django core team) introduced there. Simon describes node.js as “A toolkit for writing extremely high performance non-blocking event driven network servers in JavaScript”. I highly recommend you to read his blog post, “Node.js is genuinely exciting”

After reading Simon’s blog, watching Ryan Dahl’s talk video, and witnessing “A cambrian explosion of lightweight web frameworks based on top of Node” (again quotes from Simon) , I started thinking what would be an interesting app to write by myself.

Many people either created web framework, or chat apps, but I wanted to create something you wouldn’t normally do as web app, and this is what I came up with.

I’ve learned a lot while building this, and would like to share them step by step( or grab the code here if you are impatient to read my entire post.)

Step 1: Simple Text Streaming

In my original idea, I was thinking about making an simple app to tail errorlog and stream the output directly to web real time.

The code is something like this.

 1 var sys = require('sys')
 2   var filename = process.ARGV[2];
 3 
 4   if (!filename)
 5     return sys.puts("Usage: node watcher.js filename");
 6 
 7   // Look at http://nodejs.org/api.html#_child_processes for detail.
 8   var tail = process.createChildProcess("tail", ["-f", filename]);
 9   sys.puts("start tailing");
10 
11   tail.addListener("output", function (data) {
12     sys.puts(data);
13   });
14 
15   // From nodejs.org/jsconf.pdf slide 56
16   var http = require("http");
17   http.createServer(function(req,res){
18     res.sendHeader(200,{"Content-Type": "text/plain"});
19     tail.addListener("output", function (data) {
20       res.sendBody(data);
21     });  
22   }).listen(8000);
I will explain important bits.
1 var tail = process.createChildProcess("tail", ["-f", filename]);
Here, I created an child process to watch “tail” command. This will happen only once when you started node server.
1 tail.addListener("output", function (data) {
Then, I created an listener to send the output of tail command to http body as new message is written to errorlog. This will keep the connection open per http request.

Keep watching a process activity continuously is not an easy thing to do in normal web app, but it’s almost effortless thanks to node.js non-blocking architecture, and javascript’s event driven pattern.

Try running the script by specifying errorlog you want to watch like below

1 node tail.js development.log

Open http://localhost:8000. It will show you error messages as they are written to the log file.

Tail example

However, I wanted to do more, like drawing usage graph and stuff. It doesn’t make sense for me to do all drawing at server side and send the image as it comes, so now it’s time to write some code at client side.

Step 2: Ajax polling

Initially, I tried to do Ajax long polling as you normally do with online chat app, but it did not work as I expected. (You can check out my failed attempt here if you are curious)

I think there are 3 problems applying long polling to a scenario like my app .

  • Ajax success callback will fire when you receive all the response. It will not fire off if your response never finishes.
  • Even if you put timeout every 30 sec, the ajax success callback needs to wait for 30 sec, which is far from real time update.
  • If you shorten the timeout to 1 sec, you will receive constant result, but it's far from real time. In addition, continuous request will hammer your server, and it won’t guarantee that you get result from server every one sec. if there are network latency, you will lose the data during the latency.

Step 3: WebSocket

I need some solution to establish connection between client and server and do something as data arrives to client. Luckily, I had a chance to attend HTML5 communication workshop, and got introduced to one of HTML5 feature called “Web Sockets”

Here is the explanation of HTML5 Websocket from Kaazing, the company which provided the workshop.

The HTML 5 specification introduces the Web Socket interface, which defines a full-duplex communications channel that operates over a single socket and is exposed via a JavaScript interface in HTML 5 compliant browsers. The bi-directional capabilities of Comet and Ajax, unlike Web Sockets, are not native to the browser, and rely on maintaining two connections-one for upstream and one for downstream—in order to stream data to and from the browser. Note, that to support streaming over HTTP, Comet requires a long-lived connection, which is often severed by proxies and firewalls. In addition, few Comet solutions support streaming over HTTP, employing a less performant technique called “long-polling” instead.

Web Sockets account for network hazards such as proxies and firewalls, making streaming possible over any connection, and with the ability to support upstream and downstream communications over a single connection, Web Sockets place less burden on your servers, allowing existing machines to support more than twice the number of concurrent connections. Simple is Better

(What is an HTML5 WebSocket)

I also learnt during the workshop about drastic reduction of network traffic in use of WebSocket.

During making connection with WebSocket, client and server exchange data per frame which is 2 bytes each, compared to 8 kilo bytes of http header when you do continuous polling. Here is the comparison of 2 scenarios

Case 1: 10,000 clients polling every second: * Network throughput is (871 x 10,000) = 8,710,000 bytes = 69,680,000 bits per second (66 Mbps)

Case 2: 10,000 frames every second: * Network throughput is (2 x 10,000)/1 = 20,000 bytes = 160,000 bits per second (156 Kbps)

Apparently this turned on Google….

Reducing kilobytes of data to 2 bytes…and reducing latency from 150ms to 50ms is far more than marginal. In fact, these two factors alone are enough to make WebSocket seriously interesting to Google.

(From Ian Hickson (Google, HTML5 spec lead))

Within WebSocket supported browser (at this moment Chromium, and OSX version of Chrome only, but don’t go away yet. There is a solution for other browsers,which I will explain later), all you have to do is something like this (the example from Kaazing’s website)

1 var myWebSocket = new WebSocket("ws://www.websocket.org");
2 
3   myWebSocket.onopen = function(evt) { alert("Connection open ..."); };
4   myWebSocket.onmessage = function(evt) { alert( "Received Message:  "  +  evt.data); };
5   myWebSocket.onclose = function(evt) { alert("Connection closed."); };

Step 4: WebSocket meets node.js

I was curious if I can serve WebSocket from node.js and found that Alexander Teinum already did hard work for me

With this, all you have to do at server side is to define what server returns while the connection is established. Here is the code example from Alexander’s blog to echo whatever you sent from browser. You have to define it under “resources” directory.

1 exports.handleData = function(connection, data) {
2       connection.send('\u0000' + data + '\uffff');
3   }

And here are the snippets of what I did for my app.

Server side

 1 String.prototype.trim = function() {
 2     return this.replace(/^\s+|\s+$/g,"");
 3   }
 4 
 5   var sys = require('sys');
 6   var child_process = process.createChildProcess("iostat", ["-w 1"]);
 7 
 8   exports.handleData = function(connection, data) {
 9 
10     connection.addListener('eof', function(data) {
11      child_process.removeListener("output", output)
12     })
13 
14     var output = function (output_data) {
15       sys.puts(output_data);
16       var output_array = output_data.trim().split(/\s+/);
17       for (var i=0; i < output_array.length; i++) {
18         output_array[i] = parseFloat( output_array[i]);
19       };
20       output_hash = {
21         date:new Date(),
22         cpu:{
23           us:output_array[3],
24           sy:output_array[4],
25           id:output_array[5]
26       }
27       connection.send('\u0000' + JSON.stringify(output_hash) + '\uffff');
28     }
29     child_process.addListener("output", output);
30   }

At this snippet, I added child process to watch the result of iostat (I just changed my mind by that time to show iostat, rather than tail). Then, I added an listener inside "handleData" function to parse the result of iodata, construct the result set into hash, then send the response back in JSON format.

One thing worth explaining is this line.

1 connection.addListener('eof', function(data) {
2    child_process.removeListener("output", output)
3   })

I added a logic to close the listener when the client browser closes connection (by closing the tab, or call “connection.close”)

Without this logic, node.js will blow up every time users close their connection with following error message.

1 [websocket-server-node.js (master)]$ node server.js
2 ~/work/sample/websocket-server-node.js/resources/loop.js:5
3 connection.send(’\u0000′ + counter + ‘\uffff’);
4 ^
5 Error: Socket is not open for writing
6 at Timer. (/work/sample/websocket-server-node.js/resources/loop.js:5:15)

Client side

 1 webSocket = new WebSocket('ws://localhost:8000/iostat');
 2   webSocket.onopen = function() {
 3       out.html('Connection opened.<br>');
 4   };
 5 
 6   webSocket.onmessage = function(event) {
 7       stats = event.data;
 8       data = JSON.parse(stats);
 9 
10       // Adding the pursed result into array.
11       stats_array.unshift(data);
12       stats_array.pop();
13 
14       for (var i=0; i < stats_array.length; i++) {
15         if (stats_array[i]) {
16           var cpu_total = stats_array[i].cpu.sy + stats_array[i].cpu.us
17           $('#date_'  + i).html(stats_array[i].date);
18 
19         // More Logic here to add the data into table continues.
20         // ...
21         //
22 
23         };
24         var total_array = [];
25         for (var j=0; j < stats_array.length; j++) {
26           if (stats_array[j]) {
27             total_array[j] = [stats_array[j].cpu.us, stats_array[j].cpu.sy]
28           }else{
29             total_array[j] = [0,0]
30           };
31         };
32         // Draw charts.
33         drawCharts(total_array.reverse());
34       };
35 
36   };
37 
38   webSocket.onclose = function() {
39     out.html('Connection closed.<br>');
40   };

The above client side does not really have anything special. I added the incoming data into array and injected into result table and draw charts based on the data.

The entire source is here.

Step 5: What else can we do with WebSocket?

In this example, I showed how to stream the output of iostat in real time, but you should be able to stream other stuff , such as XMPP(Extensible Messaging and Presence Protocol, used for Google Talk and Google Wave), and STOMP(Streaming Text Orientated Messaging Protocol, which ActiveMQ uses as protocol). To do them on node.js, you need to implement a logic to handle these protocols on top of TCP. I haven't seen any libraries, but hopefully someone implement them... It’s actually possible to transfer binary data, such as video and audio, but it may not be practical, as Javascript at client side has to encode them.

Step 6: Is this WebSocket future thing?

Many features of HTML5 (Canvas, geo location, offline storage, etc) are already implemented in many of existing browsers, but WebSocket is still a cutting edge. You need to keep an eye on which browser it will start implementing the feature at somewhere like StackOverflow

Does this mean you can forget about WebSocket for another few years? If you are seriously considering WebSocket in enterprise environment (but not with node.js), you might want to consider Kaazing Open Gateway. They are doing pretty good job by automatically detecting Websocket support of your browser, and switch to alternative solutions(Silverlight, Flash Socket and so on). They also support XMPP and STOMP, so if you are interested in displaying real time stock feed, there is already an solution. In fact, their demo page has some interesting real time stock feed as well as resource monitoring, just like I did.

However, I am probably not enterprise enough for their license, and I am more interested in using it with node.js. I found some interesting solution by Hiroshi Ichikawa

  • web-socket-js - HTML5 Web Socket implementation powered by Flash

He basically wrote a wrapper to convert your HTML5 WebSocket into Flash Socket.

If you include his javascript library like below

1 <script type="text/javascript" src="./swfobject.js"></script>
2    <script type="text/javascript" src="./FABridge.js"></script>
3    <script type="text/javascript" src="./web_socket.js"></script>
4 
5    <script>
6        // This is for websocket-js
7        WebSocket.__swfLocation = "./WebSocketMain.swf";

And add flash policy file on your machine, or tweak websocket-server-node.js to return policy file (alternatively, if you serve both client side and server side code from same domain/port, you don’t need to worry about cross domain policy at all.), you should be able to use WebSocket on other browsers, as long as flash is supported.

 1 function doHandshake() {
 2       // some code to do handshake
 3       // ...
 4       // ...
 5 
 6       // Return flash policy file for web-socket-js
 7       // http://github.com/gimite/web-socket-js
 8       if(request[0].match(/policy-file-request/)){
 9         sys.puts('requesting flash policy file');
10 
11         policy_xml = 
12         '<?xml version="1.0"?>' +
13         '<!DOCTYPE cross-domain-policy SYSTEM ' +
14         'ww.macromedia.com/xml/dtds/cross-domain-policy.dtd">' +
15         '<cross-domain-policy>' +
16         "<allow-access-from domain='*' to-ports='*'/>" +
17         '</cross-domain-policy>'
18         connection.send(policy_xml);
19         connection.close();
20       }

Here is the proof.

The proof

There are some other things going on apart from what I covered above.

There are new things coming up almost every day. The best way to stay tuned to node.js/websocket is just keep watching twitter search feed

Summary

Here is the recap of what I learnt.

  • It is extremely easy to write streaming logic in node.js
  • However, you need a solution at client side to handle the incoming data real time.
  • Websocket enables you to have continuos communication in significantly less network overhead compared to existing solution.
  • Websocket is not available in most browsers yet, but there are workarounds.

I hope I was able to share my experiment well and looking forward to hearing about exciting apps/libraries/projects related to the both topics. Also, my understanding about them are less than perfect, so welcome any feedback / corrections / suggestions.

NOTE: There is more story about real time web here