Sí, seguro que el primer código que has visto de Node.js es uno de esos milagrosos servidores web hechos con pocas líneas. Olvídese de aparatosos sockets, de punteros o de BufferedStreamsWriters, un módulo, unos callbacks, eliges un puerto y a dar servicio. Bueno, uno trata de ser un good Node.js citizen y este código se entiende y mola, pero a la larga, si lo queremos ir mejorando y añadiendo otras funcionalidades cada vez va a ser más complicado mantenerlo. Lo que propongo en este post es tomar este código y refactorizarlo a un estilo más POO. Igual es una herejía pero bueno, supongo que es un tema que habrá que hablar con los guruses. Veamos el ejemplo típico de servidor web, sacado de un libro:

/**
* httpserver.js
* simple web server from O'Reilly Learning Node, Chp 6.
*/
var http = require('http'),
	path = require('path'),
	fs = require('fs'),

_base = '/home/examples/public_html';

http.createServer(function (req, res) {
	pathname = _base + req.url;
	console.log(pathname);

	path.exists(pathname, function(exists) {
		if (!exists) {
			res.writeHead(404);
			res.write('Bad request 404\n');
			res.end();
		} else {
			res.setHeader('Content-Type', 'text/html');
			// 200 status - found, no errors
			res.statusCode = 200;
			// create and pipe readable stream
			var file = fs.createReadStream(pathname);

			file.on("open", function() {
				file.pipe(res);
			});

			file.on("error", function(err) {
				console.log(err);
			});
		}
	});
}).listen(8124);

console.log('Server running at 8124/');

Servidor web node, refactorizado

El servidor web basado en el módulo http es sencillo pero eso de tener todo el mogollón metido dentro de un método que a su vez tiene más métodos, con más callbacks... francamente será muy molón pero eso no hay dios que lo mantenga, por mucho que tus gafas de pasta tengan un grosor de 5cm y por mucho que uses el sublime y vayas de programador hipster. A molar te vas a la bajera o al FIB, que es más mainstream que el Ubuntu. ¡Aquí debian y vim! Bah venga que es broma.

Bueno al lío. Lo que hago en este caso es darle al servidor web forma de clase y cambiar la forma de crear el servidor para que todo se maneje con métodos separados. De hecho una de las recomendaciones de la guía no oficial de estilo de Node.js es que un método debe caber en una diapositiva proyectada en pantalla y debe ser legible desde la última fila de la sala, y no son lo únicos que dicen que los métodos deben ser escuetos. Venga va, la clase MyWebServer y una clase auxiliar para los logs.

/**
* static_web_server.js
* classic sample of static web browser using html module
* refactored or reshaped for better understanding.
* YOU MUST create a public_html folder where this script resides.
* @author Pello Xabier Altadill Izura
* @greetz to \"'any'\" ;)
*/

var httpServer = require('http');
var FILESYSTEM = require('fs');
var PATHMODULE = require('path');


/**
* MyWebServer
* a class to represent a web server
*/
var MyWebServer = function (port, serverpath) {
	// this reference comes in handy in cases like this*
	var self = this;

	this.port = port;             // web server port
	this.serverpath = serverpath; // public folder
	this.http = httpServer;       // instance of http server
	this.path = PATHMODULE;    // instance of path module
	this.filesystem = FILESYSTEM; // instance of fs module
	this.server = null;
	this.logger = null;
	this.default = 'index.html';

	/**
	* We will call this method to initialize the server
	*
	*/
	this.init = function (logger) {
		this.logger = logger;

		// Instead of putting all code inside createServer...
		this.server = this.http.createServer().listen(this.port);
		// we set a handler function on request event.
		this.server.on('request', this.handleRequest);

		this.logger.log('MyWebServer > initialized');
	};

	/**
	* now we handle requests in an isolated method
	* I think is more readable...
	*/
	this.handleRequest = function (request, response) {
				// We have to use 'self' because inside this method 'this' refers to
				// instance of httpserver
				pathname = self.serverpath + request.url;
				self.logger.log("Request:" + pathname);

				// Very easy. File exists? handleOK, if not, handle 404
				self.path.exists(pathname, function(exists) {
					if (!exists) {
						self.handleBadRequest(response);
					} else {
						self.handleOk(response);
					}
				});

		};

	/**
	* handleOK is called when the request is valid
	* it opens the file as a stream and connects to response
	*/
	this.handleOk = function (response) {
				self.logger.log("200:" + pathname);
				response.setHeader('Content-Type', 'text/html');

				// We set status code to 200 status, file is found, no errors
				response.statusCode = 200;

				// We open the file and serve it as a stream
				var file = self.filesystem.createReadStream(pathname);

				file.on("open", function() {
					file.pipe(response);
				});


				file.on("error", function(err) {
					self.handleInternalServerError(response);
				});
			};

	/**
	* in case of bad request we send a 404 response
	*/
	this.handleBadRequest = function (response) {
				self.logger.log("404: BAD Request:" + pathname);
				response.writeHead(404);
				response.write('Bad request 404\n');
				response.end();
			};

	/**
	* In case of server error we send back a 500 response
	*/
	this.handleInternalServerError = function (response) {
				self.logger.log("500: Internal server error:");
				response.writeHead(500);
				response.write('Internal server error 500\n');
				response.end();
			};
};

/**
* MyCoolLogger
* just a simple class to have more verbose log messages with useless info
*/
var MyCoolLogger = function (prefix,showdate) {
		this.prefix = prefix;
		this.date = (showdate != null)?showdate:false;

		this.log = function (msg) {
			datestring = (this.date)?"["+Date()+"]":"";
			console.log(prefix + datestring + "> " + msg);
		}
};

var PORT = 8666;
var PATH = '/root/nodejs/static_web_server/public_html';

var logger = new MyCoolLogger('http_server',true);
var server = new MyWebServer(PORT, PATH);

server.init(logger);

logger.log('Server initialized on port:' + PORT);

Tiene un método en desuso pero en fin, a lo que iba: pese a que ahora tiene más líneas creo que el código es más fácil de mantener a la larga si queremos ir añadiendo cosas, como por ejemplo detectar el MIME y establecer una respuesta más ajustada. Por no hablar de aplicar pruebas unitarias, si creamos métodos separados con una función concreta siempre será más factible meter pruebas, y frameworks para javascript y para node los hay. Lo del tema de meter el self dentro de las clases en Node.js sí que debe ser algo habitual en la comunidad, y en este caso es útil.

Esto se vería en los logs:

linux:~/nodejs/static_web_server# node static_web_server.js
http_server[Sat Aug 17 2013 01:07:17 GMT+0200 (CEST)]> MyWebServer > initialized
http_server[Sat Aug 17 2013 01:07:17 GMT+0200 (CEST)]> Server initialized on port:8666
http_server[Sat Aug 17 2013 01:07:34 GMT+0200 (CEST)]> Request:/root/nodejs/static_web_server/public_html/
path.exists is now called `fs.exists`.
http_server[Sat Aug 17 2013 01:07:34 GMT+0200 (CEST)]> 200:/root/nodejs/static_web_server/public_html/
http_server[Sat Aug 17 2013 01:07:34 GMT+0200 (CEST)]> 500: Internal server error:
http_server[Sat Aug 17 2013 01:07:39 GMT+0200 (CEST)]> Request:/root/nodejs/static_web_server/public_html/index.html
http_server[Sat Aug 17 2013 01:07:39 GMT+0200 (CEST)]> 200:/root/nodejs/static_web_server/public_html/index.html
http_server[Sat Aug 17 2013 01:07:40 GMT+0200 (CEST)]> Request:/root/nodejs/static_web_server/public_html/index.htmlsadf
http_server[Sat Aug 17 2013 01:07:40 GMT+0200 (CEST)]> 404: BAD Request:/root/nodejs/static_web_server/public_html/index.htmlsadf