Content with Style

Web Technique

Long polling example with node.js

by Pascal Opitz on May 7 2010, 17:15

Here's a little long polling example that I have thrown together while playing around with node.js. I must say it's a pretty slick tool. Anyone familiar with JavaScript suddenly can create powerful server side applications.

The Server

First we need to create a file called test.js!


var events = require('events');
var sys = require('sys');
var http = require('http');
var fs = require('fs');

var TestApp = function() {
  var _self = this;
  var counter = 0;
  
  var routes = {
    '/' : function(request, response) {
      response.writeHead(200, {'Content-Type': 'text/html'});
      var string = _self.indexTemplate;
      response.write(string);
      response.end();
    },

    '/jsupdate' : function(request, response) {
      response.writeHead(200, {'Content-Type': 'text/javascript'});
      updateLoop.call(this, request, response, counter);
    },

    '/jsstatus' : function(request, response) {
      response.writeHead(200, {'Content-Type': 'text/javascript'});
      response.write('counter: ' + counter + '\n');
      response.end();
    },

    '/increment' : function(request, response) {
      response.writeHead(200, {'Content-Type': 'text/plain'});
      counter++;
      response.write('counter: ' + counter + '\n');
      response.end();
    },

    '/reset' : function(request, response) {
      response.writeHead(200, {'Content-Type': 'text/plain'});
      counter = 0;
      response.write('reset\n');
      response.end();
    }
  }

  var updateLoop = function(request, response, current_counter) {
    if(current_counter != counter) {
      response.write('counter: ' + counter + '\n');
      response.end();
      return false;
    }
    
    setTimeout(function() {
      updateLoop.call(this, request, response, current_counter);
    }, 1000);
  };

  var _requestHandler = function(request, response) {
    sys.puts('request: \'' + request.url + '\'');
    
    if(routes[request.url] === undefined) {
      response.writeHead(404, {'Content-Type': 'text/plain'});
      response.write('not found\n');
      response.end();
    } else {
      routes[request.url].call(this, request, response);
    }
  };
  
  var _updateHandler = function(request, socket, head) {
    sys.puts('update');
  };

  var _closeHandler = function() {
    sys.puts('close');
  };
  
  sys.puts('New TestApp');
  
  fs.readFile('./index.html', function (err, data) {
    if (err) throw err;
    _self.indexTemplate = data;
    sys.puts('Template loaded');
  });
  
  var _server = http.createServer().
          addListener('request', _requestHandler)
          .addListener('close', _closeHandler)
          .addListener('update', _updateHandler)
          .listen(8000);
  sys.puts('Listening to port 8000');
};

new TestApp();

The Client

The client side is just a small index.html file that gets loaded by the server application to be served up. Note that you'll have to restart the server in order to make changes to it.


<html>
  <head>
    <title>Long polling test</title>
  </head>
  
  <body>
    <p>Long polling test.</p>
    <p><textarea id="output"></textarea></p>
  </body>
  <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
  <script type="text/javascript">
    var client = new function() {
      var _poll = function() {
        $.get('/jsupdate', function(response) {
          $('textarea').text(response);
          _poll();
        });
      }
      
      $.get('/jsstatus', function(response) {
        $('textarea').text(response);
        _poll();
      });
    }
  </script>
</html>

Testing the application

Now all you need to do is run node test.js and you should be able to open one or more browser windows with the test page.

To check the other actions I used curl:


$ curl http://localhost:8000/increment
counter: 1
$ curl http://localhost:8000/increment
counter: 2
$ curl http://localhost:8000/increment
counter: 3
$ curl http://localhost:8000/reset
reset

On you test page updates should come through in the textfield.

Where does this leave us?

Node.js seems to gain momentum big time at the moment. There's tons of client libraries already, and frameworks sitting on top of it seem to pop up like mushrooms.

Especially PHP coders will find themselves frustrated when it comes to long polling, as PHP usually uses Apache or other blocking servers, or one is forced to create one process per request, which becomes hard to manage.

After seeing a proxy example in node.js, I believe node.js could be a simple solution: Run Apache on port 8080 or something and use node.js to either serve long-polling stuff or proxy through to your Apache run application.

But even long polling aside, the event based programming that's coming with node.js would be attractive for the creation of daemons and other little system tools. How stable node.js performs under high load is a different story, and I haven't done enough research yet as to make a comment on that. I can imagine that Supervisord would be a nice helper to manage node.js itself, in case it crashes or your applications develop memory leaks.

If you, Dear Reader, have some experiences in the production environment, please go ahead and share in the comments.

Update 13/05/2010

I have put the example files on github.

Comments

  • Casey, do you mean the counter incrementing? Potentially yes, it would, but it is just an example and not meant to ever run that long.

    If you mean the client side _poll() function, keep in mind that the next function call will only happen on callback, read: when the socket closes. This is only when the counter gets updated.

    by Pascal Opitz on January 26 2011, 12:30 #

  • I\'m not too familiar with recursion in JavaScript, but won\'t the code in the client example eventually throw a stack overflow exception?

    by Casey Watson on January 24 2011, 02:36 #