Node.JS

From CSE330 Wiki
Revision as of 21:36, 10 April 2013 by Shane (talk | contribs) (A Static Fileserver Using Node.JS)
Jump to: navigation, search

AJAX is a useful technology for web applications that need to pull periodic updates from your server. But what do you do when you have thousands of clients all requesting data that changes real-time? A PHP+AJAX approach would choke. It is situations like these that Node.JS comes to the rescue.

Non-Blocking I/O

You dabbled in the concept of non-blocking I/O when you implemented AJAX in the previous JavaScript module. Node.JS uses this execution model at its core.

The core idea is the use of anonymous functions as callbacks for processing the results of intensive operations. Consider the pseudo-code:

// Blocking I/O Pseudo-Code

var data = database.query("select * from news");
print data[0].title;

The above pseudo-code queries a database, saves the result of the query in a variable named data, and then prints out the first title in data. The problem arises when multiple clients all want to access the server at the same time. In a blocking I/O model, a client needs to wait until all other clients are finished before it can start its process. With intensive operations and thousands of clients, this can easily crash your server!

The solution is to use non-blocking I/O. Consider this revised pseudo-code:

// Non-Blocking I/O Pseudo-Code

database.query("select * from news", function(data){
	print data[0].title;
});

The revised pseudo-code achieves the same end result as the first version. However, it allows other clients to perform tasks while this process is waiting for the database to be queried! The code occurs in two parts: first, we start the query. Second, when the query is complete, our anonymous callback function is executed. In Node.JS, callback functions are usually passed one or more parameters.

Better yet, with non-blocking I/O, you have no concurrency issues, because everything runs in just one thread. Nifty!

Installing Node.JS

Debian

Installing Node.JS is as easy as installing some packages from the Apt repository.

$ sudo apt-get install python nodejs npm nodejs-dev

Make sure you have build-essentials installed as well. (You should have done this when you first made your instance.)

To make sure it worked, open up a JavaScript prompt by typing `node`. A Hello World JavaScript could be: console.log("Hello World");

RHEL

For whatever reason, you can't currently install an updated version of Node.JS from Yum. You will therefore need to compile it from source.

Get the link to the most recent version of the Node.JS Linux binary from http://nodejs.org/download/ (you can use the 64-bit version).

Download, unpack, configure, and make the package. For example:

$ cd # go to your home directory
$ wget http://nodejs.org/dist/x.x.x/node-x.x.x-linux-x64.tar.gz # download the tarball (remember to change the path)
$ tar -zxf node-x.x.x-linux-x64.tar.gz # unpack the tarball
$ cd node-x.x.x-linux-x64
$ ./configure
$ make
$ sudo make install

To make sure it worked, open up a JavaScript prompt by typing `node`. A Hello World JavaScript could be: console.log("Hello World");

Your First Node.JS Application: Hello World

Let's try running the example application given on the front page of nodejs.org:

var http = require('http');
http.createServer(function (req, res) {
	res.writeHead(200, {
		'Content-Type': 'text/plain'
	});
	res.end('Hello World\n');
}).listen(1337, 'localhost');
console.log('Server running at http://localhost:1337/');

Paste the example into a file named `hello.js`, and then run it by calling `node hello.js`. You should now be able to see your Hello World by visiting your server at port 1337! (Press ^C to stop the application.)

Let's walk through each line in the above example.

  • var http = require('http'); enables us to use Node's built-in HTTP server. We've been spoiled that languages like PHP automatically load all of the libraries that we need. However, most functionality in Node.JS is NOT loaded by default. When you want to use any non-fundamental feature of Node, you need to require it into a variable, as this example does with the HTTP package. You should be familiar with this concept when you needed to use ArrayList and other specialty packages in Java in CSE 132.
  • http.createServer(function (req, res) { ... }).listen(1337, 'localhost'); uses the HTTP server API to listen for HTTP connections on port 1337. The anonymous callback function will be called whenever a connection is made.
  • res.writeHead(200, { 'Content-Type': 'text/plain' }); specifies the Content-Type header to be "text/plain".
  • res.end('Hello World\n'); writes "Hello World" to the response.

Note the use of non-blocking I/O, even in this one little example. Whenever a connection is made, the callback function will be called and passed parameters associated with that connection.

A Static Fileserver Using Node.JS

Node.JS does not automatically serve up static files in the way that an Apache/PHP does. We need to either use an extension that handles this for us, like Express, or we need to write our own code to make a simple static fileserver. The latter option is really not all that difficult, and it gives good insight into how a static fileserver works.

The following code will make an HTTP server on port 3456 that serves up all static files in <STATIC DIRECTORY NAME> relative to app.js.

// Require the functionality we need to use:
var http = require('http'),
	url = require('url'),
	path = require('path'),
	mime = require('mime'),
	fs = require('fs');

// Make a simple fileserver for all of our static content.
// Everything underneath <STATIC DIRECTORY NAME> will be served.
var app = http.createServer(function(req, resp){
	var filename = path.join(__dirname, "<STATIC DIRECTORY NAME>", url.parse(req.url).pathname);
	fs.exists(filename, function(exists){
		if (exists) {
			fs.readFile(filename, function(err, data){
				if (err) {
					// File exists but is not readable (permissions issue?)
					resp.writeHead(500, {
						"Content-Type": "text/plain"
					});
					resp.write("Internal server error: could not read file");
					resp.end();
					return;
				}
				
				// File exists and is readable
				var mimetype = mime.lookup(filename);
				resp.writeHead(200, {
					"Content-Type": mimetype
				});
				resp.write(data);
				resp.end();
				return;
			});
		}else{
			// File does not exist
			resp.writeHead(404, {
				"Content-Type": "text/plain"
			});
			resp.write("Requested file not found: "+filename);
			resp.end();
			return;
		}
	});
});
app.listen(3456);

Express: A Node.JS Web Framework

On its own, Node.JS has only rudimentary HTTP handling. Express makes the handling of HTTP requests much easier.

First, you'll need to install Express. You can do this using Node's own package manager, npm:

$ sudo npm install -g express

Note: the -g means to install the express package globally, so that it is accessible to all Node applications on your instance. Not using -g would install the package so that only the project in the current working directory could use it.