Node by example

Node by example: 12. Sample Project

12. Sample Project

The complete source code can be downloaded here: http://github.com/Hendrik/node-by-example

The following is a sample project which displays your website visitors in real time on google maps using node with the geoip and websocket server modules.

Basically what the script does is to watch the domains access log file for any changes, parse the changed line(s) to retrieve the accessing IP and then send it to the client via websocket.

In order for this project to work you need the following:
- google maps API key, available here: http://code.google.com/apis/maps/signup.html
- browser with websocket support, e.g.: google chrome: http://www.google.com/chrome

You can download the full source here: http://github.com/Hendrik/node-by-example

Let's start with the server code: See 12_example_project/server.js

First we need to include the websocket server & geoip modules, as well as setting up variables for the geoip database location (make sure to change it to have it match your path) and the ip_cache. Only 1 marker should appear on the map per IP, so we need to make sure to cache the already parsed IPs.

var sys = require('sys'),
    ws = require('./lib/ws');
    geoip = require('./lib/geoip'),
    dbpath = '/usr/local/share/GeoIP/GeoLiteCity.dat',
    ip_cache = [],
    filename = process.ARGV[2];

 

The "filename" is the full location of your selected website log file, e.g.: /usr/local/apache/domlogs/my-site.com

if (!filename)
    return sys.puts('Usage: node " + __filename + " filename');

 

Next we create a function that will parse the geoip data into json format and then send it to the client via the websocket:

function result_output(result, websocket) {
  var json_result = {};
  for (var key in result) json_result[key] = result[key];
  websocket.write(JSON.stringify(json_result));
}

 

We need another function to query the geoip database, which will provide the geoip data based on the provided IP:

function db_connect(ip, websocket) {
  var con = new geoip.Connection(dbpath, function(con) {
    con.query(ip, function(result) {
      result_output(result, websocket);
      con.close();
    });
  });
}

 

Next start the child process to "tail -f" the provided log file. The "-f" flag makes tail monitor the end of the file continously until the process stops:

var tail = require('child_process').spawn('tail', ['-f', filename]);
sys.debug('start tailing logfile');

 

Then create the websocket server and make it listen on port 8000:

ws.createServer(function(websocket) {
  ...
}).listen(8000);

 

Now the tricky part:
once a websocket client connects we want to start processing the "tail" child process output.

websocket.addListener('connect', function(response) {
  sys.debug('connect: ' + response);

  tail.stdout.addListener('data', function(data) {
    ip = data.toString().split(" ")[0];
    if (!ip_cache[ip]) {
      sys.debug(ip);
      ip_cache[ip] = true;
      db_connect(ip, websocket);
    }
  });

}).addListener('data', function(data) {
  sys.debug(data);
}).addListener('close', function() {
  sys.debug('close');
});

 

So, every time the 'data' event emits for the "tail" child process, it will parse the provided string and extract the IP.
If the IP doesn't exist yet in the ip_cache, then the IP will be sent to the db_connect() function to get its geoip data, which in turn sends it to the result_output() function to send the geoip data in json format to the websocket client.

The full source code for the client side file is located in the provided index.html file. In order for it to work you need to replace the following:
GOOGLE_MAPS_API_KEY needs to be replaced with your google maps API key
YOUR_SERVER_IP_HERE needs to be replaced with your server IP

The juicy bit is the following, which creates the websocket in the browser and every time it receives data it will extract the latitude and longitude data to place a new marker on the google map at the location of the visitor, based on his/her IP:

ws = new WebSocket("ws://YOUR_SERVER_IP_HERE:8000/");

// when data is received from the websocket server, extract lat & long to place a new marker
// at the correct location

ws.onmessage = function(evt) {
  var data = eval("(" + evt.data + ")");

  map.addOverlay(new GMarker(new GLatLng(data.latitude, data.longitude)));

};

 

To test this example project start the node websocket server, e.g.:

node server.js /usr/local/apache/domlogs/my-website.com

where "/usr/local/apache/domlogs/my-website.com" needs to be replaced with your actual website log file.

 

Open the index.html file in a websocket compatible browser, e.g.: google chrome and then visit your site to trigger another entry in your log file.
This in turn should result in your IP being displayed in the console and the browser window will show a new location marker on the map based on your geoip location.