Node.js – the programmers “Save the World” tutorial

What this tutorial is

This tutorial was designed to help you understand how to create your own node.js program – not how to copy and paste someone else’s code! (However… you might have some luck finding the complete code at https://bitbucket.org/akademy/save-the-world/src).

It is assumed you are a programmer, with some experience of javascript and other programming languages such as Python, Ruby or Java. It’s really written for other programmers and tries to get to the points quickly (except for the odd alien invasion)!

It may be useful to imagine: Earth has only minutes to live and you are our only hope – your teacher is barking orders at you in a manic attempt to teach you the skills you’ll need to defeat the invading armada. You need to set up a node.js server before the laser bolts start burning! Quick!

Fundamentals you need to know

The first thing to understand is that node.js was built from day one to act both like a server (e.g. Apache, nginx, etc.) and a programming language (like Python, PHP etc). It is asynchronous, and event driven; just like javascript is on the web – that’s because it is javascript… For instance, you should open a file like this, it is none blocking and event driven (the function passed in is fired once the data has been read from the file):

fs.readFile( 'myfile.txt', function (err, fileData) {
    if( !err ) {
        console.log(fileData);
    }
});

First things first

You’ll need to install it. Go to node.js and download and run the version you need for your system. Once you can use “node” and “npm” on your command-line we are good to start whacking away at the keyboard.

Second things second

“Hello World” :- if you must output this you can do it with console.log (yes the same thing you use in the browser), but “Hi Gaia” has less characters… Put it in a file and run it with the command “node <filename>”.

You can put console.log(“a string”) anywhere in your code and it’ll output to STDOUT by default. You know you can’t resist trying it… even with the impending doom.

Real stuff

Let’s make a simple (non-alien killing) server.

node.js comes with a core library of modules (this link will show you the list, but there’s no time for that). We’ll be using the http module. In node.js you use the “require” command to pull in the modules you need, you set the module to a local variable and use it like a regular object. Call the variable whatever you want, but for simplicity we’ll call it “http”:

var http = require("http");

This particular module has a “createServer” function – yes it creates servers. We pass a function into it, it gets called when the server receives a request. The function has two parameters, the first is info about the request, and the second a handle to our response. Call them what you want (maybe alien_attack and human_defence) but “request” and “response” will do for us.

The createServer function returns our server, it has a function called listen, that takes the base-ten port number the server will listen on. Aliens talk on port 0xDEAF (that’s 57007).

http.createServer( function( request, response ) {
    console.log( "We have contact!" );
}).listen(57007);

Don’t bother running it, time is of the essence – and we haven’t created a response to our alien overlords yet…

The response handler has several useful functions, “writeHead” writes head values, “write” writes a response, “end” closes the response (it gets sent then), voilà:

response.writeHead(200, {"Content-Type": "text/virus"}); /* normally "text/plain" ! */
response.write("Upload...");
response.end("...virus!");

But before we try anything like that, lets have a look at what the request and response objects look like. A simple way to do that is with another module called “util“. It has a particular function called “inspect” and it’ll pretty-print any object for us. It’s another module so we’ll need to require it before using it… we’ll call it “util” (but “defence_tools” would have been more fun…). Pass in the variable you want to inspect as the first parameter, there are other options (see util.inspect ) but a useful one is depth with indicates how far down a hierarchy you’d like to display. So to output our request to the command-line we’ll need to add:

var util = require('util');
console.log( util.inspect( request , {depth:1}) );

Or we could send it to the browser with the write function of response.

response.write( util.inspect( request, {depth:1}) );

Our full alien killing server now looks like this:

var alien_killer = require("http"),
    defence_tools = require('util');

alien_killer.createServer( function( alien_attack, human_defence ) {

	console.log( defence_tools.inspect( alien_attack, {depth:1}) );
	console.log( defence_tools.inspect( human_defence, {depth:1}) );

	human_defence.writeHead(200, {"Content-Type": "text/virus"});
	human_defence.write( "Upload virus!" );
	human_defence.end();

}).listen(57007);

Run the file under Node and you should see something at http://localhost:57007 .

Real World Stuff

But that’s of no use – all requests reproduce the same response.

So let’s use the request to figure out some correct responses. Primarily we need to use the url member of the request object. We can utilise another module named “url” (you should call it defense_codes), this separates the url into its inherent parts (schema, host, port, path, query, etc) making it a breeze to match the correct response.

In our response handler we add some if statements, each returns a different response depending on the url path :

var alien_killer  = require( "http" ),
    defence_codes = require( "url" );

alien_killer.createServer( function( alien_attack, human_defence ) {

	var codes = defence_codes.parse( alien_attack.url );

	if( codes.pathname === "/" ) {
		response = "Waiting...";
	}
	else if( codes.pathname === "/lasers" ) {
		response = "Raise Worldwide Shields!";
	}
	else if( codes.pathname === "/surrender" ) {
		response = "Never give up! Never surrender!";
	}
	else {
		response = "No viable response. Fire wildly into the skies!";
	}

	human_defence.writeHead(200, {"Content-Type": "text/plain"});
	human_defence.write(response);
	human_defence.end();

} ).listen(57007);

Now you can try the server again. The top most level (http://localhost:57007) shows we are waiting; if the aliens send “lasers” (http://localhost:57007/lasers) we take appropriate action; if they demand surrender (http://localhost:57007/surrender) we tell them our intentions; for all other requests we try our luck.

Current World Wide Defence documents state there are 24 categories and 173 sub categories our server will need to handle. We need a better way to organise these  responses so let’s create some modules of our own. I’m not going to write all the responses – we haven’t time! – but maybe we can create a few essentials before they come knocking booming at our door.

Any js file you create can become a module, you just need to specify which parts will be exported to other files with the declaration: exports.<the-external-name>= <thing-to-export>. Lets put our response control into another file and make it “require’able” with export. Our external file looks like this:

var defence_codes = require( "url" );

function earth_defence_router( alien_attack ) {
	var codes = defence_codes.parse( alien_attack.url ), response;

	if( codes.pathname === "/" ) {
		response = "Waiting...";
	}
	else if( codes.pathname === "/lasers" ) {
		response = "Raise Worldwide Shields!";
	}
	else if( codes.pathname === "/surrender" ) {
		response = "Never give up! Never surrender!";
	}
	else {
		response = "No viable response. Fire wildly into the skies!";
	}
	return response;
}
exports.router = earth_defence_router;

To use this we utilise require with a simple path that says get it from the current folder, we use the name of the file without the extension, we’ve called it router.js so:

var defence_handler = require( "./router" );

Now we can call our router function with defence_handler.router( alien_attack ), our new file becomes:

var alien_killer  = require( "http" );
var defence_handler = require( "./router" );
alien_killer.createServer( function( alien_attack, human_defence ) {
    response = defence_handler.router( alien_attack, human_defence );
    human_defence.writeHead( 200, {"Content-Type": "text/plain"} );
    human_defence.write( response );
    human_defence.end();
} ).listen(57007);

We could improve this further by dividing up the routers into a hierarchical group of files based on the sub-folder level in the URL. You may have noticed that the require statement is nothing but an (albeit special) function that takes a string. We can use it anywhere we like, and can construct the strings in code.

If we take our url-paths and give them each a handler name:

var handlers = {
   "/"             : "handler_wait",
   "/lasers"         : "handler_laser",
   "/surrender"     : "handler_surrender",
 };

we can construct the filename we will pass into require from the subpath which was requested (we’ll work out the subpath in code). It’d be nice to keep these handlers in their own folder, so we’ll adjust the require string to include that and move all the files into a sub folder:

var handler = require("./handlers/" + handlers[subpath]);

Now if each separate handler uses the same export name within it, we can always safely call it in code. Our top level router looks like this:

var defence_codes = require( "url" );

var handlers = {
    "/"             : "handler_wait",
    "/lasers"         : "handler_laser",
    "/surrender"     : "handler_surrender",
};

function earth_defence_router( alien_attack, human_defence ) {

    var codes = defence_codes.parse( alien_attack.url );

    var subpaths = codes.pathname.split("/");
    var subpath = ( subpaths.length > 1 ) ? "/" + subpaths[1] : "/";

    if( subpath in handlers ) { 
        // if it's in our safe list, use it.
        var handler = require( "./handlers/" + handlers[subpath] );
        return handler.handle( alien_attack, human_defence );
    }

    return "No viable response. Fire wildly into the skies!"; // default response
}

exports.router = earth_defence_router;

Not each single router could further chose responses based on the url. For instance the handler_laser could look something like this:

var defence_codes = require( "url" );

var responses = {
    "/"                        : "Lasers? They're friendly right?",
    "/low"                     : "Working on our tan.",
    "/high"                    : "Raise Worldwide Shields!",
    "/earth-shattering"        : "Kaboom.",
};

function handler_laser( alien_attack, human_defence ) {

    var codes = defence_codes.parse( alien_attack.url );

    var subpaths = codes.pathname.split("/");
    var subsubpath = ( subpaths.length > 2 ) ? "/" + subpaths[2] : "/";

    if( subsubpath in responses ) {
        return responses[subsubpath];
    }

    return "Perhaps if we had some kind of really big mirror?";
}

exports.handle = handler_laser;

This simply works out the second level path name and returns an appropriate response. Try it with different urls, e.g http://localhost:57007/lasers/high , http://localhost:57007/lasers/ , http://localhost:57007/lasers/earth-shattering , http://localhost:57007/lasers/sword .

Real Alien World Stuff

But what we really need is to trick the alien fools into allowing us to access their systems. Then by bouncing a message through their defence shields we should be able to figure out the frequency their shields use – via the time delay – and send some almighty exploding thringy into their ship: First we’ll set up a web page which the aliens are sure to go to, then we’ll open up a socket and send back the information we need. Finally we’ll deliver our “farewell gift”.

We’ll need quite a lot of code to get the functionality we require so we’ll shortcut and install an external module called Sockets.io – this particular module makes two-way direct communication between earth and alien browser easy. We will utilize the Node Package Manager (NPM) to install the module – NPM is likely installed already (it comes with Node.js). Now on your command-line type:

npm install sockets.io

That downloads the sockets.io source into your working folder in a subfolder called “node_modules”. To use it in our code we first need to require it:

var socketio = require('socket.io');

Then we tell sockets.io which http server to listen on :

var server = http.createServer(...);
var socket = socketio.listen( server );

Now socket.io can piggy-back our server and override and handle the parts it needs. In particular socket.io will deliver it’s own code to the alien browser, the very same code it will also use on our server – code reuse on both client and server is easy with Node.js and we’ll be using the same great short-cut to disguise our communication.

We can attract the aliens to our website – like most weak minded souls – with pretty pictures of cats (these particular aliens find them quite juicy). So we need a webpage with a cat on, our basic webpage looks like this:

<html>
  <body>
    <h1>Eat me please</h1>
    <img src="pwitty-cat.jpg">
    <script>/* ... */</script>
  </body>
</html>

We also have to include the sockets.io code and we just include this script in the normal way:

<script src="/socket.io/socket.io.js"></script>

Notice the path we use here, as socket.io “wrapped” our server, it can intercept this call and serve up the code on it’s own. To send a message from a website we would connect then emit a message:

var socket = io.connect('http://localhost:57001');
socket.emit( "my-message-event", "Hello, Earth!" );

But that wouldn’t do anything on our server yet because we have to catch the message. Let’s correct that by adapting the server code we have. We’ll use the connection event to create a function, it passes in our new socket which we use to catch our message event.

var server = alien_killer.createServer( function() { /* ... */  } );
server.listen(57007);

var socketio = alien_backdoor.listen(server);
socketio.on( 'connection', function(socket) { 
    socket.on( "my-message-event" , function( message ) {
            console.log( "Message recieved:", message );
            socket.emit( "my-message-response", "Hello, Spaceship!" );
    } );
});

That’s the simplest form, but we’ll need to adapt it so we can encrypt our communication. We will need to create a piece of code we can run on both the server and the client, we’ll create a simple class with a couple of functions: scramble and unscramble. On the server we need to set it too export with the module.exports assignment:

function Communicate(  ) {
        this.scramble = function( text ) { /* ... */  }
        this.unscramble = function( text ) { /* ... */  }
}
module.exports = Communicate;

But that will fail on the browser (as there isn’t “module”) instead we need to attach Communicate to the window variable; to get both server and client working we wrap it in a try catch, it’ll add to the window if it exists and the module.exports if not:

try {
    window.Communicate = Communicate;
} catch(e) {
    module.exports = Communicate;
}

We’ll also wrap all that in a self initialising function to create a new scope which will protect the global scope (though not necessary in this particular case):

(function() {
  function Communicate() {
    this.scramble = function( text ) {
      for( var i=0,scrambled=""; i<text.length; i++) {
          scrambled += String.fromCharCode( text.charCodeAt(i) + 1 );
      }
      return scrambled;
    }
    this.unscramble = function( text ) {
      for( var i=0, unscrambled=""; i<text.length; i++) {
        unscrambled += String.fromCharCode( text.charCodeAt(i) - 1 );
      }
      return unscrambled;
    }
  }

  try {
    window.Communicate = Communicate;
  } catch(e) {
    module.exports = Communicate;
  }

})()

One last thing, we have to adjust the server code to return our html (cat) file and out javascript (communicate) file.

server.on('request', function(req, res){
	if( req.url == "/communicate.js" ) {
		res.writeHead( 200, {'content-type': 'text/javascript'} );
		fs.readFile( 'communicate.js', function( error, file ) {
			res.end(file);
		});
	}
	else {
		res.writeHead(200, {'content-type': 'text/html'} );
		fs.readFile( 'socket.html', function( error, file ) {
			res.end(file);
		});
	}
});

Real Alien World Communication

Now we can communicate! Our message will consist of some text and a piece of data, for instance:

{ message : "our_message", data: "some_123_data" }

Communication will progress thus:

  1. Browser says: “on the spaceship”,
  2. Server replies with: “get ship time” (and notes down local time)
  3. Browser sends ship time.
  4. Server works out time delay, and thus shield frequency.
  5. Blow that mother (ship) out of the skies.

We will communicate via a “miaow” event, using “emit” and “on” to send and catch messages. On the browser we have:

var socket = io.connect( 'http://signet-coarse.codio.io:8080' );
var com = new Communicate();

socket.on( "miaow", function( response ) {
  var message = com.unscramble( response["message"] );
  console.log(message);

  if( message == "GET-SHIP-TIME" ) {
    socket.emit( "miaow", { message: com.scramble( "SHIP-TIME" ), data: com.scramble( (+new Date())+"" ) } );
  }
});

socket.emit( "miaow", { message : com.scramble( "ON-SPACESHIP" ) } );

That connects, then sets up a function to catch any messages coming in, we unscramble our message, and when the right one comes we send back our data.

On the server we’ll have:

var socketio = io.listen(server);
socketio.on( 'connection', function( socket ) {    
    var localTime = null;
    var com = new Communicate();
    
    socket.on( "miaow", function( response ) {
        var message = com.unscramble( response["message"] );
        console.log(message);

        if( message == "ON-SPACESHIP" ) {
            localTime = (+new Date());
            socket.emit( "miaow", { message: com.scramble( 'GET-SHIP-TIME') } );
        }
        else if( message == "SHIP-TIME" ) {
            var shipTime = com.unscramble( response['data'] );
            var timeDifference = shipTime - localTime;

            console.log( "Time from Spaceship to Earth", timeDifference );
        }
    });
});

This is almost the same as the browser code but there’s a couple more messages to handle and we have to calculate the time difference.

Now lets start the server and take down those ugly (probably) aliens!

node server.js

Come aliens, eat cats…

Summary

I hope that was an enjoyable introduction to Node.js, we covered quite a lot there:

  • Running a server
  • Responding with different content
  • Installing a package with NPM
  • Creating code that will run on both servers and clients
  • Sockets.io communication
  • And maybe saving the Earth.

Good work. You deserve a promotion for this: “Node.JS Second Class”. Congratulations.

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.