Websockets with Javascript
Visualising postcode lookups using Websockets and Node.js - an alternative to Ajax and Comet
At the time of writing, to get this working you'll need Google Chrome 4.0.2+ or one of the nightly builds of Firefox or Opera (we're using Chrome). **
Last month Google Chrome announced it was supporting websockets into the latest build.
Web sockets are a next-generation bidirectional communication technology for web applications with the most exciting feature being the long awaited ability of being able to 'push' events/data to the browser from some other process/system running somewhere else.
If you are familiar with Ajax you will probably have been frustrated at some point with the over complicated requirement of hitting the server with Ajax requests in the background to ask it if something has happened, or to ask if it has the new data you have been waiting for. After a while and with loads of stuff going on, this becomes messy and difficult to manage. A real stumbling block when dealing with large amounts of real time data.
Huzzar ! Websockets
Websockets allow us to listen to an address somewhere and either send data to it, or wait for something to be sent. The interface in the browser is through Javascript and is easy to setup and use. To illustrate we've made a simple visualisation tool using Google Maps and Node.js. There is no live demo but with a *nix system with a local webserver running on localhost you'll have no problems. Unpack the files and checkout the ENGAGE.README.txt
A little bit on Node.js
It is well out of the scope of this post to cover Node.js fully but it describes its goal as 'to provide an easy way to build scalable network programs'. In our example we use it to run a child process and monitor a log file using the unix tail program and output the data to http://127.0.0.1:8010, but you could use anything in its place that can do these two things. You could probably even use php and websockets although I haven't tried it. There is an excellent post by Simon Wilson on why node.js is genuinely exciting that really shows its potential.
In our quick example we've included a Node.js webserver that will be perfect for what we're trying to do here and used the API provided to plug in an extra piece of functionality.
Lets look at the code
Our example is made up of three sections
- The server. In this case a Node.js process.
- The geocoding.
- The client. This is a html page that listens out for data being sent to it. Google maps is also used here to visualise the geocoded data.
.01 Install and start node.js
As we stated above this isnt a tutorial on Node.js and we wont be discussing it here. Grab Guille's node server and add our tail.js file to the modules folder. This lets us point the websocket in the browser to http://127.0.0.1/tail
Show tail.js codevar sys = require('sys');
/* Change this to point to your log file */
var filename = '/path/to/your/log.txt';
/* Create a child process of tail -f */
var child_process = process.createChildProcess("tail", ["-f", filename]);
/* Send message when this module is called */
var Module = this.Module = function(){
sys.puts( 'tail started' );
};
/* Send results of tail -f */
Module.prototype.onData = function(data, connection){
child_process.addListener("output", function (data) {
connection.send( data );
});
};
/* Do something on disconnect of socket */
Module.prototype.onDisconnect = function(connection){
};
Once node is installed you can start it like this.
Show code$node runserver.js --port='8010'
.02 Some Geocoding
The best free geocoding service we've currently used is the one provided by Google. You've got two choices here. Javascript or http. For simplicity we've opted for http here. Using our simple geocode class we send a request to google for our search term.
What we want is a longitude and latitude for the postcode / town that we are geocoding. Once we have that back from Google we can write it to our log file.
Show PHP$oGc = new GoogleGeoCode();
$aDecoded = json_decode( $oGc->getLatLng( $_GET['searchterm'] . ", UK" ), true );
$aResults['lat'] = $aDecoded['Placemark'][0]['Point']['coordinates'][1];
$aResults['lng'] = $aDecoded['Placemark'][0]['Point']['coordinates'][0];
/* This gives us our results in the array $aResults.
The next step is to save them to our log file. */
$fp = fopen( "log.txt", "a" );
if (flock( $fp, LOCK_EX )) { // do an exclusive lock
fwrite( $fp, "\n" . $aResults['lat'] . "," . $aResults['lng'] );
flock( $fp, LOCK_UN ); // release the lock
} else {
echo "Couldn't get the lock!";
}
fclose($fp);
.03The client
This is really the bit we are most interested in and contains the websockets implementation and it is surprisingly simple.
Show Javascript/* Open the connection */
var webSocket = new WebSocket('ws://127.0.0.1:8010/tail');
/* When the connection is open send the server some data, in this case the string 'start' */
webSocket.onopen = function(event){
document.getElementById('status').innerHTML = 'waiting for socket';
webSocket.send('start');
};
/*
When we hear back from the server this block is executed
- We parse the result into an array, then grab the last element
which represents the last line of the log file.
- We then create a new google maps marker and place it on our map
zooming and centering
*/
webSocket.onmessage = function( oEvent ){
var aCurrent = [];
var sLastLines = array_pop( oEvent.data.split( '\n' ) );
var aParts = sLastLines.split( ',' );
document.getElementById('status').innerHTML += '
' + sLastLines;
var latlng = new google.maps.LatLng( aParts[0], aParts[1] );
var marker = new google.maps.Marker({
position: latlng,
map: map
});
map.setZoom( 7 );
map.setCenter(latlng);
};
/* If we loose the socket connection run this function */
webSocket.onclose = function(event){
document.getElementById('status').innerHTML = 'socket closed';
};
So there we have it. We think this is quite exciting and opens the way for all kinds of fun stuff where you can push things to the browser. Why not head over to the blog and let us know your ideas for this technology ?
**This is an experiment and is provided 'as is'. We don't offer any support and the provided example is intended for illustration purposes only.


