Web proxy in node.js for high availability

Update (3/8/10): Updated code to work with version 0.1.30 of node.js

I’ve been thinking about high availability websites lately. In
particular, I want sites that can be upgraded (including database
migrations or even infrastructure changes) without downtime.

I’ve also been playing with node.js lately, and I
decided to spike out a web proxy that would sit between users and the
actual website (eg, a rails app). When performing upgrades, the proxy
would hold users connections and wait. Once the upgrade was done, the
proxy would forward requests as usual. Users would see an extra long
request, but as long as the upgrade was short (eg, less than a minute),
the user should not know the site was down.

This type of proxy server seems like a good fit with node. Node’s event
model means that there will be very little overhead when holding
connections. There are no threads stacking up and waiting. Since
everything is non-blocking, this server should scale well.

Here is a very simple version of the code:

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

http.createServer(function (req, res) {
  checkBalanceFile(req, res);
}).listen(8000);

function checkBalanceFile(req, res) {
  fs.stat("balance", function(err) {
    if (err) {
      setTimeout(function() {checkBalanceFile(req, res)}, 1000);
    } else {
      passThroughOriginalRequest(req, res);
    }
  });
}

function passThroughOriginalRequest(req, res) {
  var request = http.createClient(2000, "localhost").request("GET", req.url, {});
  request.addListener("response", function (response) {
    res.writeHeader(response.statusCode, response.headers);
    response.addListener("data", function (chunk) {
      res.write(chunk);
    });
    response.addListener("end", function () {
      res.close();
    });
  });
  request.close();
}

sys.puts('Server running at http://127.0.0.1:8000/');

Here is a gist if anyone would like to
fork.

Basically, I use http.createServer to create a web server on port 8000.
On incoming requests, I call checkBalanceFile. This method will try to
stat a local file called balance. If it finds it, it will call
passThroughOriginalRequest, which forwards the request to another web
server on port 2000. If the balance file does not exist, I use
setTimeout to call checkBalanceFile again in one second.

With a proxy server like this, the main application can be upgraded by
removing the balance file. While the file is missing, the node web
server will hold all of the connections and check every second for the
reappearance of the balance file. Once it comes back, all requests will
be forwarded along and then streamed back to the user.

Currently, this spike only works with GET
requests and does not pass any headers through, since I wanted to keep
the code simple.

Paul Gross

Paul Gross

I'm a lead software developer in Seattle working for Braintree Payments.

Read More