sábado, 19 de noviembre de 2011

Publicada la versión v0.6 de node.js

Hace dos semanas publicaron la versión v0.6 de node.js (de hecho, ahora mismo ya vamos por la v0.6.2). Entre las mejoras que incluye se pueden destacar las siguientes que detallaré a continuación:
  • Balanceo de carga entre múltiples procesos
  • Mejoras en depuración por línea de comando
  • Módulo de compresión zlib
  • Mejor soporte para comunicación entre procesos

Balanceo de carga entre múltiples procesos

Una instancia de node.js corre en un único thread. Para aprovechar de las ventajas de los sistemas de varios núcleos, podemos lanzar un cluster de procesos de node para gestionar mejor la carga.

El módulo cluster permite crear una red de procesos que compartan los puertos del servidor

var cluster = require('cluster');
var http = require('http');
var numCPUs = require('os').cpus().length;

if (cluster.isMaster) {
  console.log("Cpus encontradas:"  + process.pid)
  // Creamos un proceso trabajador por core.
  for (var i = 0; i < numCPUs; i++) {
    cluster.fork();
  }

  cluster.on('death', function(worker) {
    console.log('El trabajador ' + worker.pid + ' ha muerto');
  });
} else {
  // Los procesos trabajadores tienen un servidor http.
  http.Server(function(req, res) {
    res.writeHead(200);
    res.end("Hola mundo desde el proceso " + process.pid+ "\n");
  }).listen(8000);
}

Recargando la página varias veces veo que saca  "Hola mundo desde el proceso 9020" y "Hola mundo desde el proceso 420".


Mejoras en depuración por línea de comando

El motor V8 viene un un potente depurador que se puede acceder mediante el protocolo TCP. Para simplificarnos la vida, node.js trae un cliente para dicho depurador. Para utilizarlo hay que arrancar node.js con el argumento debug. Aparecerá a un prompt en el que se pueden realizar las tareas de depuración. A continuación muestro un ejemplo donde se utilizan los siguiente comandos:

  • cont : continúa hasta el siguiente punto de interrupción
  • next :  ejecuta siguiente línea de comando
  • repl : abre el "repl" del debugger, que nos permite ejecutar en la linea de comandos evaluaciones contra el contexto de la aplicación
  • quit : salir

El ejercicio:

C:\node.js>node debug miscript.js
< debugger listening on port 5858
connecting... ok
break in C:\node.js\miscript.js:2
  1 // miscript.js
  2 x = 5;
  3 setTimeout(function () {
  4   debugger;
debug> cont
< Hola
break in C:\node.js\miscript.js:4
  2 x = 5;
  3 setTimeout(function () {
  4   debugger;
  5   console.log("mundo!");
  6 }, 1000);
debug> next
break in C:\node.js\miscript.js:5
  3 setTimeout(function () {
  4   debugger;
  5   console.log("mundo!");
  6 }, 1000);
  7 console.log("Hola");
debug> repl
Press Ctrl + C to leave debug repl
> x
5
> 2+2
4
debug> next
break in C:\node.js\miscript.js:6
  4   debugger;
  5   console.log("mundo!");
  6 }, 1000);
  7 console.log("Hola");
  8 });
< mundo!
debug> quit

El fichero miscript.js es el siguiente:
// miscript.js
x = 5;
setTimeout(function () {
  debugger;
  console.log("mundo!");
}, 1000);
console.log("Hola");

Es bastante parecido a lo que se puede hacer por línea de comando en el depurador de Chrome (aunque ahora con al utilidad de desarrolladores apenas se usa).

Tienes una referencia a los comandos de depuración aquí: http://nodejs.org/docs/v0.6.0/api/debugger.html#commands_reference

Módulo de compresión zlib

Con este módulo resulta muy sencillo utilizar gzip, tanto al servir páginas como al consumir servicios en modo cliente. Los siguientes código lo ilustran perfectamente:


//ejemplo de un cliente con soporte gzip
//www.apache.org devuelve la pagina con gzip. 34KB de tamaño 9KB de trafico con gzip
var zlib = require('zlib');
var http = require('http');
var fs = require('fs');
var request = http.get({ host: 'www.apache.org', 
                         path: '/',
                         port: 80,
                         headers: { 'Accept-Encoding': 'gzip,deflate' } });
request.on('response', function(response) {
  var output = fs.createWriteStream('www.apache.org.index.html');
  switch (response.headers['content-encoding']) {
    // se puede usar zlib.createUnzip() para gestionar ambos casos
    case 'gzip':
      response.pipe(zlib.createGunzip()).pipe(output);
      break;
    case 'deflate':
      response.pipe(zlib.createInflate()).pipe(output);
      break;
    default:
      response.pipe(output);
      break;
  }
});

Tras ejecutar este script y podremos ver en el fichero "www.apache.org.index.html" el código fuente descargado.

A nivel de servidor es igual de sencillo:


// ejemplo de servidor
// Nota: hacer un gzip en cada petición es muy caro. Sería más 
// eficiente cachear la respuesta comprimida

var zlib = require('zlib');
var http = require('http');
var fs = require('fs');
http.createServer(function(request, response) {
  var raw = fs.createReadStream('index.html');
  var acceptEncoding = request.headers['accept-encoding'];
  if (!acceptEncoding) {
    acceptEncoding = '';
  }

  // Nota: this is not a conformant accept-encoding parser.
  // See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3
  if (acceptEncoding.match(/\bdeflate\b/)) {
    response.writeHead(200, { 'content-encoding': 'deflate' });
    raw.pipe(zlib.createDeflate()).pipe(response);
  } else if (acceptEncoding.match(/\bgzip\b/)) {
    response.writeHead(200, { 'content-encoding': 'gzip' });
    raw.pipe(zlib.createGzip()).pipe(response);
  } else {
    response.writeHead(200, {});
    raw.pipe(response);
  }
}).listen(1337);


Mejor soporte para comunicación entre procesos

El método fork() es un caso especial de spawn() para crear procesos de node.js. Adicionalmente a los métodos habituales de un proceso hijo (ChildProcess) creados por spawn, el objeto devuelto por fork tiene un canal de comunicaciones con su padre. A través del canal se envian mensajes con child.send(message, [sendHandle]) y los mensajes se reciben mediante el evento "message" en el hijo.

Los dos siguientes scripts lo ilustran perfectamente.
padre.js
var cp = require('child_process');
var hijo = cp.fork(__dirname + '/hijo.js');
hijo.on('message', function(m) {
  console.log('El PADRE recibió el mensaje:', m);
});
hijo.send({ hola: 'mundo' });

hijo.js
process.on('message', function(m) {
  console.log('El HIJO recibió el mensaje:', m);
});
process.send({ foo: 'bar' });

La ejecución queda como sigue:
C:\node.js>node padre.js
El PADRE recibió el mensaje: { foo: 'bar' }
El HIJO recibió el mensaje: { hola: 'mundo' }



No hay comentarios:

Publicar un comentario