Está en la página 1de 242

INTRODUCCIN A NODE.

JS A
TRAVS DE KOANS

Arturo Muoz de la Torre Monzn (@arturomtm)


Ingeniero de Teleco por la Universidad Politcnica de
Madrid. Entusiasta de las tecnologas que dan vida a la
Web. Curioso e imaginativo, siempre en evolucin.

Introduccin a Node.JS a travs de Koans


por Arturo Muoz de la Torre Monzn
http://nodejskoans.com
Revisin del texto:
D. Carlos ngel Iglesias Fernndez
Profesor en la Escuela Tcnica Superior de Ingenieros de
Telecomunicacin de la Universidad Politcnica de Madrid.

El contenido de esta obra (excepto el cdigo fuente)


est sujeto a una licencia Creative Commons Atribucin - No comercial - CompartirIgual 3.0 Unported.
El cdigo fuente de los programas contenidos en esta obra, escritos por el
autor exclusivamente para ella, estn todos bajo una licencia GPL.
Koans for Node.js
Copyright (C) 2013

Arturo Muoz de la Torre

This program is free software: you can redistribute it and/or


modify it under the terms of the GNU General Public License as
published by the Free Software Foundation, either version 3 of
the License, or any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the

GNU General Public License for more details.


You should have received a copy of the GNU General Public License
along with this program.

If not, see

<http://www.gnu.org/licenses/>.

El diseo de la portada ha corrido a cargo del autor del libro, empleando para ello
las tipografas: Bebas Neue, una versin modificada de la Aharoni Bold, Comfortaa, Tribal Animals Tatto Design (http://tattoowoo.com/) y Entypo pictograms
by Daniel Bruce (www.entypo.com).
2013-05-23

A mis padres, Arturo y Fabi, y a


mi hermano Edu, por su apoyo y
confianza sin lmites.
A Irene, porque sin ella no hubiera llegado nunca al final.

ndice general
ndice general

ndice de Tablas

ndice de Figuras

VII

1. Introduccin

1.1. Qu son los Koans? . . . . . . . . . . . . . . . . . . . . . . . . . . . .

1.2. Gua de lectura . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

1.3. Conseguir el cdigo fuente . . . . . . . . . . . . . . . . . . . . . . . . .

2. Introduccin a Node v0.8

2.1. Qu es Node? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

2.2. Es una plataforma . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

2.2.1. El proceso de arranque de Node . . . . . . . . . . . . . . . . . .

2.2.2. El formato CommonJS . . . . . . . . . . . . . . . . . . . . . . . 14


2.2.3. Mdulos disponibles en el core de Node

. . . . . . . . . . . . . 15

2.2.4. Mdulos de terceras partes . . . . . . . . . . . . . . . . . . . . . 20


2.3. Construida encima del entorno de ejecucin de JavaScript de Chrome . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
2.3.1. El lenguaje JavaScript . . . . . . . . . . . . . . . . . . . . . . . . 22
2.3.2. El motor V8 de Google . . . . . . . . . . . . . . . . . . . . . . . . 24
2.4. Fcil desarrollo de rpidas, escalables aplicaciones de red

. . . . . 25

2.5. Usa E/S no bloqueante dirigida por eventos . . . . . . . . . . . . . . 27


2.5.1. El modelo de Concurrencia de Node . . . . . . . . . . . . . . . . 28
2.5.2. Arquitectura de Node . . . . . . . . . . . . . . . . . . . . . . . . 29
2.5.3. La clase EventEmitter . . . . . . . . . . . . . . . . . . . . . . . . 33
2.5.4. Postponiendo la ejecucin de funciones . . . . . . . . . . . . . . 34
2.6. Es ligero y eficiente [1]

. . . . . . . . . . . . . . . . . . . . . . . . . . 35
I

2.7. Perfecto para aplicaciones en tiempo real data-intensive . . . . . . . 36


2.7.1. Tiempo real y Node . . . . . . . . . . . . . . . . . . . . . . . . . . 36
2.7.2. Para qu es til Node entonces? . . . . . . . . . . . . . . . . . 37
3. Mdulos Buffer y Dgram

39

3.1. Aspectos de UDP relevantes para Node . . . . . . . . . . . . . . . . . . 39


3.2. UDP en Node . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
3.3. Codificacin de caracteres . . . . . . . . . . . . . . . . . . . . . . . . . 44
3.4. Buffers en Javascript . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
3.5. Aplicacin con Buffers y UDP . . . . . . . . . . . . . . . . . . . . . . . 50
3.5.1. Descripcin del problema . . . . . . . . . . . . . . . . . . . . . . 50
3.5.2. Diseo propuesto

. . . . . . . . . . . . . . . . . . . . . . . . . . 52

3.5.2.1. El protocolo RTP . . . . . . . . . . . . . . . . . . . . . . 52


3.5.2.2. Descripcin de la solucin . . . . . . . . . . . . . . . . 54
3.6. Objetivos de los Koans . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
3.7. Preparacin del entorno y ejecucin de los Koans . . . . . . . . . . . . 62
3.8. Conclusin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
4. Mdulos Stream y Net

65

4.1. Aspectos de TCP relevantes para Node . . . . . . . . . . . . . . . . . . 65


4.2. Streams en Node . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
4.3. TCP en Node . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
4.4. Aplicacin con Streams TCP . . . . . . . . . . . . . . . . . . . . . . . . 78
4.4.1. Descripcin del problema . . . . . . . . . . . . . . . . . . . . . . 78
4.4.2. Diseo propuesto

. . . . . . . . . . . . . . . . . . . . . . . . . . 79

4.5. Objetivos de los Koans . . . . . . . . . . . . . . . . . . . . . . . . . . . 84


4.6. Preparacin del entorno y ejecucin de los Koans . . . . . . . . . . . . 85
4.7. Conclusin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
5. Mdulo Http

89

5.1. Aspectos de HTTP relevantes para Node . . . . . . . . . . . . . . . . . 89


5.1.1. La parte del Cliente . . . . . . . . . . . . . . . . . . . . . . . . . 91
5.1.2. La parte del Servidor . . . . . . . . . . . . . . . . . . . . . . . . . 96
5.2. HTTP en Node

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100

5.2.1. ServerRequest . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103


5.2.2. ServerResponse . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
5.2.3. Clientes HTTP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
5.2.4. ClientResponse . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
II

5.3. Aplicacin con HTTP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110


5.3.1. Descripcin del problema . . . . . . . . . . . . . . . . . . . . . . 110
5.3.2. Diseo propuesto

. . . . . . . . . . . . . . . . . . . . . . . . . . 112

5.4. Objetivos de los Koans . . . . . . . . . . . . . . . . . . . . . . . . . . . 117


5.5. Preparacin del entorno y ejecucin de los Koans . . . . . . . . . . . . 119
5.6. Conclusin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
6. Express

121

6.1. Connect . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121


6.2. Express . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
6.2.1. Request . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
6.2.2. Response . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
6.3. MongoDB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
6.4. Aplicacin de ejemplo . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
6.4.1. Clon de Twitter, objetivo 1: autenticacin y control de sesiones 140
6.4.1.1. Descripcin del Objetivo . . . . . . . . . . . . . . . . . . 140
6.4.1.2. Diseo propuesto al Objetivo implementado con Express . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141
6.4.1.3. Objetivos de los Koans

. . . . . . . . . . . . . . . . . . 149

6.4.1.4. Preparacin del entorno y ejecucin de los Koans . . . 150


6.4.2. Clon de Twitter, objetivo 2: publicacin de whizs y follow y
unfollow de otros usuarios . . . . . . . . . . . . . . . . . . . . . 151
6.4.2.1. Descripcin del Objetivo . . . . . . . . . . . . . . . . . . 151
6.4.2.2. Diseo propuesto al Objetivo implementado con Express . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
6.4.2.3. Objetivos de los Koans

. . . . . . . . . . . . . . . . . . 154

6.5. Conclusin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155


7. Socket.IO

157

7.1. Qu es Socket.IO? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157


7.1.1. Socket.IO pretende hacer posible las aplicaciones en tiempo
real . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
7.1.2. en cada navegador y dispositivo mvil

. . . . . . . . . . . . . 158

7.1.3. difuminando las diferencias entre los diferentes mecanismos


de transporte

. . . . . . . . . . . . . . . . . . . . . . . . . . . . 159

7.2. Usando Socket.IO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163


7.2.1. Servidor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
III

7.2.2. Cliente [2] . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167


7.3. Aplicacin con Socket.IO . . . . . . . . . . . . . . . . . . . . . . . . . . 176
7.3.1. Descripcin del juego . . . . . . . . . . . . . . . . . . . . . . . . 176
7.3.2. Objetivos perseguidos . . . . . . . . . . . . . . . . . . . . . . . . 176
7.3.3. Diseo propuesto

. . . . . . . . . . . . . . . . . . . . . . . . . . 177

7.3.4. Implementacin con Socket.IO . . . . . . . . . . . . . . . . . . . 181


7.4. Objetivos de los Koans . . . . . . . . . . . . . . . . . . . . . . . . . . . 186
7.5. Preparacin del entorno y ejecucin de los Koans . . . . . . . . . . . . 187
7.6. Conclusin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188
8. Conclusin y trabajos futuros

189

A. Listados

191

A.1. Mdulos dgram y Buffer

. . . . . . . . . . . . . . . . . . . . . . . . . . 191

A.2. Mdulos net y Stream . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196


A.3. Mdulo http . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201
A.4. Mdulo Express . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 206
A.5. Mdulo Socket.IO

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217

IV

ndice de tablas
5.1. Ejemplos de tipos MIME . . . . . . . . . . . . . . . . . . . . . . . . . . 94
6.1. Algunas de las variables configurables en los ajustes de Express . . . 129

VI

ndice de figuras
3.1. Reproductor VLC, Men Media

. . . . . . . . . . . . . . . . . . . . . 51

3.2. Reproductor VLC, Open Network Stream . . . . . . . . . . . . . . . . 51


3.3. Reproductor VLC, configuracin de la IP . . . . . . . . . . . . . . . . . 52
3.4. Diagrama de Colaboracin de la Solucin RTP . . . . . . . . . . . . . 54
3.5. Ejecucin de Netcat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
3.6. Respuesta a la peticin con Netcat . . . . . . . . . . . . . . . . . . . . 64
4.1. Diagrama de Colaboracin de la Solucin TCP

. . . . . . . . . . . . . 79

4.2. Telnet al puerto de la aplicacin . . . . . . . . . . . . . . . . . . . . . . 86


4.3. Reproductor VLC, configuracin . . . . . . . . . . . . . . . . . . . . . . 87
4.4. Solucin TCP, salida de los comandos . . . . . . . . . . . . . . . . . . 87
5.1. Interfaz de la Solucin HTTP . . . . . . . . . . . . . . . . . . . . . . . . 111
5.2. Autenticacin en la Solucin HTTP . . . . . . . . . . . . . . . . . . . . 111
5.3. Diagrama de colaboracin de la Solucin HTTP . . . . . . . . . . . . . 112
6.1. Creacin de las Colecciones en el prompt de MongoDB . . . . . . . . . 140
6.2. Scaffolding creado por Express

. . . . . . . . . . . . . . . . . . . . . . 142

6.3. Pgina principal de la aplicacin con Express . . . . . . . . . . . . . . 148

VII

VIII

Captulo 1

Introduccin
Con el presente proyecto se pretenden establecer unas lneas de aprendizaje de
la plataforma Node a travs de un recorrido interactivo por sus principales mdulos, tanto propios como de terceras partes, con el que introducirse y asentar
sus principios bsicos de funcionamiento. A travs de seis captulos se quiere
proporcionar al lector de una amplia visin de qu herramientas pone Node a su
disposicin y cmo debe usarlas para realizar sus propios desarrollos.
Cada uno de estos captulos tratar uno o dos mdulos muy relacionados entre
s, con la intencin de que se cubran tpicos que vayan de menor complejidad a
mayor. La estructura de dichos captulos es siempre la misma:
un ligero repaso a los conceptos tericos que hay detrs del mdulo, generalmente desgranando una RFC (Request For Comments), con especial nfasis
en aquellos que el API da opcin a manejar.
un recorrido por el API del mdulo no sintetizado, como pueda aparecer en
la documentacin de Node, sino explicando la relaciones intrnsecas entre
los mtodos, propiedades y eventos del mdulo.
una propuesta de aplicacin a desarrollar que est basada en el mdulo que
se est tratando. Se procurar que la aplicacin cubra la mayor parte posible
de los conceptos que se destacaron en la introduccin terica.
el diseo propuesto al anterior problema, focalizando el desarrollo en aquellos puntos que usan las herramientas que el mdulo de Node proporciona
para cumplir con los requisitos que la especificacin anterior del problema
1

impone.
una seleccin de fragmentos de cdigo, cuyo dominio se considera obligatorio para el conocimiento mnimo necesario del mdulo, de tal manera que
permitan desarrollar una aplicacin con l. Estos fragmentos son los candidatos para convertirse en Koans. Los Koans son la tcnica de aprendizaje
escogida para el proyecto. En los los prrafos siguientes se describir qu
son.
La seleccin de mdulos que se ha realizado viene motivada esencialmente por
un factor: Node est pensado para aplicaciones de red. Tiene sentido, por tanto,
que sean todos aquellos relacionados con este hecho. Y no slo dentro de los
mdulos nativos de la plataforma, sino tambin aquellos que su popularidad entre
la comunidad de desarrolladores los ha hecho un estndar de facto. El proyecto
comprender pues los siguientes captulos:
Captulo 3: mdulos dgram y Buffer, o UDP como primera parada para ir
cogiendo la dinmica de la plataforma.
Captulo 4: mdulos net y stream, para introducir con TCP un punto de
complejidad en los protocolos de transporte.
Captulo 5: mdulo http, con el que subir un nivel en la pila de protocolos.
Captulo 6: mdulos Express, como la herramienta perfecta para la creacin
de aplicaciones web.
Captulo 7: mdulo Socket.IO, el paradigma de protocolo para las aplicaciones en tiempo real.
Como se ha comentado unas lneas atrs, el mtodo elegido para el aprendizaje
interactivo de la plataforma son los Koans, pero...qu son los Koans?

1.1.

Qu son los Koans?

El trmino koan proviene de la filosofa Zen oriental. Es el nombre que se le


da a un pequeo problema que un maestro plantea a su discpulo para evaluar
su progreso. Suelen ser breves y escuetos y, a menudo, con apariencia trivial o
absurda pero que, sin embargo, suponen para el discpulo un paso ms en su
2

camino hacia el despertar puesto que suponen para l realizar un esfuerzo para
entender la doctrina.
Los koans en el mbito informtico fueron idea de Joe OBrien y Jim Weirich, de
EdgeCase, como forma de alcanzar la iluminacin en el aprendizaje del lenguaje
Ruby. Manteniendo la analoga con la filosofa Zen, los koans de Ruby son pequeos trozos de cdigo donde se sustituye alguna parte con unos guiones bajos __,
al estilo de los ejercicios de rellenar el hueco, que el alumno deber cambiar por el
cdigo que piense que es el correcto para que el ejercicio ejecute sin errores. Para
evaluar si lo que el alumno ha escrito en lugar de los __ es correcto se emplean
unos casos de prueba unitarios diseados para cada koan.
Posteriormente los koans se han extendido a ms lenguajes de programacin,
entre los que se incluyen JavaScript o Python.
La filosofa de los Koans que se han diseado para Node es la misma que la que
se ha presentado para los lenguajes de programacin: fragmentos de cdigo evaluables mediante pruebas unitarias que marquen al alumno el camino a seguir
para la correcta comprensin y asimilacin de los aspectos relevantes necesarios para iniciarse en Node. Estos trozos de cdigo se engloban en el marco de
una aplicacin concreta, con lo que no son independientes entre s, sino que su
resolucin lleva al alumno a tener en sus manos un programa con completa funcionalidad donde entender mejor cules son los puntos clave ms bsicos cuando
se programa con Node como tecnologa de Servidor.

1.2.

Gua de lectura

Con la intencin con la que se crearon los Koans se crea esta obra, cuyos captulos
van marcando el camino hacia el conocimiento y manejo de Node. El recorrido que
se marca para ello es el que sigue:
Captulo 2
El punto de partida que se va a tomar para introducir la programacin de
aplicaciones para Internet es uno de sus protocolos ms sencillos: UDP. Con
l se presentar el mdulo dgram, que implementa las caractersticas de
este protocolo, y Buffer, ntimente ligado a los protocolos de red porque es la
manera que tiene Node de manejar datos binarios sin un formato concreto,
en bruto.
3

Con dgram se tomar contacto con la creacin y gestin de conexiones UDP


y, sobre todo, con todo lo referente a operaciones de Entrada/Salida asncrona en redes, como envo y recepcin de datos.
Con Buffer, se conocer una de las clases bsicas de Node, siempre presente
en el espacio de ejecucin de cualquier programa, por lo que resulta imprescindible conocer cmo se crean y se manejan los datos que contiene a travs
de los mltiples mtodos que Buffer ofrece para ello.
El pegamento que junta ambos mdulos es una aplicacin que implementa
el protocolo RTP (Real Time Protocol), basado en UDP, para transmitir canciones a los clientes que lo soliciten.
Captulo 3
Siguiendo con los protocolos de red, se da un paso ms con respecto al tema
anterior y se introduce el mdulo net, que da soporte a la transmisin de
datos sobre el protocolo TCP, hemano mayor de UDP. Estos datos se ven
como un flujo continuo, o stream, con lo que la inclusin del mdulo stream
en este captulo cobra sentido.
TCP introduce mayor complejidad, lo que se traduce en mayor nmero de parmetros a controlar en una transmisin, completando as los conocimientos
adquiridos en el tema anterior sobre comunicacin Cliente-Servidor. Ahora
se introducirn los mtodos especficos para TCP que se usan para gestionar
el ciclo de vida de una conexin: desde su generacin a su cierre, pasando
por la lectura y escritura de datos en ella.
El paso que se avanza con este captulo se refleja en la aplicacin ejemplo
que se plantea como problema: es una evolucin de la anterior, donde ahora
se incorpora soporte TCP para el control del envo del audio a los clientes.
Se conseguir introduciendo un servidor TCP que acepte conexiones por las
que se reciban comandos en modo texto.
Captulo 4
Por encima del nivel de transporte, en el que se ubicaran los protocolos
vistos en los captulos anteriores, se encuentra el nivel de aplicacin para el
que Node tambin posee mdulos en su API. En concreto, para el Protocolo
de Transferencia de Hipertexto, HTTP, en el que se basa toda la Web.
La naturaleza de este mdulo es similiar a la de los anteriores puesto que
4

sigue el modelo Cliente-Servidor ya asimilado, pero se aaden ms caractersticas propias de la especificacin HTTP. Por ello, junto al manejo de
conexiones HTTP se introducirn los mecanismos para gestionar peticiones
interpretando los mtodos y cabeceras propias del protocolo para generar
las respuestas de la manera ms adecuada.
Aunque HTTP se site por encima de TCP, no implica que no pueda convivir
con ms protocolos en una misma aplicacin. En este caso, en la aplicacin
de ejemplo, se utilizar junto con RTP para proporcionar una interfaz web
con la que controlar qu se emite por streaming, simulando un reproductor
de audio tradicional.
Captulo 5
Una vez que se conocen las libreras fundamentales de Node es hora de
evolucionar y subir un peldao en la arquitectura de los mdulos de terceras
partes para la plataforma. Visto HTTP, se presentar la manera de tratar con
l desde otra perspectiva: la de la creacin de Aplicaciones Web.
Si se sigue el movimiento en la pila de una aplicacin web para Node puede
encontrarse con Connect y Express, dos de las libreras ms famosas del
entorno. Ambas hacen uso de los mdulos que ofrece Node para poner a
disposicin del programador un middleware (Connect) y un framework web
(Express). A lo largo del captulo se aprender a configurar Connect para incorporar las diversas funcionalidades que ofrece y a hacer uso de la funcionalidad enriquecida que aade Express a las peticiones y respuestas HTTP,
as como a entender el nivel de abstraccin que supone respecto del mdulo
nativo http de Node.
Junto con ellas, se hablar de Mongoose, un driver para Node para la base
de datos no relacional MongoDB. Este tipo de bases de datos estn muy
presentes en el desarrollo de las aplicaciones web ms modernas y, como
toda base de datos, son imprescindibles a la hora de desarrollar una solucin
completa. Por ser un driver muy completo y complejo, se presentarn las
operaciones ms elementales que se pueden realizar con l para emprender
un desarrollo bsico pero perfectamente funcional.
Captulo 6
El ltimo peldao antes de adquirir una base ms o menos amplia sobre la
programacin para Internet con Node, son las aplicaciones en tiempo real,
5

las cuales estn adquiriendo, si es que no lo han hecho ya, una importancia
fundamental en cualquier desarrollo web.
Con este captulo se aprendern algunos de los conceptos realacionados
con ellas, y se afianzarn con un mdulo, Socket.IO, diseado para hacerlo
de una manera eficiente y sencilla. Se presentar su funcionamiento, cmo
realiza y gestiona todas las conexiones, tanto en la parte servidor como en la
cliente, y cmo se produce el intercambio de datos entre los mismos.
Siguiendo el mismo esquema que en captulos anteriores, se concluir con
el desarrollo de una aplicacin que haga uso de las mnimas partes necesarias para obtener un comportamiento que responda a las caractersticas de
tiempo real. Para apreciarlas, qu mejor que un juego donde una interaccin
instantnea es obligada para una correcta experiencia de usuario.

1.3.

Conseguir el cdigo fuente

Todo el cdigo que se ha generado para este proyecto puede localizarse en Github1 , una comunidad de desarrolladores. El repositorio concreto donde se hallan
todos los archivos es uno en la cuenta personal del autor del libro, al que se ha llamado nodejskoans: https://github.com/arturomtm/nodejskoans.git. Para
tener una copia con la que trabajar, es necesario clonarlo en un directorio de la
mquina de trabajo:
$ mkdir nodejskoans
$ cd nodejskoans
$ git clone https://github.com/arturomtm/nodejskoans.git
Una vez clonado, se puede trabajar con el cdigo tal y como se explica en los
captulos que siguen.

http://github.com

Captulo 2

Introduccin a Node v0.8


Node.js, de ahora en adelante Node, es un proyecto creado por Ryan Dahl a principios de 2009. Se dise orientado a la creacin de aplicaciones para Internet,
principalmente Web, porque la programacin de software para servidores era el
tipo de desarrollo que haca el autor en aquella fecha.
La idea empez a gestarse a partir de otro proyecto para el framework Ruby on
Rails, un pequeo y rpido servidor web llamado Ebb, tambin de la autora de
Ryan Dahl, que evolucion a una librera en C [3]. El aspecto negativo de esto era
la complejidad que supone el lenguaje C para programar aplicaciones basadas en
dicha librera. Es en este punto donde entra en escena el, por aquel entonces, recin aparecido intrprete de JavaScript de Google, V8, que no tard en adoptarse
como el motor de la plataforma emergente.
Una de las razones de la evolucin del proyecto desde Ruby a C, y luego de C a
JavaScript fue el objetivo de realizar un sistema en que la Entrada/Salida fuera
enteramente no bloqueante. Ryan tena claro que era esencial para obtener un
alto rendimiento. Con Ruby y C siempre haba una parte del sistema que era
bloqueante. Pero JavaScript se ajusta a este requisito porque est diseado para
ejecutarse en un bucle de eventos, que es, precisamente, lo que Node hace: delegar en la plataforma las operaciones de Entrada/Salida que solicita la aplicacin.
De esta manera Node puede seguir realizando tareas sin estar bloqueado esperando, y cuando las operaciones se hayan completado, procesar en su bucle de
eventos el evento generado y ejecutar el cdigo que lo maneja segn se haya definido. La consecuencia de este modelo Entrada/Salida es que se puede atender
a un altsimo nmero de clientes a la vez, motivo que ha llevado a Node a ser el
7

paradigma de plataforma de aplicaciones de tiempo real.

Desde la presentacin de Node, el proyecto no ha parado de crecer. Actualmente,


es el segundo repositorio ms popular en Github1 , con ms de 20.000 seguidores,
y tiene ms de 24.794 libreras, a las que llaman mdulos, registradas en la web
de su gestor de paquetes, NPM2 . Todo ello estando slo, a fecha de redaccin de
este proyecto, en la versin 0.8 (rama estable).

Adems de la gran actividad en Internet, a la que hay que sumarle el concurso


mundial de aplicaciones Node Knockout3 , la comunidad de Node tiene una cita
anualmente con el ciclo de conferencias NodeConf4 , donde se presentan todos los
avances de la plataforma, o con la JSConf5 , un evento para desarrolladores de
JavaScript donde la plataforma es protagonista de muchas de las conferencias
que se realizan.

Node est apadrinado por la compaa Joyent6 , que contrat a Ryan Dahl cuando
comenzaba el proyecto. Joyent ofrece, conjuntamente con Nodejistsu7 , como IaaS,
un entorno en la Nube donde desplegar aplicaciones Node.

Pero no slo Joyent ofrece alojamiento para esta plataforma. Heroku8 tambin
tiene soluciones cloud-computing personalizables, y, si se eligen opciones gratuitas
y open source, Nodester9 da espacio para aplicaciones como PasS.

Como colofn, Node fue galardonado en 2012 con el premio Tecnologa del Ao
por la revista InfoWorld, perteneciente a una divisin prestigioso grupo internacional de prensa especializada IDG. Y, posiblemente, todo no haya hecho ms que
empezar.

https://github.com
https://npmjs.org/
3
http://nodeknockout.com/
4
http://www.nodeconf.com
5
http://jsconf.com
6
http://joyent.com
7
https://www.nodejitsu.com/
8
http://www.heroku.com
9
http://nodester.com
2

2.1.

Qu es Node?

La mejor manera de aproximarse a Node es a travs de la definicin que aparece


en su pgina web: [4]
Node.js es una plataforma construida encima del entorno de ejecucin javascript
de Chrome para fcilmente construir rpidas, escalables aplicaciones de red.
Node.js usa un modelo de E/S no bloqueante dirigido por eventos que lo hace ligero y eficiente, perfecto para aplicaciones data-intensive en tiempo real
Esta visin global de Node se puede diseccionar en pequeas partes que, una vez
analizadas separadamente, dan una visin mucho ms precisa y detallada de las
caractersticas del proyecto.

2.2.

Es una plataforma

En efecto, Node provee un entorno de ejecucin para un determinado lenguaje


de programacin y un conjunto de libreras bsicas, o mdulos nativos, a partir
de las cuales crear aplicaciones orientadas principalmente a las redes de comunicacin, aunque una parte de estas libreras permite interactuar con componentes del sistema operativo a travs de funciones que cumplen con el estndar
POSIX.
Bsicamente este estndar o familia de estndares define las interfaces y el entorno, as como utilidades comunes, que un sistema operativo debe soportar y
hacer disponibles para que el cdigo fuente de un programa sea portable (compilable y ejecutable) en diferentes sistemas operativos que implementen dicho
estndar. En este caso, Node facilita funciones para manejo de archivos, Entrada/Salida, seales y procesos conformes a las caractersticas establecidas por
POSIX [5].

2.2.1.

El proceso de arranque de Node

El cdigo que realiza el arranque, o bootstrap, del ncleo de la plataforma es


lo primero que se ejecuta y uno de sus cometidos es proveer el mecanismo para cargar el resto de los mdulos del core segn vayan siendo necesarios o se
9

demanden. Se puede echar un vistazo a este cdigo en el fichero del cdigo fuente src/node.js. Ah se ve que el encargado de realizar la carga es el objeto
NativeModule, que ofrece un minimalista sistema de gestin de mdulos.
Pero aparte de NativeModule, en el proceso de arranque ocurren muchas ms
cosas, de las que se encargan el resto de funciones presentes en node.js.
Una de las primeras acciones que se realizan es hacer disponibles las variables,
objetos y funciones globales. Por globales se entiende que estn disponibles en el
scope donde corre Node, que se hace disponible al programa mediante, precisamente, la variable global. Por defecto, disponemos de las funciones que ofrecen
los mdulos console y timers, el objeto Buffer, del mdulo buffer, y el objeto
nativo process.
Se hablar de process por ser quizs uno de los objetos ms interesantes de la
plataforma desde el punto de vista del diseo. Presente en el espacio global de
ejecucin del proceso principal de Node representa a ese mismo proceso. Como
se ha dicho, se hace disponible en l despus de crearse en cdigo nativo a travs de la funcin SetupProcessObject() en el proceso de arranque definido en
el fichero src/node.cc10 . Con esta funcin se crea un objeto al que se le aaden propiedades y mtodos en cdigo nativo que luego estn disponibles para el
programador a travs del API. stos permiten:
identificar la arquitectura y sistema operativo donde corre Node, mediante
las

propiedades

process.arch

process.platform,

con

process.features
tener conocimiento mnimo sobre el proceso de Node como su identificador
de proceso con process.pid, el directorio de trabajo con process.cwd(),
que se puede cambiar con process.chdir(), el path desde donde se inici
con process.execPath, o las variables de entorno del usuario con
process.env
conocer versiones de Node y de libreras nativas con las propiedades
process.version y process.versions
en caso de sistemas *nix, manejar identificadores de grupos y usuarios con
process.getuid(),

process.setuid(),

process.getgid()

process.setgid()
10

node.cc https://github.com/joyent/node/blob/v0.8.20-release/src/node.cc#L2166

10

estadsticas de ejecucin como el tiempo que lleva corriendo el proceso, con


process.uptime()

la

memoria

que

est

consumiendo

con

process.memoryUsage()
Adems, existen una serie de variables y mtodos no documentados del todo que
son interesantes desde el punto de vista de funcionamiento interno de Node:
se tiene un registro de los mdulos que se han cargado en la plataforma, en el array process.moduleLoadList. En esta lista identificaremos
dos tipos de mdulos por la etiqueta que precede al nombre del mdulo:
NativeModule y Binding.
NativeModule se refiere a los mdulos JavaScript que componen el core de
Node y se cargan a travs del objeto NativeModule.
Binding, por su parte, identifica a los mdulos escritos en C/C++ de ms
bajo nivel que los otros. Estos bindings o addons, ofrecen sus mtodos o sus
objetos al cdigo de un programa como si de otro mdulo ms se tratara, o,
en la arquitectura de Node, sirven como base a las libreras en JavaScript.
Un binding se carga internamente con el mtodo process.binding(), que
no debera ser accesible para el desarrollador, aunque lo es y se puede invocar.
Resear

que

process.binding()

no

tiene

nada

que

ver

con

process.dlopen(), otro mtodo que tambin se puede invocar, aunque debe hacerlo siempre el cargador de mdulos, y que sirve para cargar los addons de terceras partes o que no forman parte del ncleo de Node. Son dos
maneras diferentes de cargar libreras, que en el fondo hacen lo mismo pero
con distinta finalidad.
se tiene tambin estadsticas de diagnstico del mencionado bucle de eventos
a travs de process.uvCounters(). Obtendremos los contadores internos
del bucle de eventos que se incrementan cada vez que se registra un evento
en l.
Seguidamente, se termina de inicializar el objeto process ya a nivel de cdigo no
nativo, sino JavaScript:
Se establece la clase EventEmitter su como prototipo, con lo cual proccess
hereda sus mtodos y por tanto la capacidad de generar eventos y notificarlo
a sus subscriptores. Se profundizar en EventEmitter cuando se hable de
11

la arquitectura de Node.
Se establecen los mtodos de interaccin con el bucle de eventos

11 .

El bu-

cle de eventos es una caracterstica de la arquitectura de Node que tiene


mucho que ver con el altsimo rendimiento de la plataforma. Ms adelante se analizar en detalle este punto. A nivel de programador, el API ofrece
process.nextTick().
Se inicializan los Streams de Entrada/Salida estndar 12 . El API los presenta
como las propiedades: process.stdout, process.stderr, habilitados ambos para la escritura y, a diferencia de los dems Streams de Node, son
bloqueantes; y process.stdin que es de lectura y est pausado de inicio
cuando la entrada estndar no es otro Stream, con lo que, para leer de l, se
debe abrir primero invocando a process.openStdin().
Se definen los mtodos relacionados con las seales13 del sistema operativo:
process.exit(), para terminar la ejecucin del programa especificando un
cdigo de salida, y process.kill(), para enviar seales a otros procesos,
tal y como se hara con la llamada al sistema operativo kill.
Se aade funcionalidad a los mtodos para manejar listeners14 que hereda
de EventEmitter, recubrindolos para que sean capaces de escuchar y actuar
sobre seales del sistema operativo (tipo Unix), no slo eventos.
Se determina si, en lugar de ser el proceso principal de Node, el programa
es un proceso worker del modulo cluster, de los que se hablar cuando se
comente el entorno de ejecucin de Node.
Por ltimo, se determinar en qu modo se va a ejecutar Node y se entra en l.
Actualmente hay varios modos:
Script
se accede a este modo pasando por lnea de comandos el nombre de un
fichero .js que contiene el cdigo del programa. Node cargar ese fichero y
lo ejecutar. Es el modo ms comn de trabajar en la plataforma.
Tambin se puede indicar por lnea de comandos mediante el modificador -e
11

node.js https://github.com/joyent/node/blob/v0.8.20-release/src/node.js#L47
node.js https://github.com/joyent/node/blob/v0.8.20-release/src/node.js#L352
13
node.js https://github.com/joyent/node/blob/v0.8.20-release/src/node.js#L432
14
node.js https://github.com/joyent/node/blob/v0.8.20-release/src/node.js#L464
12

12

o eval seguido del nombre del script.


REPL
son las siglas de Read Eval Print Loop. Es el modo interactivo en el que
Node presenta un prompt (>) donde manualmente se van introduciendo
expresiones y comandos que sern evaluados al presionar la tecla Enter.
Debug
se invoca incluyendo el modificador debug en la lnea de comandos y deja
a Node en un modo interactivo de depuracin de scripts donde se pueden
introducir mediante comandos las acciones tpicas en este tipo de sesiones:
step in, step out, fijar y limpiar breakpoints...
Adicionalmente, los autores del proyecto permiten cargar cdigo propio del programador y arrancar con l en lugar de hacerlo con el arranque normal de Node.
Para ello se debe incluir en el directorio lib/ del cdigo fuente del proyecto el
fichero _third_party_main.js que contendr el cdigo personalizado, y luego
compilarlo todo.
El objeto que carga las libreras, como se ha comentado, es NativeModule el cual
ofrece un pequeo mecanismo para la carga y gestin de mdulos. Este mecanismo es auxiliar y una vez cumple su funcin se reemplaza por la funcionalidad,
ms completa, que ofrece el mdulo module, cargado a su vez, paradjicamente, por NativeModule. Cuando se desea tener disponible cualquier mdulo para
usarlo en un programa, el mtodo require() que se invoca es el de module15 .
Este mtodo comprueba, con ayuda de NativeModule, si el mdulo est en la cach de mdulos y en caso negativo, lo busca en el sistema de ficheros o en caso
de que sea un mdulo del core de Node, en el propio ejecutable de la plataforma,
ya que stos estn embebidos en l [6]. Una vez localizado, lo compila, lo mete en
la cach y devuelve su exports, es decir, las funciones, variables u objetos que el
mdulo pone a disposicin del programador.
Las libreras del core son tambin archivos de extensin .js que se hallan en
el directorio lib/ del cdigo fuente (pero empotrados en el ejecutable una vez
compilado, como se ha dicho). Cada uno de esos archivos es un mdulo que sigue
el formato que define CommonJS.
15

module.js https://github.com/joyent/node/blob/v0.8.20-release/lib/module.js#

L377

13

2.2.2.

El formato CommonJS

CommonJS es una creciente coleccin de estndares que surgen de la necesidad


de completar aspectos que se echan en falta en la especificacin de JavaScript,
el lenguaje de programacin usado en Node. Entre estos aspectos perdidos se
pueden nombrar la falta de una API estndar y bien definida, la de unas interfaces
estndar para servidores web o bases de datos, la ausencia de un sistema de
gestin de paquetes y dependencias o, la que de mayor incumbencia: la falta de
un sistema de mdulos.[7]
Node soporta e implementa el sistema que CommonJS define para esta gestin
de mdulos, lo cual es importante no slo a la hora de organizar el cdigo sino
a la hora de asegurar que se ejecuta sin interferir en el cdigo de los dems
mdulos aislndolos unos de otros de tal manera que no haya conflicto entre, por
ejemplo, funciones o variables con el mismo nombre. A esto se le conoce como
scope isolation.
Las implicaciones del uno de este estndar son:[8]
El uso de la funcin require() para indicar que queremos emplear una
determinada librera pasndole el identificador de la librera (su nombre)
como parmetro.
La existencia de la variable exports dentro de los mdulos. Esta variable
es un objeto que es el nico modo posible que tiene un mdulo de hacer
pblicas funciones y dems objetos, aadindolas a exports conforme se
ejecuta el cdigo de dicho mdulo.
La definicin dentro de un mdulo de la variable module. Esta variable es un
objeto con la propiedad obligatoria id que identifica unvocamente al mdulo
y por tanto, se obtiene de l el exports que interese al desarrollador.
En definitiva, se puede hacer uso de las caractersticas que ofrezca un mdulo,
siempre y cuando ste las tenga aadidas a su exports, si se indica en el cdigo
con require(modulo).
Por ejemplo, el siguiente mdulo, al que se llamar circle y se usar en un programa cdigo gracias a require(circle), permite calcular el rea y permetro
de cualquier circulo; sin embargo, no permite conocer el valor de la constante PI
ya que no la incluye en su exports:
14

var PI = 3.14;
exports.area = function (r) {
return PI * r * r;
};
exports.circumference = function (r) {
return 2 * PI * r;
};

2.2.3.

Mdulos disponibles en el core de Node

Se dispone pues de una serie de mdulos que conforman el ncleo de Node y


que se pueden usar en las aplicaciones. Para utilizar la gran mayora de estas
libreras se debe indicar explcitamente, mediante la funcin require(), que,
como se ha visto, es el mecanismo que Node ofrece para tener disponibles los
exports de los mdulos. Sin embargo, como se ha comentado antes, hay una
serie de mdulos que estn disponibles implcitamente, ya que se cargan en el
proceso de bootstrap presente en src/node.js. El API los denomina Globals y
ofrecen distintos objetos y funciones accesibles desde todos los mdulos, aunque
algunos de ellos slo en el mbito (scope) del mdulo, no en el mbito global (de
programa). Estos mdulos son:
console
marcado en el API como STDIO, ofrece el objeto console para imprimir mensajes por la salida estndar: stdout y stderr. Los mensajes van desde los
habituales info o log hasta trazar la pila de errores con trace.
timers
ofrece las funciones globales para el manejo de contadores que realizarn
la accin especificada pasado el tiempo que se les programa. Debido a la
cmo est diseado Node, relacionado con el bucle de eventos del que se
hablar en un futuro, no se puede garantizar que el tiempo de ejecucin de
dicha accin sea exactamente el marcado, sino uno aproximado cercano a
l, cuando el bucle est en disposicin de hacerlo.
module
proporciona el sistema de mdulos segn impone CommonJS. Cada mdulo
que se carga o el propio programa, est modelado segn module, que se ver
como una variable, module, dentro del mismo mdulo. Con ella se tienen dis15

ponibles tanto el mecanismo de carga require() como aquellas funciones y


variables que exporta, en module.exports, que destacan entre otras menos
corrientes que estn a un nivel informativo: mdulo que ha cargado el actual
(module.parent),

mdulos

que

carga

el

actual

(module.children)...
buffer
es el objeto por defecto en Node para el manejo de datos binarios. Sin embargo, la introduccin en JavaScript de los typedArrays desplazar a los Buffers
como manera de tratar esta clase de datos [9].
Los mdulos siguientes, listados por su identificador, tambin forman parte del
ncleo de Node, aunque no se cargan al inicio, pero se exponen a travs del
API:
util
conjunto de utilidades principalmente para saber de si un objeto es de tipo
array, error, fecha, expresin regular...Tambin ofrece un mecanismo para
extender clases de JavaScript a travs de herencia:
inherits(constructor, superConstructor);
events
provee la fundamental clase EventEmitter de la que cualquier objeto que
emite eventos en Node hereda. Si alguna clase del cdigo de un programa
debe emitir eventos, sta tiene que heredar de EventEmitter.
stream
interfaz abstracta que representa los flujos de caracteres de Unix de la cual
muchas clases en Node heredan.
crypto
algoritmos y capacidades de cifrado para otros mdulos y para el cdigo de
programa en general.
tls
comunicaciones cifradas en la capa de transporte con el protocolo TLS/SSL,
que proporciona infraestructura de clave pblica/privada.
string_decoder
proporciona una manera de, a partir de un Buffer, obtener cadenas de ca16

racteres codificados en utf-8.


fs
funciones para trabajar con el sistema de ficheros de la manera que establece el estndar POSIX. Todos los mtodos permiten trabajar de forma
asncrona (el programa sigue su curso y Node avisa cuando ha terminado
la operacin con el fichero) o sncrona (la ejecucin del programa se detiene
hasta que se haya completado la operacin con el fichero).
path
operaciones de manejo y transformacin de la ruta de archivos y directorios,
a nivel de nombre, sin consultar el sistema de ficheros.
net
creacin y manejo asncrono de servidores y clientes, que implementan la
interfaz Stream mencionada antes, sobre el protocolo de transporte TCP.
dgram
creacin y manejo asncrono de datagramas sobre el protocolo transporte
UDP.
dns
mtodos para tratar con el protocolo DNS para la resolucin de nombres de
dominio de Internet.
http
interfaz de bajo nivel, ya que slo maneja los Streams y el paso de mensajes, para la creacin y uso de conexiones bajo el protocolo HTTP, tanto del
lado del cliente como del servidor. Diseada para dar soporte hasta a las
caractersticas ms complejas del protocolo como chunk-encoding.
https
versin del protocolo HTTP sobre conexiones seguras TLS/SSL.
url
formateo y anlisis de los campos de las URL.
querystrings
utilidades para trabajar con las queries en el protocolo HTTP. Una query son
los parmetros que se envan al servidor en las peticiones HTTP. Dependiendo del tipo de peticin (GET o POST), pueden formar parte de la URL por lo
17

que deben codificarse o escaparse y concatenarse de una manera especial


para que sean interpretadas como tal.
readline
permite la lectura lnea por lnea de un Stream, especialmente indicado para
el de la entrada estndar (STDIN ).
repl
bucle de lectura y evaluacin de la entrada estndar, para incluir en programas que necesiten uno. Es exactamente el mismo mdulo que usa Node
cuando se inicia sin argumentos, en el modo REPL comentado con anterioridad.
vm
compilacin y ejecucin bajo demanda de cdigo.
child_process
creacin de procesos hijos y comunicacin y manejo de su entrada, salida y
error estndar con ellos de una manera no bloqueante.
assert
funciones para la escritura de tests unitarios.
tty
permite ajustar el modo de trabajo de la entrada estndar si sta es un
terminal.
zlib
compresin/descompresin de Streams con los algoritmos zlib y gzip. Estos formatos se usan, por ejemplo, en el protocolo HTTP para comprimir los
datos provenientes del servidor. Es conveniente tener en cuenta que los procesos de compresin y descompresin pueden ser muy costosos en trminos
de memoria y consumo de CPU.
os
acceso a informacin relativa al sistema operativo y recursos hardware sobre
los que corre Node.
_debugger
es el depurador de cdigo que Node tiene incorporado, a travs de la opcin
debug de la lnea de comandos. En realidad es un cliente que hace uso
18

de las facilidades de depuracin que el intrprete de Javascript que utiliza


Node ofrece a travs de una conexin TCP al puerto 5858. Por tanto, no es
un mdulo que se importe a travs de require() sino el modo de ejecucin
Debug del que se ha hablado antes.
cluster
creacin y gestin de grupos de procesos Node trabajando en red para distribuir la carga en arquitecturas con procesadores multi-core.
punycode
implementacin del algoritmo Punycode, disponible a partir de la versin
0.6.2, para uso del mdulo url. El algoritmo Punycode se emplea para convertir de una manera unvoca y reversible cadenas de caracteres Unicode a
cadenas de caracteres ascii con caracteres compatibles en nombres de red.
El propsito es que los nombres de dominio internacionalizados (en ingls,
IDNA), aquellos con caracteres propios de un pas, se transformen en cadenas soportadas globalmente. [10]
domain
mdulo experimental en fase de desarrollo y, por tanto, no cargado por defecto para evitar problemas, aunque los autores de la plataforma aseguran
un impacto mnimo. La idea detrs de este l es la de agrupar mltiples
acciones de Entrada/Salida diferentes de tal manera que se dotan de un
contexto definido para manejar los errores que puedan derivarse de ellas [9].
De esta manera el contexto no se pierde e incluso el programa continua su
ejecucin.
Quedan una serie de libreras, que no se mencionan en la documentacin del API
pero que existen en el directorio lib/ del cdigo fuente. Estas libreras tienen
propsitos auxiliares para el resto de los mdulos, aunque se pueden utilizarlas
a travs de require():
_linklist
implementa una lista doblemente enlazada. Esta estructura de datos se emplea en timers.js, el mdulo que provee funcionalidad de temporizacin.
Su funcin es encadenar temporizadores que tengan el mismo tiempo de
espera, timeout. Esta es una manera muy eficiente de manejar enormes cantidades de temporizadores que se activan por inactividad, como los timeouts
de los sockets, en los que se reinicia el contador si se detecta actividad en
19

l. Cuando esto ocurre, el temporizador, que est situado en la cabeza de la


lista, se pone a la cola y se recalcula el tiempo en que debe expirar el primero
[11].
buffer_ieee754
implementa la lectura y escritura de nmeros en formato de coma flotante
segn el estndar IEEE754 del IEEE16 que el mdulo buffer emplea para las
operaciones con Doubles y Floats.
constants
todas las constantes posibles disponibles de la plataforma como, por ejemplo, las relacionadas con POSIX para seales del sistema operativo y modos
de manejo de ficheros. Slo realiza un binding con node_constants.cc.
freelist
proporciona una sencilla estructura de pool o conjunto de objetos de la misma clase (de hecho, el constructor de los mismos es un argumento necesario). Su utilidad se pone de manifiesto en el mdulo http, donde se mantiene
un conjunto de parsers HTTP reutilizables, que se encargan de procesar las
peticiones HTTP que recibe un Servidor.
sys
es un mdulo deprecado, en su lugar se debe emplear el mdulo utils.
Todos los mdulos anteriores, una vez se ha compilado la plataforma, quedan
incorporados dentro del binario ejecutable, por lo que, fsicamente por su nombre
de archivo no son localizables en disco. Por otra parte, si en disco hubiera un
mdulo cuyo identificador, segn CommonJS, coincidiera con el de algn mdulo
del ncleo, el mdulo que se cargara sera el contenido en el binario de Node, o
sea, el mdulo del core.

2.2.4.

Mdulos de terceras partes

Node, adems de por las caractersticas que se estn desgranando, es una gran
plataforma de desarrollo por el inmenso ecosistema que ha crecido en torno suyo.
Se pueden encontrar infinidad de mdulos desarrollados por terceras partes para
usar en proyectos propios, o desarrollar libreras con un propsito especfico,
tanto para uso propietario como para uso de la comunidad Node.
16

IEEE: Institute of Electrical and Electronics Engineers

20

A la hora de utilizar mdulos de terceras partes, se debe ser capaz de especificar


su ubicacin para cargarlos o saber dnde Node va a buscarlos para instalarlos.
Cuando se indica, a travs de la funcin del estndar CommonJS, que un mdulo es necesario, el orden que sigue Node para resolver la ruta del fichero hasta
encontrarlo es:
1. Cargar el fichero de nombre igual que el identificador del mdulo, modulo.
2. Si no lo encuentra, le aadir la extensin .js y buscar modulo.js que
interpretar como un fichero de cdigo JavaScript.
3. Si tampoco lo encuentra, le aadir la extensin .json y buscar
modulo.json e interpretar el contenido como un objeto en notacin
JavaScript (JSON).
4. Por ltimo, buscar modulo.node e intentar cargarlo como cdigo C/C++
compilado, a travs del mtodo process.dlopen() comentado con anterioridad.
5. Si el identificador del mdulo fuese un directorio, por ejemplo modulo/, Node
buscar el archivo modulo/package.json que contiene informacin de cul
es el punto de entrada a la librera. De no hallarlo, intentara resolver dicho
punto se ha descrito antes, primero buscando modulo/index.js y, de no
encontrarlo, buscando modulo/index.node.
Determinar la ruta dentro del sistema de ficheros de la mquina donde se halla
el fichero depende de si el identificador de mdulo comienza por:
/ se est referenciando una ruta absoluta en el sistema, con lo que el cargador empezar a buscar desde la raz del sistema de archivos del sistema
operativo
./ o ../ la referencia es a una ruta relativa al directorio donde se encuentra
el mdulo
si el identificador no comienza con las barras, ni es un mdulo del core, Node
lo buscar en el directorio node_modules/ que lo supone al mismo nivel en
el rbol de directorios que el fichero de cdigo que requiere al mdulo. Si no
lo encuentra ah, ir buscndolo en todos los directorios padres hasta llegar
al directorio raz del sistema de ficheros. El propsito de ello es localizar las
dependencias de una aplicacin para que no colisionen.
21

Por ltimo, existen unas variables de entorno desde donde Node buscar libreras
en caso de que todo lo anterior falle. Por ejemplo, desde la ubicacin que indique
NODE_PATH. No obstante, este mtodo se desaconseja totalmente por haber quedado obsoleto, con lo que no se le va a dar mayor importancia.

2.3.

Construida encima del entorno de ejecucin de JavaScript de Chrome

2.3.1.

El lenguaje JavaScript

El lenguaje de programacin que se usa en Node es Javascript. Javascript es un


dialecto de la especificacin estndar ECMA-262 [12]. El uso mayoritario ha estado en el llamado lado del cliente, en referencia a la arquitectura cliente-servidor,
para la capa de presentacin de las aplicaciones web, ntimamente ligado con el
DOM (Document Object Model) [13] que es la interfaz de programacin estndar
para acceder y manipular el contenido, la estructura y el estilo de los componentes de una pgina web con los que JavaScript interacta en el Navegador
proporcionando una experiencia interactiva al usuario.
Es un lenguaje que se caracteriza principalmente porque es: [14]
interpretado: est diseado para que una capa intermedia de software, el intrprete, lo ejecute, en contraposicin a los lenguajes compilados, que corren
directamente sobre la arquitectura y sistema operativo objetivo (por regla
general, ya que Java es compilado pero se ejecuta dentro de una mquina
virtual)
dinmico: ligado con la caracterstica anterior, realiza acciones que en otro
tipo de lenguajes se haran en tiempo de compilacin, como evaluacin de
cdigo, o eval, que es la interpretacin de cdigo en tiempo de ejecucin, o
como la modificacin, tambin en tiempo de ejecucin, de las caractersticas
de las clases o el tipo de las variables
funcional: la programacin funcional es un paradigma de programacin que
se basa en la evaluacin de expresiones, como son las funciones, evitando
tener estados y datos mutables, a diferencia de la programacin imperativa
que se basa en cambios de estado a travs de la ejecucin de instrucciones
22

[15]. JavaScript posee caractersticas de la programacin funcional como


son las Funciones de Orden Superior. Se denominan as a las funciones que
admiten por parmetro otras funciones o que como resultado devuelven una
funcin.
orientado a objetos parcialmente (o basado en objetos): de los tres requisitos bsicos que definen este tipo de programacin, JavaScript no soporta
el polimorfismo y la encapsulacin slo es posible para funciones dentro
de funciones (las funciones de orden superior antes mencionadas), aunque
posee un modelo de herencia por prototipado.
dbilmente tipado: se realiza una conversin, o cast, del tipo de las variables
en tiempo de ejecucin, segn el uso que se hace de ellas.
JavaScript encaja perfectamente en el paradigma de la programacin orientada a
eventos, en la que el flujo del programa no es secuencial sino que depende de los
eventos, asncronos en su mayora por naturaleza, que se producen durante la
ejecucin del mismo. Las caractersticas funcionales del lenguaje de las que hemos hablado son tiles en este paradigma. Las funciones de primer orden hacen
posible el uso de:
funciones annimas: son las funciones que se pasan como parmetro a las
funciones de orden superior. Son tiles para usarlas como callback, asignndolas por parmetro a algn objeto mediante el mtodo correspondiente.
Los callbacks son funciones que se ejecutan como respuesta a un evento.
closures, o cierres: se generan cuando una variable queda referenciada fuera
de su scope a travs, por ejemplo, de una funcin devuelta por otra funcin
de orden superior. Se emplean en callbacks para hacer referencia a scopes
que de otra manera se perderan. [16]
Al ser un lenguaje interpretado, Javascript requiere de un intrprete o mquina
virtual que lo ejecute. En la actualidad hay varios de estos intrpretes disponibles,
por ejemplo: SpiderMonkey de Mozilla, Nitro de Apple o V8 de Google.
SpiderMonkey y V8 fueron diseados para servir como motor Javascript de los
navegadores web Mozilla Firefox y Google Chrome respectivamente. Pero, a diferencia del motor Nitro para Webkit, pueden ser embebidos en otras aplicaciones,
como es este caso.
23

2.3.2.

El motor V8 de Google

La eleccin del motor V8 de Google para Node se debi a que ambos proyectos
comenzaron prcticamente a la vez [3] y, en palabras del autor de Node, V8 es
una buena, organizada librera. Es compacta e independiente de Chrome. Se distribuye en su propio paquete, es fcil de compilar y tiene un buen archivo header
con una buena documentacin. No tiene dependencia de ms cosas. Parece ms
moderna que la de Mozilla [17]. Adems este motor presenta unas caractersticas
revolucionarias a la hora de interpretar JavaScript.
Para realizar una comparativa entre los diferentes motores JavaScript se pueden
ejecutar los test de benchmarking [18] diseados para probar V8. Estas pruebas
evalan el rendimiento en operaciones matemticas complejas, como criptografa,
o una simulacin de kernel de sistema operativo entre otros muchos, y se ejecutan
sobre el motor presente en el navegador.
V8 es de cdigo abierto, bajo licencia New BSD [19], y est escrito en C++. Implementa la 5a edicin del estndar ECMA-262 y es multiplataforma, lo que ha
permitido a Node estar presente en sistemas tipo Unix (estndar POSIX), Mac y,
a partir de la versin 0.6, en Windows [20].
Dos de las caractersticas principales de V8, que lo han hecho destacar sobre el
resto de motores, son:
compilacin y ejecucin de cdigo JavaScript: el cdigo fuente se pasa cdigo
mquina cuando es cargado y antes de ser ejecutado por primera vez.
recoleccin eficiente de basura: este trmino se utiliza al hablar de la liberacin de memoria que ocupan los objetos JavaScript cuando no van a usarse ms. V8 emplea un recolector stop-the-world, es decir, V8, cclicamente,
detiene la ejecucin del programa (en la llamada pausa embarazosa) procesando slo una parte de la memoria heap para minimizar el efecto de la
parada. Durante esta parada no se crean nuevos objetos pero tambin se
evita con ella que no haya indisponibilidad momentnea de los existentes
[21].
V8 permite a cualquier aplicacin C++ que lo use hacer disponibles sus objetos
y funciones al cdigo JavaScript que ejecuta. Un claro ejemplo de esto es el ya
comentado objeto process disponible en el scope global de cualquier programa
para Node.
24

Todo el cdigo JavaScript que corre en Node, tanto el de su ncleo como el de


programa, se ejecuta en un nico contexto que se crea cuando se inicia la plataforma. Para V8, los Contextos son entornos de ejecucin separados y sin relacin
que permiten que se ejecuten varias aplicaciones JavaScript, una en cada Contexto, en una nica instancia del intrprete [22]. En el caso de Node, como se ha
comentado, slo hay uno y slo una instancia de V8 por lo que nicamente se
ejecuta un programa a la vez. Si se desea ejecutar ms de un script de Node, se
deben levantar ms instancias.
El hecho de que la plataforma ejecute una sola instancia de V8 induce a pensar
que en mquinas con procesadores de mltiples cores, todos menos uno quedarn desaprovechados. Para paliar esta deficiencia existe un mdulo, cluster, an
en estado experimental, que permite lanzar una red de procesos Node, en el que
uno de ellos hace de maestro (master) y el resto de trabajadores (workers). El rol
de maestro es el de vigilar que los procesos trabajadores ejecuten su tarea, que
ser la misma en todos, incluso compartiendo puertos TCP (a diferencia de si se
lanzasen los procesos mediante el mdulo child_process). Un error en alguno de
los workers que lo lleve a detenerse generar un evento death que puede ser
recogido por el maestro para proceder conforme est programado. Por supuesto,
entre los procesos puede haber comunicacin a travs de mensajes, con la misma
API que se emplea en el mdulo child_process.

2.4.

Fcil desarrollo de rpidas, escalables aplicaciones


de red

Una ventaja de emplear JavaScript como lenguaje para las aplicaciones en Node
es que, al ser un lenguaje con una curva de aprendizaje pronunciada, es decir, que
sus fundamentos bsicos se aprenden fcilmente, es posible empezar a desarrollar aplicaciones rpidamente con slo tener unas nociones de las caractersticas
fundamentales del lenguaje y conocer el grueso de las libreras del ncleo de la
plataforma.
Un ejemplo muy tpico, y que aparece en la pgina web de Node, es un sencillo
servidor HTTP que responde a las peticiones con un Hello World en tan slo
cinco lneas de cdigo:
var http = require(http);
25

http.createServer(function (req, res) {


res.writeHead(200, {Content-Type: text/plain});
res.end(Hello World\n);
}).listen(1337, "127.0.0.1");
console.log(Server running at http://127.0.0.1:1337/);
En el cdigo anterior se puede apreciar lo comentado:
el uso de require(http) segn CommonJS para indicar que se usa el
mdulo http
el uso de funciones de ese mdulo para tratar con servidores web y peticiones
HTTP
una de las caractersticas fundamentales de JavaScript relacionada con su
orientacin a eventos: el callback, que es la funcin que se pasa como parmetro a createServer() y que se ejecutar cada vez que el servidor reciba
una peticin HTTP
Este trozo de cdigo se ha usado para realizar pruebas de rendimiento y comparativas frente a otras tecnologas para usos similares que emplean otra aproximacin (por ejemplo, multithreading) para tratar con los problemas asociados a la
escalabilidad de aplicaciones.
Cuando se habla de escalabilidad caben dos perspectivas sobre el tema:
escalabilidad horizontal: aquella en la que, idealmente, el rendimiento crece
linealmente con la adicin de nodos al sistema. En este caso, se tendra un
conjunto de mquinas, cada una de ellas ejecutando Node de manera sincronizada con el resto. En la prctica Node no incorpora caractersticas que
permitan realizar esto de una manera concreta e integrada con su arquitectura pero esto no implica que no sea posible, siempre que lo desarrolle el
programador.
escalabilidad vertical: en la que se busca un aumento, tambin lineal idealmente, de rendimiento aadiendo recursos a un solo nodo, donde corre el
sistema. Los recursos principales son la CPU, memoria, disco y red donde
a Node puede exigrsele un poco ms [23]. Pero la puesta a punto de estos
recursos es slo una parte para obtener el mximo rendimiento.
Una de los aspectos con mayor impacto en la escalabilidad es el diseo del sis26

tema. ste es uno de los puntos fuertes de Node. Su arquitectura y la forma en


que las aplicaciones deben programarse para correr en ella hacen que se cumplan
principios bsicos del buen diseo para la escalabilidad [24], un par de ejemplos
de esto son:
Sin esperas: el tiempo que un proceso espera a que un recurso est disponible es tiempo que otro proceso no emplea para ejecutarse. La naturaleza
asncrona y monoproceso de Node aseguran que se optimizar el tiempo de
ejecucin de ambas tareas.
Lucha por los recursos: Node gestiona internamente de manera eficiente los
recursos del sistema para que todas las operaciones sobre, por ejemplo, red
o ficheros que se demandan en el cdigo estn satisfechas sin que se abuse
de ellas. As se evita un uso descompensado de las mismas por las distintas
partes del cdigo de la aplicacin.
No obstante, la escalabilidad real de Node est todava por verse [25].

2.5.

Usa E/S no bloqueante dirigida por eventos

Uno de los puntos crticos, usual cuello de botella, que afecta en alto grado al
rendimiento de cualquier sistema, en especial a aquellos que hacen un uso intensivo de datos, ya sea mediante fichero, bases de datos o conexiones a travs
de una red, son las operaciones de Entrada/Salida con ficheros y dispositivos.
Habitualmente, en las presentaciones de Node que su autor emplea para dar a
conocer la plataforma, se justifica el diseo de la arquitectura con unas medidas
sobre el impacto que tiene la interaccin de un sistema normal con las distintas
fuentes de datos en trminos de latencia y ciclos de reloj de la CPU. En concreto,
se manejan las cifras siguientes [26]:
cach L1: 3 ciclos
cach L2: 14 ciclos
RAM: 250 ciclos
disco: 41.000.000 ciclos
red: 240.000.000 ciclos
Las latencias correspondientes a las cachs del procesador y la memoria RAM son
muy bajas, pero se puede considerar que las de disco y red poseen un retardo
27

excepcionalmente alto que deja al procesador desocupado. La reaccin habitual


a estos ciclos muertos, en el sentido de actividad de un programa tpico, es,
simplemente, la espera a que la operacin termine para continuar con la ejecucin
de las siguientes instrucciones. Es lo que se conoce como sistemas bloqueantes:
durante los ciclos de Entrada/Salida el programa se queda bloqueado en esa
tarea.
A nivel de aplicacin, el ejemplo comn, utilizado tambin en las presentaciones
sobre Node, es la peticin tpica a una base de datos:
result = query(select * from table);
//utilizar result
donde no se podr utilizar el resultado de la peticin hasta que no se haya completado. Consecuentemente, se bloquea el proceso entero.

2.5.1.

El modelo de Concurrencia de Node

La solucin que plantea el autor de Node es un modelo de concurrencia basado


en eventos. En este modelo de concurrencia el flujo del programa viene determinado por la reaccin del programa a diversos eventos, en su mayora asncronos.
Las implicaciones que conlleva el uso de un modelo de estas caractersticas son
varias:
la necesidad de un bucle de procesado de eventos. ste se ejecutar en un
nico proceso y, lo que es ms importante, slo ejecutar un manejador de
eventos, o callback, a la vez. De esto se desprende que no se debe de ninguna manera realizar operaciones especialmente complejas en ese manejador,
que supongan un gran uso de CPU o sean de larga duracin puesto que
el bucle dejar de atender los siguientes eventos que se produzcan con la
consecuente degradacin del programa.
el uso de un lenguaje que encaje en el modelo basado en eventos. En este
caso, JavaScript es la opcin ideal pues el intrprete de este lenguaje se basa
en un modelo idntico.
La consecuencia ms relevante de esto es que el sistema resultante es no bloqueante: durante los ciclos de Entrada/Salida, el programa realiza las siguientes
tareas hasta que se hayan completado dichos ciclos lo cual ser notificado con
28

un evento que desencadenar la ejecucin del callback, cuyo cdigo modela la


siguiente tarea a realizar una vez se dispone del resultado de las operaciones
Entrada/Salida.
Nuevamente, a nivel de aplicacin, esta vez el cdigo tendra el aspecto que sigue:
query(select * from table, function(result){
//utilizar result
});
donde despus de la llamada a query() se retorna de inmediato al bucle de
eventos para seguir con la ejecucin del programa. Una vez se haya completado
la peticin a la base de datos, se ejecutar la funcin de callback en la que se
puede utilizar el resultado de la llamada.

2.5.2.

Arquitectura de Node

Implementando este modelo se cumple uno de los objetivos de diseo [26] de Node
que es que ninguna funcin debe realizar Entrada/Salida directamente. Node
delega todas estas operaciones a mecanismos del sistema operativo a travs de
la librera libuv [27], desarrollada especficamente para la arquitectura de Node
[28]. Libuv surge cuando se porta la plataforma a Windows y el propsito es el
de abstraer todo el cdigo que sea dependiente de una plataforma especfica (*nix/Windows), en concreto aquel referente a la gestin de eventos y operaciones
Entrada/Salida no bloqueantes.
En versiones de Node previas, hasta la 0.6, en los sistemas *nix aquella funcionalidad resida en las libreras libev y libeio respectivamente, y en sistemas
Windows, en IOCP. Con la versin 0.6 se introdujo libuv que implementaba para
Windows la funcionalidad que antes corresponda a IOCP, aunque para *nix segua teniendo debajo a libev y libeio. Con la versin 0.8 de la plataforma, libuv
asume las funciones de las dos libreras para todos los sistemas operativos.
Para entender qu funciones asume libuv se debe conocer qu papel desempeaban las dos libreras que originariamente formaban parte de Node:
Libev, en palabras de sus creadores, es un bucle de eventos en el que se
registran los denominados event watchers que indican qu eventos debe el
29

bucle gestionar y cmo debe reaccionar ante ellos, va callback. Los eventos
son de muchos tipos, desde que un descriptor, como un fichero o un socket,
puede comenzar a leerse hasta que se ha producido un timeout. Node de
por s, al arranque, registra muy pocos watchers17 , tan slo los referentes
al recolector de basura y aquel que se encarga de la ejecucin de funciones
postpuestas para la siguiente iteracin del bucle [11].
Por su parte, libeio provee los mecanismos de Entrada/Salida asncrona para conseguir programas completamente no bloqueantes. Ofrece versiones
asncronas de la mayora de funciones del estndar POSIX para manejo de
ficheros, que es para lo que se utiliza en Node quedando la Entrada/Salida
de red gestionada por libev. Internamente, emplea un pool de threads [29]
que, por la naturaleza de Node, es transparente para el programador y por
tanto, no hacen que Node sea multithreaded a nivel de sistema, no de aplicacin. Adems se integra a la perfeccin en cualquier bucle de eventos, como
por ejemplo con el que proporciona libuv [30].
El ltimo paso al inicio de Node es la llamada a la funcin que arranca el bucle de
eventos. En este punto ya tenemos cargado en el Contexto todo el cdigo JavaScript, tanto del ncleo (bootstrap) como propio, donde se han definido ante qu
eventos reacciona el programa y el comportamiento ante ellos a travs de funciones de callback. La misin del bucle es esperar a que stos ocurran y ejecutar el
cdigo que modela dicho comportamiento.
Desde que se define un callback como respuesta a alguna operacin de Entrada/Salida, hasta que dicho callback entra en ejecucin, en Node se siguen una serie
de pasos:
1. se carga el mdulo que a su vez carga el binding correspondientes
2. se instancia la clase que provee el binding, disponible a travs de V8, a la
que se le instalan los callbacks.
3. el binding escuchar los eventos que se produzcan en las operaciones de
Entrada/Salida
4. si hay un callback instalado, el binding lo ejecutar a travs de
MakeCallback(), una funcin interna de node.cc para ejecutar cdigo
JavaScript desde cdigo C.
17

node.cc https://github.com/joyent/node/blob/v0.8.20-release/src/node.cc#L2820

30

Los bindings son la manera que tiene el cdigo en JavaScript de los mdulos del
core de hacer uso del cdigo nativo [31]. Un binding es un mdulo escrito en
C/C++ que es capaz de utilizar las libreras, por ejemplo libev y libeio, a travs de
libuv, para interactuar con el bucle de eventos y la Entrada/Salida directamente,
que es algo que el cdigo JavaScript no puede hacer. Se podra decir que es una
librera a bajo nivel. El binding a su vez proveer una serie funciones al mdulo
para que ste ofrezca en su exports al desarrollador de aplicaciones su funcionalidad. Por ejemplo, los siguientes bindings se emplean en las libreras del core
de Node:
buffer
Soporte de codificacin en diferentes sistemas y manejo de datos binarios a
bajo nivel.
Se usa en el mdulo buffer.js
cares_wrap
Consultas asncronas a DNS a travs de la librera cares de la que Node
depende.
Se usa en el mdulo dns.js
constants
Exposicin directa de las constantes posibles de la plataforma.
Se usa en los mdulos child_process.js, constants.js, fs.js
crypto
Capacidades criptogrficas y de cifrado a travs de la librera openSSL.
Se usa en los mdulos crypto.js, tls.js
evals
Acceso a funcionalidad de mquina virtual V8 sobre la manipulacin de Contextos para la evaluacin de scripts con cdigo JavaScript. De ah el nombre
del mdulo, node_script.cc.
Se usa en los mdulos module.js, vm.js
fs
Operaciones propias del sistema de ficheros, no operaciones con ficheros
como el nombre del cdigo fuente del mdulo podra indicar (node_file.cc).
31

Entre otras: manejo de directorios o cambio de permisos.


Se usa en los mdulos fs.js, path.js
fs_event_wrap
Modela la clase evento de sistema de archivos que se emplea para notificar
cambios o errores en el mismo.
Se usa en el mdulo fs.js
http_parser
Usado en conjunto con la dependencia http_parser de Node, que extrae
del mensaje HTTP toda la informacin relevante. Sirve de capa intermedia
entre sta y el cdigo JavaScript realizando principalmente la gestin de los
callbacks.
Se usa en los mdulos http.js, querystring.js
natives
Contiene el cdigo fuente de todas las libreras del core de Node. El cdigo
fuente del mdulo se genera en tiempo de compilacin por lo que no se
hallar en disco.
Se usa en el mdulo _debugger.js
os
Funciones de recogida de informacin sobre el sistema operativo directamente expuestas a os.js.
Se usa en el mdulo os.js
pipe_wrap
Capacidades de comunicacin con procesos y con streams de TCP.
Se usa en los mdulos child_process.js, net.js
process_wrap
Mtodos spawn y kill para el manejo de procesos.
Se usa en el mdulo child_process.js
tcp_wrap
Manejo de conexiones TCP y su stream asociado.
32

Se usa en el mdulo net.js


timer_wrap
Funciones bsicas para el manejo de un timer: start, stop, again...
Se usa en el mdulo timers.js
tty_wrap
Integracin con el terminal de libuv.
Se usa en el mdulo tty.js
udp_wrap
Manejo de UDP.
Se usa en el mdulo dgram.js
zlib
Integracin del proceso de compresin y/o descompresin en el thread pool
de libuv.
Se usa en el mdulo zlib.js
Un mdulo cargar un binding gracias a la funcin binding() del objeto process.
Esta funcin se pens para uso interno de Node, pero es accesible al programador. Se podran desarrollar mdulos para sustituir a los existentes, si se quisiera
o se tuviera la necesidad y conocimientos suficientes.
Cuando un binding se carga, su nombre aparece en la lista de mdulos cargados moduleLoadList del objeto process precedido de la palabra Binding para diferenciarlo de los mdulos JavaScript, a cuyo nombre precede la palabra
NativeModule.

2.5.3.

La clase EventEmitter

Pero la Entrada/Salida no es la nica manera que existe en Node de que se produzcan eventos. Se puede dotar a cualquier clase de la habilidad de emitirlos
gracias a la clase EventEmitter que el mdulo events facilita y de la que se pueden
heredar sus mtodos empleando la funcin inherits() del mdulo utils. Esta
parte no hace uso del bucle de eventos, y se produce a nivel de cdigo JavaScript
[32].
33

Bsicamente, al proporcionar a una clase mtodos para la gestin de eventos, se


facilita que:
la clase d la oportunidad a otras clases de escuchar sus eventos subscribindose a ellos a travs de mtodos como on() o addListener(), en
desuso a favor del anterior. Se puede hacer incluso que un subscriptor escuche un evento una sola vez con once(). Es importante indicar que, para
evitar posibles prdidas de memoria, la clase avisa por consola de que se
ha superado el lmite de 10 subscriptores, aunque permite tenerlos. Sin embargo, como no todas las clases pueden tener esa limitacin, se ofrece la
posibilidad de aumentarlo siempre que, explcitamente, se fije con una llamada a setMaxListeners()
la clase d la oportunidad a sus subscriptores de dejar de atender a los
eventos que escuchan con removeListener(). Incluso se pueden eliminar
todos con removeAllListeners()
por supuesto, la clase disponga de un mecanismo para emitir los eventos cuando el programador lo crea conveniente. Este mtodo es la funcin
emit() a la que se le indica qu tipo de evento se genera. emit() hace uso
del objeto _events, propiedad de EventEmitter, en el que cada propiedad
tiene el nombre de un evento y el valor asociado a stas es un array de
callbacks instalados por los subscriptores. Por ejemplo:
_events = {
"connect":

[function(){}, function(){}],

"customEvent":

[function(){}, function(){}],

//si solo hay un subscriptor no se mete en un array


"newListener":

function(){}

2.5.4.

Postponiendo la ejecucin de funciones

Adems de todo lo comentado se dispone de una ltima forma de interactuar


con el bucle de eventos. Se puede programar la ejecucin de una funcin en la siguiente iteracin del bucle pasndosela como argumento a la funcin nextTick()
del objeto process, que es el mecanismo que Node proporciona con tal propsito.
34

process.nextTick() se define en el proceso de bootstrap de node.js 18 . A travs


de ella se podrn encolar una o ms funciones en nextTickQueue, un array no
accesible a travs del scope porque est protegido19 mediante un cierre, o closure,
al ser una variable local. El contenido de esta cola se ejecuta en la siguiente
iteracin del bucle de eventos proporcionado por libuv. El funcionamiento interno
de este proceso se describe a continuacin:
1. nextTick() encola el callback en nextTickQueue y notifica a Node que hay
tareas

pendientes

para

la

siguiente

iteracin

llamando

_needTickCallback() del objeto process. Esta funcin es la nica que


no se asigna durante el bootstrap sino en el arranque de la plataforma20 .
2. el bucle de eventos, al que, en el arranque de Node en src/node.cc, se ha
programado para que cada una de sus iteraciones ejecute la funcin nativa
Tick()21 , comprueba el flag de callbacks pendientes need_tick_cb y si los
hay, invoca a la funcin JavaScript _tickCallback() de la variable global
process definida en src/node.js.
3. _tickCallback() va ejecutando las funciones encoladas en nextTickQueue
conforme las va extrayendo de la cola.

2.6.

Es ligero y eficiente [1]

Node es una fina capa de software entre el sistema operativo y la aplicacin escrita
para la plataforma porque los objetivos que se han perseguido con su arquitectura
son la velocidad y la eficiencia.
Centrados en esos propsitos, se desecha emplear un modelo multithread para manejar las conexiones pues el coste en trminos de tiempo para crearlos y
memoria consumida por cada uno de ellos es elevado. Se busca entonces una
solucin de alto rendimiento que se caracterizar por cumplir las condiciones
generales para este tipo de aplicaciones: realizar operaciones de Entrada/Salida
no bloqueantes delegando en el sistema operativo (a travs de kqueue, epoll...) y
coordinarlo todo a travs de uno o varios bucles de eventos. La finalidad es multi18

node.js https://github.com/joyent/node/blob/v0.8.20-release/src/node.js#L254
node.js https://github.com/joyent/node/blob/v0.8.20-release/src/node.js#L230
20
node.cc https://github.com/joyent/node/blob/v0.8.20-release/src/node.cc#L2303
21
node.cc https://github.com/joyent/node/blob/v0.8.20-release/src/node.cc#L260
19

35

plexar la mayor cantidad posible de operaciones de Entrada/Salida en un mismo


thread en lugar de una por thread. Como ejemplos de alternativas que cumplen
estas condiciones y pueden ser tomadas como referencia estn los green threads
y los procesos del lenguaje Erlang.
Finalmente el modelo escrito en C en que se basa la solucin adoptada por Node, como ya se ha desgranado, es un thread que ejecuta un bucle de eventos
con Entrada/Salida no bloqueante capaz de manejar alrededor de 20000 flujos
Entrada/Salida.

2.7.

Perfecto

para

aplicaciones

en

tiempo

real

data-intensive
2.7.1.

Tiempo real y Node

El modelo de concurrencia de Node encaja con los requisitos que se exigen a las
aplicaciones en tiempo real flexible (soft real-time). Un sistema de tiempo real es
un sistema informtico que interacciona repetidamente con su entorno fsico y
que responde a los estmulos que recibe del mismo dentro de un plazo de tiempo
determinado [33]. Hay principalmente dos tipos de estos sistemas:
sistemas de tiempo real flexible, entendindose por stos aquellos en los que
las restricciones/condiciones de latencia son flexibles: se pueden perder plazos, es decir, la respuesta al estmulo no cumple las condiciones de tiempo
impuestas, y adems el valor de la respuesta decrece con el tiempo pero no
acarrea un desenlace fatal
sistemas de tiempo real estricto, en contraposicin a los anteriores, donde
todas las acciones deben terminar en el plazo especificado y cualquier incumplimiento de las condiciones de retardo puede desembocar en un fallo
total del sistema [34].
Un pequeo apunte sobre el tiempo real y el recolector de basura de V8, tal y
como se recuerda en [35]: el motor de Google puede modificar la respuesta de un
programa, por ejemplo un servidor, a los eventos que se generan, ya que, como
se ha sealado anteriormente, este recolector es stop-the-world, con lo que se
detendr la ejecucin del programa hasta que se realice el ciclo de recoleccin.
36

No obstante, un ciclo de recoleccin no comienza aleatoriamente: es Node quien


avisa a V8 de que puede entrar en accin.
Cuando se inicia la plataforma, en la preparacin del bucle de eventos, se programa un chequeo para cada iteracin del bucle, mediante node::Check(), que
har que se ejecute el recolector, mediante node::Idle(), si el bucle no tiene
ningn evento mejor que procesar. La condicin para ello es que la diferencia de
tiempos en las ltimas iteraciones del bucle sea mayor de 0.7 segundos, si bien
es cierto que hay un temporizador, que se ejecuta cada 5 segundos, que realiza
tambin chequeos de memoria y tiempos de inactividad con el mismo propsito.
No obstante, estos detalles de implementacin estn sujetos a cambios, como se
puede apreciar en la versin de src/node.cc de la rama inestable 0.9.

2.7.2.

Para qu es til Node entonces?

Node es til por tanto para aplicaciones no crticas que admitan un cierto retardo.
Adems se debe tener en cuenta su capacidad de manejar un alto nmero de
conexiones y procesar un enorme nmero de operaciones de Entrada/Salida muy
rpidamente. ste es un requisito especialmente importante para las aplicaciones
que hacen un uso intensivo de datos (data-intensive applications) porque emplean
la mayor parte del tiempo en realizar este tipo de operaciones. Se puede afirmar
por tanto que Node encaja de manera excelente si se quiere [35]:
Interfaces ligeros REST/JSON : el modelo de Entrada/Salida no bloqueante,
para atender las peticiones REST, en combinacin con JavaScript, para el
soporte nativo JSON, lo hacen ptimo como capa superior de fuentes de
datos como bases de datos u otros servicios web.
Aplicaciones monopgina: las aplicaciones monopgina son aquellas que se
presentan en una nica pgina web, emulando a las aplicaciones de escritorio. La interaccin del usuario con el servidor a travs de la interfaz de
la aplicacin se realiza mediante peticiones AJAX, en lugar de recargar la
pgina entera. La interfaz se actualizar de acuerdo a dicha interaccin, sin
abandonar la pgina. El uso de AJAX puede propiciar una avalancha de
peticiones que el servidor debe ser capaz de procesar. Es aqu donde Node
entra en accin.
Se debe destacar la ventaja de compartir cdigo, por ejemplo de validacin,
37

entre el cliente y el servidor.


Reutilizar herramientas de Unix: La capacidad de Node de lanzar miles de
procesos hijos que ejecuten comandos y tratar su salida como streams permiten utilizar la plataforma para reutilizar el software existente en lugar de
reinventarlo para ella.
Datos por streaming: al tratar las conexiones HTTP como streams, se pueden
procesar ficheros al vuelo, conforme se envan o reciben.
Comunicacin: por las caractersticas comentadas, aplicaciones de mensajera instantnea o web en tiempo real, e incluso, juegos multijugador.

38

Captulo 3

Mdulos Buffer y Dgram


3.1.

Aspectos de UDP relevantes para Node

Las siglas UDP (User Datagram Protocol) se refieren a un sencillo protocolo que
pertenece al conjunto de protocolos de Internet. Se sita en el nivel de transporte,
por encima del nivel de red emplendose por ello en comunicaciones punto a
punto en redes de conmutacin de paquetes basadas en IP. En estas redes el
paquete se denomina datagrama y se encamina sin que exista un circuito virtual
establecido sino que se hace en base a las direcciones que dicho datagrama lleva
en su cabecera.
Tiene una importante caracterstica: es no fiable. Esto significa que no garantiza
ni el orden (no hay control de flujo), ni la entrega (confirmacin o ack) y ni siquiera
que los datagramas no lleguen duplicados. Es por ello que se dice que el protocolo
es no orientado a conexin sino a transaccin. Si se desea que se cumplan uno o
varios de estos puntos, se debe proveer los mecanismos en una capa superior, lo
que en la prctica significa que debe implementarlos el programador: UDP simplemente proporciona una manera inmediata de intercambiar datagramas.
Es un protocolo extremadamente sencillo lo que en la prctica significa que es
muy ligero, es decir, apenas aade bytes adicionales a los que se quieren enviar y
evita la necesidad de una fase de inicializacin de la conexin. Consecuentemente
se dispondr de muy pocos parmetros que se puedan ajustar a la hora de realizar
comunicaciones con l.
La cabecera tiene cuatro campos de 16 bits cada uno (2 octetos):
39

puerto de origen (opcional)


puerto de destino
longitud del datagrama completo (cabecera y datos): indica el nmero de
octetos total de la cabecera ms los datos. Su mnimo valor es 8 por tanto
(cuando slo se computa la cabecera) y deja para los datos un espacio de
aproximadamente 64K octetos.
En la prctica los datagramas no son tan grandes. Existe una limitacin
impuesta por el parmetro MTU del nivel de red. MTU (Maximum Transfer
Unit) indica el mximo nmero de octetos que puede tener un datagrama
contando las cabeceras. Se tiende por tanto a que los datagramas que se
generan se ajusten a dicha MTU. Cabe la posibilidad de que el nivel IP fragmente los que excedan la MTU, pero en la prctica no se hace puesto que
ensamblarlos requiere de un mecanismo que verifique prdidas de datagramas completos y orden de segmentos, y dicho mecanismo correspondera a
un nivel superior [36].
checksum (opcional), para verificar la integridad de los datos.
El resto del datagrama lo compone el mensaje, en forma binaria, que, junto con el
puerto de destino, es la informacin indispensable que necesita este nivel.
El conjunto de aplicaciones basadas en UDP es, en principio, bastante limitado.
La RFC de 1980 hace referencia a dos: al Servicio de Nombres de Internet (DNS,
Internet Name Server) y al Transferencia de Archivos Trivial (TFPT, Trivial File
Transfer). Posteriormente y con la aparicin de tecnologas de audio y vdeo en
tiempo real, donde los requisitos de rapidez y bajo retardo se imponen a los de
fiabilidad y orden en la entrega, cosa que ajusta muy bien a lo que ofrece UDP,
lo han hecho candidato ideal como base del Protocolo de Tiempo Real (RTP, Real
Time Protocol).
ntimamente relacionado con RTP, y la distribucin de contenido multimedia en
general, aparte de, por supuesto, otro tipo de aplicaciones, estn los conceptos
de difusin, tanto broadcast como multicast. Ambos son formas de distribucin
de datos punto-multipunto o multipunto-multipunto, que es justamente lo que
puede exigirse a un servicio de distribucin de audio/vdeo. Esta difusin, sin
embargo, no se realiza a nivel de transporte, sino que se hace a nivel de red,
mediante el protocolo IP y las posibilidades de direccionamiento que ofrece.
40

Una de las maneras de enviar trfico a los clientes es el denominado Broadcasting.


Con este tipo de direccionamiento, el servidor enva datos a todos los clientes de
la red y ellos son los que deciden si estos datos son de su inters o no. Como es
lgico no se envan cliente por cliente, ya que supone un desperdicio de ancho de
banda, sino a la direccin de broadcast de la red.
Como se sabe, en una red hay dos direcciones especiales: aquella en la que los
bits del sufijo son todo ceros, que identifica a la red, y aquella en la que los bits
del sufijo son todo unos. sta ltima es la direccin de broadcast y todos los
interfaces, aparte de escuchar la direccin IP que tienen configurada, escuchan
el trfico dirigido a esta otra IP.
El otro modo de envo de trfico a mltiples puntos es el Multicasting. Se emplea
cuando el nmero de nodos interesados en unos datos determinados es lo suficientemente grande como para no enviarlos uno a uno, pero lo suficientemente
pequeo como para que no haya necesidad de hacer broadcast. Como en el caso
anterior, tambin se emplean direcciones IP especiales.
En multicasting se trabaja con direcciones IP de un rango muy concreto, el que
va de 224.0.0.0 a 239.255.255.255. Sin embargo, IANA (el organismo que controla la asignacin de estas direcciones), es muy restrictivo con ellas y otorga
generalmente direcciones de los rangos 224.0.0.0 - 224.0.0.255, 224.0.1.0 224.0.1.255 y 224.0.2.0 - 224.0.2.255 [37]. Cada una de las direcciones de
estos rangos representa un conjunto de mquinas a las que los routers dirigen el
trfico. Se dice que estas mquinas estn suscritas al grupo multicast, por tanto
debe existir la manera de indicarle a la red la suscripcin y el abandono de un
grupo.

3.2.

UDP en Node

Una aplicacin para Node puede hacer uso de UDP importando la librera dgram:
var dgram = require(dgram);
La manera de obtener un socket UDP es sencilla, tanto para cliente como para
servidor, es invocando el mtodo:
dgram.createSocket(tipo, [function(mensaje, rinfo)])
al que se pasa como parmetro el tipo de soporte IP: udp4 para IPv4 y udp6
41

para IPv6. Opcionalmente y para sockets servidor, la funcin de callback ser


la que atienda los mensajes que lleguen de los clientes instalndose como
listener del evento message, por lo que puede hacerse ms adelante.
Como librera que implementa un protocolo asociado al nivel de transporte, ofrece
mtodos que cubren las primitivas de dicho nivel:
dgram.send(mensaje, offset, longitud, puerto, direccion,
[function(error, bytes){ }])
Enva un datagrama que contiene un mensaje de tipo Buffer de longitud
determinada a un puerto de una direccion IP. Si slo se enva una parte del Buffer, offset indicar en qu posicin empiezan los datos a enviar
dentro de l, siendo 0 en el caso de enviarse entero.
dgram.bind(puerto, [direccion])
Permite escuchar en un puerto la llegada de datagramas. En mquinas con
varios interfaces, se escuchar en todos excepto si se especifica uno concreto
pasando como argumento su direccion IP.
Si la llamada a esta funcin tiene xito, se emite el evento listening.
socket.on(listening, function(){

});

dgram.close()
Deja de escuchar en el socket y lo cierra, emitiendo el evento close. A
partir de que se emita este evento no se generarn ms eventos message.
socket.on(close, function(){ ... });
Para la recepcin de datos se emplea un callback que atiende al evento message,
emitido cuando se recibe un datagrama cuyo payload, msg de tipo Buffer, e informacin del origen, rinfo, se pasan como argumentos a dicho callback:
socket.on(message, function(msg, rinfo){

});

Adicionalmente, rinfo indica el nmero de bytes del datagrama en la propiedad


rinfo.size. Por ejemplo:
rinfo = {
address: 192.168.21.7,
family: IPv4,
port: 50251,
42

size: 436 }
Junto con los tres eventos vistos (listening, message, close) se encuentra error, que se emite slo cuando ocurre un error en el socket:
socket.on(error, function(){

});

Aparte de los mtodos anteriores est disponible una funcin auxiliar con la que
obtener informacin (direccin, puerto y protocolo) del socket que la mquina est
empleando en la comunicacin:
dgram.address()
devuelve el objeto en notacin JSON, por ejemplo:
{ address: 0.0.0.0,
family: IPv4,
port: 23456 }
Ms all del nivel de transporte, Node tambin permite controlar ciertos parmetros del nivel de red, justamente aquellos relacionados con la difusin de la que
antes se ha comentado.
El nico parmetro ms general que se puede ajustar es el TTL (Time To Live).
Este tiempo de vida es un entero entre 1 y 255 (8 bits) que marca el nmero mximo de saltos que un paquete IP puede dar entre redes antes de ser descartado.
Tpicamente es 64 y se fija con:
dgram.setTTL(saltos);
Como se ha introducido al principio del tema, UDP est ntimamente relacionado
con la difusin a grupos broadcast y multicast. Mientras que habilitar o deshabilitar el envo a los primeros es tan sencillo como invocar:
dgram.setBroadcast(flag);
para los grupos multicast hay un completo grupo de funciones:
dgram.addMembership(direccionMulticast, [interfaz])
es el mecanismo que el mdulo ofrece para la subscripcin de la interfaz
de la mquina a un grupo de multicast identificado con la direccin IP
direccionMulticast. Si no se especifica la interfaz, se aadirn todos
los posibles.
43

dgram.dropMembership(direccionMulticast, [interfaz])
realiza la accin opuesta al mtodo anterior, sacando a la interfaz del grupo multicast de direccin IP direccionMulticast. Al igual que con
dgram.addMembership(), si no se especifica interfaz alguna, se sacan
del grupo todos los que pertenezcan a la mquina.
dgram.setMulticastLoopback(flag)
habilita o deshabilita a la interfaz local para que tambin reciba o deje de
recibir los paquetes multicast que se envan desde el mismo.
dgram.setMulticastTTL(saltos)
establece, de manera idntica a dgram.setTTL(), el nmero de saltos que
un datagrama multicast puede dar en una red.

3.3.

Codificacin de caracteres

El propsito final de cualquier protocolo de comunicacin, por tanto tambin de


UDP, es la de transmitir datos entre dos o ms puntos. Estos datos son datos
binarios que pueden representar texto, audio, vdeo, etc...
Cuando se trabaja con texto, o sea, cadenas de caracteres, debe existir una forma
de hacer legible el conjunto de bits sin formato que existe en memoria agrupndolos de manera que se transformen en texto reconocible, con o sin sentido,
por el ser humano. Este proceso se conoce como Codificacin de caracteres. En
l se establece una correspondencia unvoca, llamada esquema de codificacin,
entre secuencias de bits y caracteres correspondientes a algn alfabeto concreto
definido por su "juego de caracteres"(character set).
Existe un amplsimo nmero de esquemas de codificacin, muchos con propsito
de mejorar y ampliar los anteriores, de tal manera que una misma secuencia de
bits determinada puede representarse de distintas maneras segn el juego de
caracteres que se elija.
Los ms habituales son:
ASCII
[38] este esquema est diseado para la representacin de texto usando el
alfabeto ingls. Empleando 7 bits para ello, por lo que define 128 caracteres,
incluyendo alfanumricos, smbolos y 33 caracteres de control, como por
44

ejemplo NULL o CR (retorno de carro). Hacer notar que un byte consta de 8


bits, de tal manera que si cada carcter se codifica con uno, todava quedan
128 caracteres por definir. Esto se ha empleado habitualmente para definir
ampliaciones del cdigo, existiendo multitud de ellas, incompatibles entre s
puesto que slo tienen en comn los 128 primeros caracteres.
Unicode
[39] es la solucin propuesta a los problemas aparecidos por el gran nmero
de codificaciones. Pretende emplear un slo cdigo para todos los alfabetos
posibles, siendo capaz de representar ms de un milln de caracteres. Introduce los llamados puntos de cdigo (codepoints) que son, por decirlo de
alguna manera, un identificador de carcter. As, a una A le corresponde
un codepoint, a una B otro codepoint...Los codepoints se distinguen por ser
un nmero hexadecimal que comienza por U+. Para el caso anterior, a la
A le corresponde el codepoint U+0041, a la B el U+0042...JavaScript es
un lenguaje de programacin pensado para un Entrada/Salida formateada
como texto. Y ste codificado en Unicode.
Son varias las maneras en que Unicode realiza las codificaciones de los codepoints. La primera de ellas es la ms conocida y extendida en uso: UTF-8. Es
una codificacin multibyte: los primeros 128 codepoints se almacenan con
un solo byte, y los siguientes se van almacenando con dos, tres o hasta seis
bytes.
Otra de ellas, la tradicional, es la UTF-16 o UCS-2, tanto Big como Little
Endian. Emplea 2 bytes (o 16 bits) para codificar cada codepoint. El resto de
codificaciones no son tan relevantes para la comprensin del API de Node,
por lo que se dejan en el aire en este captulo.
Al margen de la codificacin pensada para el alfabeto humano, existen esquemas
de codificacin que no establecen una correspondencia entre bits y un alfabeto
humano determinado sino entre bits y un subconjunto de ese alfabeto. Por ejemplo, para transformar conjuntos de octetos en conjuntos de caracteres de texto
imprimibles. Existe un caso concreto, Base64, en que los bytes se transforman
en el rango de 64 caracteres compuesto por A-Z, a-z y 0-9 ms los smbolos
+ y /. Esta codificacin se suele usar para adaptar texto en otros lenguajes o
contenido binario (audio, video, ejecutables...) a protocolos que slo admiten una
codificacin muy concreta y ms restringida. Por ejemplo, para enviar un correo
electrnico con el protocolo SMTP (descrito en la RFC821) hay definido un formato
45

de mensaje cuya codificacin debe ser ASCII (segn la RFC822) [40]. Si se adjunta
contenido multimedia o texto en otro lenguaje con smbolos que no pueden representarse en esa codificacin, como puede ser un vdeo, habra problemas para su
envo o recepcin. En este caso concreto, se introdujo un mecanismo, MIME [41],
que utiliza Base64 para codificar el contenido de los mensajes de correo y hacer
posible su correcto intercambio.
El proceso de codificacin en Base64 consiste, a grandes rasgos, en coger los bytes de tres en tres, 24 bits en total. Agrupndolos de seis en seis bits, se obtienen
cuatro ndices, comprendidos entre 0 y 63 (26 = 64), con los que se indexa la cadena

compuesta

por

todos

los

rangos

de

caracteres

ABCDEFGHIJKLMOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
de donde se obtiene el carcter buscado. Hay que observar que cada uno de estos
caracteres es de 8 bits obtenido a partir de cuatro grupos de 6 bits. Por tanto,
cada tres bytes, se generan cuatro.
Otro de los casos es la representacin el Hexadecimal: representa los dgitos binarios en notacin hexadecimal. Puesto que el sistema de numeracin en base
16 emplea los nmeros 0-9 y las letras A-F cada octeto estar representado por
combinaciones de dos de estos caracteres.

3.4.

Buffers en Javascript

Hay ocasiones en las que la entrada o salida del sistema no es texto sino datos
binarios como, por ejemplo, los que se reciben en las conexiones UDP o TCP si
se est trabajando con redes, o los que se reciben a raz de la lectura de un
fichero, si se trabaja con el sistema de ficheros de la mquina donde se ejecuta
el programa. En estos casos se necesita tratar con los octetos en bruto, tal y
como estn en memoria, accediendo a ellos y transformndolos o convirtindolos
en determinados tipos de datos segn sea necesario durante la ejecucin del
programa.
Con este propsito existe el objeto Buffer: tener una manera eficiente de crear,
manipular y consumir bloques de octetos. A bajo nivel, el API de Node habla de
que un objeto Buffer contiene una zona de memoria tal cual est alojada fuera
del heap del motor V8. Puede verse como un array de bytes con tamao fijo que
no puede redimensionarse.
46

El mdulo buffer, que es el que provee el objeto Buffer, est presente en el espacio
global de ejecucin del script src/node.js, con lo cual no es necesario importarlo
explcitamente con require(). Directamente, se puede instanciar un Buffer de
tres maneras:
var buf = new Buffer(size)
Con size se especifica un tamao en octetos fijo, que luego no podr modificarse.
var buf = new Buffer(array)
Se crea un Buffer a partir de un array de octetos.
var buf = new Buffer(str, [codificacion])
Crea un Buffer que contiene el String str indicando opcionalmente su
codificacion. Las codificaciones vlidas son: ascii, utf8 (la que se emplea por defecto), utf16, base64 o hexadecimal.
A partir de este punto tenemos un Buffer, buf, del que conocemos su tamao
con buf.length, sobre el que se pueden realizar operaciones de escritura y lectura.
Para escribir en un Buffer se puede optar por:
una indexacin directa, como con los arrays, buf[indice]. De esta manera
se accede a la posicin indice cuyo octeto se puede fijar con un valor que,
al tener 8 bits, vara entre 0 y 255.
el mtodo buf.write(string, [inicio], [longitud], [codificacion])
que permite escribir longitud bytes a partir de la posicin inicio del Buffer
la cadena de caracteres string codificada segn codificacion (por defecto
utf8). Devuelve el nmero de octetos escritos.
Se debe tener en consideracin que la longitud de la cadena puede no coincidir con el nmero de bytes escritos puesto que stos dependen de la codificacin empleada. Por ejemplo, hay caracteres en utf8 que ocupan varios
bytes como ya se sabe y se comprueba fcilmente:
var bufferedData = new Buffer("Lingstica");
console.log("String:", data.length, "bytes");
console.log("Buffer:", bufferedData.length, "bytes");
47

Tambin se debe contemplar tambin el caso que no haya suficiente espacio


en el Buffer, con lo que slo se escribirn los bytes que quepan, sin escribir
parcialmente un carcter. Esto es, si queda una posicin libre de un byte y
se pretende introducir un carcter Unicode, que ocupa dos, no ser posible.
introducir el contenido numricamente, no como caracteres. As se pueden
escribir:
enteros de 8 bits con y sin signo, cuyos rangos vlidos son, -128 a 127
y 0 a 255, respectivamente, que ocuparn una posicin del Buffer
enteros de 16 bits con y sin signo, cuyos rangos vlidos son, -32768 a
32767 y 0 a 65535, respectivamente, que ocuparn dos posiciones del
Buffer
enteros de 32 bits con y sin signo, cuyos rangos vlidos son, -21474883648
a 21474883647 y 0 a 4294967295, respectivamente, que ocuparn cuatro posiciones del Buffer
float de 32 bits
double de 64 bits
Adems con los nmeros de ms de un byte se puede elegir el formato en el
que se almacenan en el Buffer: bien Big Endian, BE, o bien Little Endian, LE.
La diferencia entre ambos est en el orden en que se guardan en memoria.
Big Endian asigna los bytes ms significativos a los ndices ms bajos del
Buffer y, por el contrario, Little Endian asigna el byte menos significativo a
los ndices ms bajos. Sirva de ejemplo, el entero de 32 bits 0xEF4A71B8 se
escribira en el Buffer, representado aqu por [], como [EF, 4A, 71, B8]
en Big Endian y como [B8, 71, 4A, EF] en Little Endian.
El motivo de estos dos formatos es histrico: cada arquitectura de microprocesador emplea una u otra (algunas las dos). Por ejemplo, Sparc utiliza Big
Endian y los procesadores de Intel, Little Endian.
Teniendo en cuenta todo esto, la clase Buffer proporciona 14 mtodos para
la escritura en formato numrico cuya signature se determina como:
write + [U] + [Int | Float | Double] + [8 | 16 | 32] + [BE | LE]
(value, [offset], [noAssert])
48

Las combinaciones posibles deben ser lgicas y de acuerdo a lo explicado antes, por ejemplo, son vlidas buf.writeUInt16LE() o buf.writeFloatBE()
y no son vlidas, por ejemplo, buf.writeFloat32LE(), porque Float siempre es de 32 bits y por tanto no se especificara, o buf.writeUDoubleBE(),
porque Double no es un tipo que se pueda escoger con signo o sin l. Los
correctos seran, buf.writeFloatLE() y buf.writeDoubleBE().
Por otra parte, para operaciones de lectura, se puede acceder al contenido de un
Buffer de varias maneras, dependiendo del tipo de dato que se quiera leer y de
cmo se quiera hacer esta lectura.
de nuevo, a travs de indexacin directa, buf[indice] que devuelve el octeto
de la posicin indice cuyo valor est comprendido entre 0 y 255.
obteniendo la cadena de caracteres que est representada en el Buffer con
buf.toString([codificacion], [inicio], [fin]). Se puede especificar, de manera opcional, qu codificacion debe tener la cadena, y las
posiciones de inicio y fin dentro del Buffer. Por defecto, se obtendr una
String en utf8 de todo el Buffer (inicio 0 y fin buf.length).
leyendo el contenido numricamente mediante los mtodos duales a los que
se emplean para operaciones de escritura. Si en estas operaciones el nombre
del mtodo empezaba por write, ahora comienza por read.
La clase Buffer adems ofrece operaciones similares a las que se pueden realizar
con un array:
buf.copy(bufferDestino, [inicioDestino], [inicioOrigen],
[finalOrigen])
como su nombre indica, copia en el bufferDestino a partir de la posicin inicioDestino, el contenido del Buffer origen, buf, desde la posicin
inicioOrigen hasta la posicin finalOrigen. Por defecto se copia todo el
Buffer buf en bufferDestino y en caso de que se intente copiar ms cantidad de la que cabe a partir de donde se empiece en destino, slo se copiarn
las posiciones que quepan. Hay que tener en cuenta que un carcter Unicode
puede ocupar ms de una posicin, con lo que quedar ilegible si se copia
parcialmente.
buf.slice([inicio], [final])
es una funcin que devuelve un Buffer cuyo contenido es el mismo que el
49

contenido del Buffer buf entre las posiciones inicio y final pero, muy
importante, porque hace referencia a l. Esto significa que si se modifica
cualquier octeto en alguna posicin de ese rango en alguno de los dos Buffers, el cambio se refleja en ambos.
buf.fill(valor, [inicio], [final])
asigna el valor deseado a todas las posiciones del Buffer buf comprendidas
entre inicio y final con un valor. Por defecto, se escribe todo el Buffer.
Buffer.concat(lista, [longitudTotal])
concatena todos los Buffers contenidos en el array lista devolviendo un
nuevo Buffer, siempre que lista tenga ms de uno o no est vaca, porque
si no, devuelve el Buffer o no devuelve nada respectivamente.
Es una funcin muy lenta, que supone un cuello de botella importante en
una aplicacin, por lo que se debe utilizar slo si es estrictamente necesario. Una de las maneras para intentar aumentar el rendimiento es pasando
el argumento longitudTotal, que es la longitud que debe tener el Buffer
resultante.

3.5.

Aplicacin con Buffers y UDP

Como ejemplo del tipo de aplicaciones que se pueden crear con UDP se va a implementar una versin muy sencilla del protocolo RTP. Se emplear para desarrollar
una aplicacin que emitir en tiempo real los archivos de audio MP3 alojados en
una mquina remota.

3.5.1.

Descripcin del problema

La aplicacin que se pretende realizar es un servidor en tiempo real muy bsico


de archivos de audio MP3, utilizando RTP. Estos archivos estn contenidos en un
directorio del servidor y se van reproduciendo en orden secuencial. El destinatario
es un grupo de multicast definido por la direccin IP 224.0.0.14, escogida arbitrariamente de entre las posibles, a la que puede unirse como cliente cualquier
aplicacin de audio con soporte para ello. En concreto, para el desarrollo de la
prctica se ha empleado VLC1 .
1

http://www.videolan.org/vlc/

50

Se puede configurar de la siguiente manera para escuchar las canciones que


emite la aplicacin:

1. Ejecutar el Reproductor y en la pantalla principal ir a Media

Figura 3.1: Reproductor VLC, Men Media

2. Seleccionar Open Network Stream para recibir contenido emitido desde la


Red

Figura 3.2: Reproductor VLC, Open Network Stream

3. Configurar la IP del grupo multicast donde se va a recibir la transmisin y


en qu puerto. En este caso rtp://224.0.0.14:5000, ambos escogidos de
manera arbitraria pero coherente con las especificaciones.
51

Figura 3.3: Reproductor VLC, configuracin de la IP

3.5.2.
3.5.2.1.

Diseo propuesto
El protocolo RTP

RTP son las siglas de Real Time Protocol, un protocolo definido para el envo de
contenido multimedia en tiempo real sobre UDP. La versin definitiva de la especificacin completa se encuentra en la RFC3350 [42]. Slo se implementar una
pequea parte de ella, que proporcione la suficiente funcionalidad para el propsito de la aplicacin. Esto incluye, bsicamente, la generacin correcta de la
cabecera RTP de los paquetes.
El mnimo contenido funcional de la cabecera de un paquete RTP consta de los
siguientes campos, explicados segn la RFC propone:
versin: 2 bits que almacenan el nmero de versin del protocolo, que va por
la segunda, por lo que el valor ser siempre 2.
padding: flag que indica la presencia de relleno al final del paquete. Suele
ser comn en aplicaciones que necesitan un tamao fijo de paquete. No es
el caso de esta aplicacin, por lo que su valor puede quedar a false.
extension: flag que indica que la cabecera se extiende con una cabecera de
ampliacin que no se va a usar en esta aplicacin.
52

contador CSRC: la cabecera RTP puede llevar a continuacin una lista de


fuentes que contribuyen a la generacin del contenido del paquete. Este
campo de 4 bits indica cuntas van en la lista anexa, pero slo lo insertan
los mezcladores, por lo que carece de utilidad en una aplicacin de este tipo.
marker bit: flag cuyo significado depende del contenido que se est transmitiendo. El propsito general es destacar puntos concretos en la transmisin,
como sucede cuando para transmitir un frame se emplean varios paquetes.
En este caso el marker bit activado indica que se ha transmitido el ltimo
paquete de los que componen el frame.
tipo de contenido: 7 bits que identifican numricamente el tipo de contenido
que va en el paquete. En este caso, formalmente, es audio MPEG, de sus
siglas MPA, que engloba MPEG-1 (el tipo de audio empleado en esta aplicacin) y MPEG-2. La RFC3350 remite a la RFC3551 [43, Section 6] para
obtener detalles sobre las caractersticas de todos los formatos y el valor del
tipo de contenido, en este caso 14.
nmero de secuencia: este campo de 16 bits tiene como funcin la de indicar el orden de los paquetes en la secuencia, til para poder ordenarlos y
detectar prdidas. Su valor inicial debe ser aleatorio e impredecible.
timestamp: 32 bits cuyo valor indica el instante de muestreo del primer octeto del contenido del paquete. El clculo de este valor depende del tipo de
contenido. En el caso de audio MPEG, MPA, la RFC3351 marca que la tasa de
reloj sea siempre de 90000 Hz, independientemente de la tasa de muestreo
del audio (44100 Hz para esta aplicacin).
SSRC: identifica con 32 bits la fuente, nico para cada fuente distinta en
una sesin. Esta aplicacin dispone de una sola fuente, por lo que su valor
puede ser establecido sin necesidad de tener en cuenta la probabilidad de
colisin con los valores de otras fuentes.
Adems de esto, enviar audio en formato MP3 requiere una cabecera adicional
especfica, tal y como la RFC2250 explica [44, Section 3.5], con dos campos:
MBZ: 16 bits que no tienen un uso definido y por tanto, no se van a emplear.
Fragment Offset: 16 bits que marcan el inicio del frame de audio dentro de
los datos del paquete.
53

Todo esto se implementa en el mdulo RTPProtocol.js. Este mdulo ofrece la clase


RTPProtocol cuya misin es simplemente encapsular un frame multimedia, en
este caso audio MP3 de unas caractersticas concretas, en un paquete RTP con la
cabecera que corresponda.

3.5.2.2.

Descripcin de la solucin

El resto de piezas de la arquitectura se relacionan como se representan en el


siguiente diagrama donde se asocian las clases y los eventos que emiten con
las acciones que se solicitan al resto de las clases:

Figura 3.4: Diagrama de Colaboracin de la Solucin RTP

Se puede comenzar a explicar este diagrama a partir de la clase MP3Library,


que representa una biblioteca de archivos .mp3. Su funcin es la de buscar los
archivos de audio del directorio ./songs/, dividirlos en sus correspondientes frames y almacenarlos en memoria. Pone a disposicin del programador mtodos
para acceder a estos archivos frame a frame, para que un emisor, como es por
ejemplo la clase MP3Source, los emita cuando corresponda. Lo nico relevante
de MP3Library para el propsito de la prctica es que emite el evento ready
cuando ha ledo y procesado todos los archivos .mp3 y por tanto est lista para
ofrecerlos a las fuentes MP3, MP3Source.
54

La clase MP3Source modela el comportamiento de una fuente de audio MP3 que


a partir de un archivo genera frames con una frecuencia tal que simula que se
estn emitiendo en tiempo real. En la aplicacin que se est desarrollando como
ejemplo, slo se tratar con archivos .mp3 cuyas caractersticas son que estn
muestreados a una frecuencia de 44100 Hz y codificados con una velocidad de
128 Kbps. Cualquier otro .mp3 con distintas caractersticas producir un malfuncionamiento de la aplicacin. Estos ficheros .mp3 se obtienen de la librera
Mp3Library:
var library = new mediaLibraries.Mp3Library;
library.on("ready", function(){
var mp3source = new sources.Mp3Source(library);
// ...
});
De MP3Source interesa bsicamente lo que se ha comentado: que emite el evento
frame a la frecuencia que se emitira un frame si fuera una fuente autntica de
audio en tiempo real. Un clculo rpido: el estndar MP3 [45] fija por definicin
que un frame consta de 1152 muestras, y las caractersticas que se han elegido
para los archivos MP3 determinan que la frecuencia de muestreo sea de 44100
Hertzios. Lo ltimo significa que cada segundo se generan 44100 muestras de tal
manera que:
1 segundos
mseg
1152 muestras
f rame = 26,12 f rame
44100 muestras

(3.1)

O sea, la clase MP3Source emitir un evento frame cada 26 milisegundos. Junto con l, se enviar el frame para su procesado por parte de los callbacks para
dicho evento.
El tamao del frame lo determina la segunda condicin impuesta a los ficheros
de audio, 128000 bits por segundo:
128000 bytes
bytes
0,02612 segundos
f rame = 417,959 f rame
8 segundo

(3.2)

Es decir, habr frames de 417 bytes, y frames de 418 bytes.


No se entrar en ms detalles de implementacin de estas clases, porque supondra diseccionar casi entero el formato MP3 del que slo interesa que un archivo
de este tipo se divide en frames.
55

Los frames que la fuente MP3 emite deben ser encapsulados en un paquete RTP
con su cabecera perfectamente formada antes de ser enviados al grupo multicast.
Por este motivo se har que el objeto RTPProtocol atienda los eventos frame
que emite MP3Source.
mp3source.on(frame, function(frame){
rtpserver.pack(frame);
});
Cuando a travs de estos eventos RTPProtocol recibe el frame MP3, le aade las
cabeceras, con RTPProtocol.pack(payload), y genera un evento packet para
que el objeto en la capa de red que atiende el evento pueda enviarlo. Junto al
evento packet se enva el paquete calculado.
En base a esto, la lgica de RTPProtocol es sencilla, slo requiere crear un paquete
RTP

de

tamao

adecuado

new Buffer(RTP_HEADER_SIZE +

RTP_FRAGMENTATION_HEADER_SIZE + payload.length) e introducir en l los


campos como especifica la RFC3350 perfectamente calculados. Notar que la RFC
exige que el formato de los campos sea en orden de red (network byte order) [42,
Section 4], o sea, el byte de mayor peso primero. A esto se le ha denominado en la
introduccin Big Endian y debe tenerse en cuenta a la hora de usar las funciones
del mdulo Buffer:
la versin, indicadores de Padding (que no hay), extensin de la cabecera
(que tampoco hay) y el contador CSRC (a cero porque no hay lista con fuentes
contribuyentes CSRC) constituyen el primer byte. Son valores fijos en esta
aplicacin y conforman el nmero binario 11000000, 128 en decimal:
RTPPacket.writeUInt8(128, 0);
el siguiente byte lo forman el Marker Bit y el tipo de contenido que lleva el
paquete, que siempre ser el mismo. Para audio MP3, tipo MPA, su valor
es 14 segn la RFC3351. Por tanto, el valor a escribir slo depender del
Marker Bit: 000000000 si no est activado y 100000 si lo est.
RTPPacket.writeUInt8(this.setMarker? 142 : 14, 1);
this.setMarker = false;
Para esta aplicacin, que el Marker Bit est activo o no depende de un par de
criterios un tanto arbitrarios. El primero es si no hay payload por el motivo
que sea, por ejemplo que la fuente no haya sido capaz de generar uno en el
56

tiempo previsto. Se activar siempre despus de que se hayan actualizado


los campos relativos al orden de los paquetes y al tiempo porque, a pesar de
ello, stos siguen contando.
if (!payload) {
this.setMarker = true;
return;
}
Otro motivo de que se active el flag es que se haya empezado a emitir la
siguiente cancin y se sealice de esta manera al receptor:
mp3source.on(track, function(){
rtpserver.setMarker = true;
}); (Player.js)
Una vez reflejado en la cabecera no hay que olvidarse de limpiarlo.
el nmero de secuencia, inicializado en el constructor con un valor aleatorio
this.seqNum = Math.floor(Math.random() * 1000);
se incrementa en uno por cada paquete que se emite:
++this.seqNum;
RTPPacket.writeUInt16BE(this.seqNum, 2);
el timestamp, cuyo valor inicial es aleatorio y se calcula en el constructor:
this.timestamp = Math.floor(Math.random() * 1000);
su valor en cada paquete se incrementa una cantidad igual al tiempo que
necesitan para generarse las muestras contenidas en el frame, medido con
referencia a un reloj de frecuencia 90 kHz. En el caso de audio MPA, segn la RFC3551[43, Section 4.5.13], la tasa de incremento del timestamp es
siempre 90000, independientemente de la tasa de muestreo del audio.
var SAMPLES_PER_FRAME = 1152;
var REFERENCE_CLOCK_FREQUENCY = 90000;
var SAMPLING_FREQUENCY = 90000;
var TIMESTAMP_DELTA = Math.floor(SAMPLES_PER_FRAME
* REFERENCE_CLOCK_FREQUENCY
57

/ SAMPLING_FREQUENCY);
this.timestamp += TIMESTAMP_DELTA;
RTPPacket.writeUInt32BE(this.timestamp, 4);
el identificador de fuente SSRC, que ser nico, puesto que slo se emplea
una fuente en la aplicacin, y aleatorio, porque se ha comentado que no
hay previsin de que pueda colisionar con ms fuentes. Se calcula en el
constructor:
this.ssrc = Math.floor(Math.random() * 100000);
y se escribe en el paquete siempre con el mismo valor:
RTPPacket.writeUInt32BE(this.ssrc, 8);
aadir la cabecera especfica para audio MPEG, donde ambos campos sern
cero. El primero, MBZ, porque no se usa, y el segundo, Fragment Offset,
porque el inicio del frame dentro de la carga de datos es inmediatamente al
principio, puesto que no lleva ms que eso:
RTPPacket.writeUInt32BE(0, 12);
completar el paquete aadiendo el frame MP3 a continuacin de las cabeceras:
payload.copy(RTPPacket, 16);
La ltima pieza de la aplicacin es UDPSender, que maneja la comunicacin con
la red. Se encarga de enviar los paquetes que genera el protocolo RTP a la direccin multicast empleando UDP. Por tanto, se har que responda a los eventos
packet que genera aqul:
rtpserver.on(packet, function(packet){
udpSender.broadcast(packet);
});

(Player.js)

Adems, opcionalmente recaba informacin sobre la cantidad de paquetes y bytes enviados con propsitos estadsticos que cualquier cliente puede consultar
envindole un datagrama. El contenido del datagrama no importa, puede estar
vacio. La respuesta UDP contendr un objeto JSON serializado cuyos campos son
txPackets y txBytes, por ejemplo:
58

stats = {
txPackets : 0,
txBytes : 0
};
Por defecto, no se responde a las peticiones de estadsticas con lo que habr que
habilitarlo una vez se haya creado la clase:
var udpsender = new UDPSender();
udpsender.enableStats(true);
Por esta doble funcin que desempea entonces, esta clase consta de dos sockets:txSocket, para emitir al grupo de multicast, y rxSocket, para recibir las
peticiones de los datos estadsticos:
var Sender = function(options){
var options = options || {}
this.port = options.port || 5000;
this.broadcastAddress = options.broadcastAddress || 224.0.0.14;
this.stats = {
txPackets : 0,
txBytes : 0
};
this.txSocket = dgram.createSocket(udp4);
this.rxSocket = dgram.createSocket(udp4);
};
El primero de ellos se emplear en el mtodo UDPSender.broadcast(packet)
para transmitir un paquete cuando se reciba el evento packet. El envo se
hace con el mtodo que el socket proporciona:
var self = this;
this.txSocket.send(packet,
0,
packet.length,
this.port,
this.broadcastAddress,
function(err, bytes){
++self.stats.txPackets;
self.stats.txBytes += bytes;
59

});
El ltimo argumento es opcional, pero est presente porque es un callback que
se ejecuta cuando Node termina de enviar los datos por el socket. Esto ser til
para el propsito de mantener actualizadas las estadsticas, que ser justamente
en ese momento.
El segundo socket, rxSocket, escucha los mensajes que piden que se enven las
estadsticas al solicitante. En esta aplicacin est asociado, arbitrariamente, al
puerto 5001:
this.rxSocket.bind(5001);
Una vez habilitado para recibir, recibir atendiendo el evento para ello, message
enviando los datos solicitados de inmediato. A diferencia de txSocket, que transmite para una IP multicast, la respuesta debe ir dirigida al solicitante cuya IP no
se conoce a priori pero que puede consultarse en el paquete de la peticin. Estar
contenida en la estructura rinfo, remote info, que el listener recibe junto con el
mensaje:
var self = this;
if (enable){
this.rxSocket.on(message, function(msg, rinfo){
var stats = new Buffer(JSON.stringify(self.stats));
dgram.createSocket(udp4).send(stats,
0,
stats.length,
2468,
rinfo.address);
})
else{
this.rxSocket.removeAllListeners();
}
Toda la lgica que establece la relacin entre estos mdulos est en el script
app.js, y se ha ido introduciendo conforme se ha ido desgranando la interaccin
de los mismos.
60

3.6.

Objetivos de los Koans

Al trmino de la realizacin de la aplicacin se debern conocer los aspectos ms


elementales de los mdulos buffer y dgram con los que ser capaces de crear
sencillas aplicaciones de red sobre el protocolo UDP.
De buffer se pretenden conocer las distintas funciones para escribir datos de
diferentes tamaos en un Buffer.
1. Datos de 1 byte donde no se especifica endianness con writeUInt8().
RTPPacket.___(128, 0);
2. Datos de 2 bytes [BE] distinguiendo su endianness
RTPPacket.___(this.seqNum, 2);
3. Datos de 4 bytes distinguiendo su endianness
RTPPacket.___(this.timestamp, 4);
De dgram se pretende conocer el ciclo de vida de un socket UDP, con las primitivas
ms comunes que soporta para una comunicacin completa.
1. Crear sockets con la funcin dgram.createSocket() sabiendo que hay que
especificar si se va a trabajar sobre IPv4 o Ipv6:
this.txSocket = dgram.___(udp4);
this.rxSocket = dgram.___(udp4);
2. Ser capaz de vincular un socket a un puerto determinado en una interfaz
concreto con dgram.bind():
this.rxSocket.___(7531);
3. Enviar un datagrama con dgram.send() y conocer qu argumentos acepta
esta funcin:
this.txSocket.___(packet,
0,
packet.length,
this.port,
this.broadcastAddress,
61

function(err, bytes){
++self.stats.txPackets;
self.stats.txBytes += bytes;
});
4. Saber que hay que atender el evento message para recibir correctamente
datagramas de clientes:
this.rxSocket.on(___, function(msg, rinfo){
// ...
};)
5. Conocer la estructura rinfo que Node pone a disposicin del programador
con informacin del origen del datagrama:
function(msg, rinfo){
var stats = new Buffer(JSON.stringify(self.stats));
dgram.createSocket(udp4).send(stats,
0,
stats.length,
5002,
___.address);
};

3.7.

Preparacin del entorno y ejecucin de los Koans

Con objeto de poder ejecutar los Koans satisfactoriamente, se emplea un mdulo,


koanizer, que se distribuye con la prctica y cuya misin es adaptar el cdigo para
que puedan insertarse los huecos que caracterizan a un koan.
Los koans estn distribuidos en dos ficheros: dgram-koans.js, que contiene los
relacionados con el mdulo dgram, y buffer-koans.js, que comprende aquellos
que tienen que ver con el mdulo buffer. Ambos deben ser editados y completados,
sin que quede ningn hueco ___ (tres guiones bajos seguidos).
Verificar que se han realizado con xito los koans se determina ejecutando los
casos de prueba con:
$ jasmine-node -verbose spec/
62

Como resultado de una correcta resolucin de los koans, la prctica es completamente funcional y se puede ejecutar con el comando:
$ node app.js
Para escuchar la msica que se distribuye por RTP, hay que configurar un reproductor tal y como se indica en el apartado Descripcin de la aplicacin.
Si adems se quiere comprobar la caracterstica adicional que se ha introducido,
consulta de estadsticas, puede hacerse a travs del comando netcat 2 . Con netcat se pueden realizar operaciones de intercambio de datos en redes basadas en
TCP/IP. En este caso, se emplear dos veces, para:
1. escuchar en un puerto los paquetes con informacin estadstica que enva,
bajo demanda, la aplicacin. Se ha establecido, de manera coherentemente
aleatoria, que el puerto donde se reciba sea 5002.

Figura 3.5: Ejecucin de Netcat

2. solicitar dicha informacin, con un mensaje UDP, de contenido cualquiera,


al puerto 5001 donde la aplicacin los escucha.

http://netcat.sourceforge.net/

63

Figura 3.6: Respuesta a la peticin con Netcat

3.8.

Conclusin

Con el primer captulo se ha introducido el desarrollo de una sencilla aplicacin de


Internet al estilo de Node donde se presentan sus caractersticas fundamentales.
stas sern las que marquen la lnea a seguir en este tipo de desarrollos, por
ejemplo, poner a escuchar un programa en un puerto con listen(), realizar
operaciones de Salida, con send(), o Entrada, atendiendo el evento message,
y procesar los datos con los mtodos de Buffer, como write().

64

Captulo 4

Mdulos Stream y Net


4.1.

Aspectos de TCP relevantes para Node

El protocolo de nivel de transporte TCP (Transmission Control Protocol) es, junto


con IP, el ms conocido de los protocolos de Internet precisamente por dar nombre
a la pila de protocolos en la que se basan la inmensa mayora de las aplicaciones
de esta red.
TCP es un protocolo complejo, cuyas caractersticas son todo lo contrario a las de
UDP. Comenzando con que TCP est orientado a conexin, es decir, se establece una conexin lgica punto a punto sobre la que los datos que se transmiten
pueden ser vistos como un flujo bidireccional, o stream full-duplex. En un stream
[46, Chapter 6.5.2], es invisible la manera en que se han fragmentado los datos
para su envo, vindose stos a nivel de aplicacin como un chorro continuo
que se puede procesar conforme se recibe, sin que haya llegado al final, en contraposicin a UDP donde se tiene la nocin de datagrama y se tiene presente que
los datos llegan partidos en esos datagramas hasta el nivel de aplicacin. Se
denominan full-duplex a las conexiones con bidireccionalidad lectura/escritura
simultnea: se puede escribir y leer al mismo tiempo en ambos sentidos.
TCP es un protocolo fiable: los datos enviados se entregarn en destino sin errores
y en el mismo orden en que se transmitieron. Para conseguir estas caractersticas,
TCP dispone de mecanismos como los nmeros de secuencia y los asentimientos,
conocidos como ACK, a travs de los cuales se indica qu octetos se han enviado y cules se han recibido. Hay algunos ms, como el control de congestin,
65

que regula el flujo de datos entre emisor y receptor para evitar prdidas, o el
establecimiento de conexin, que inicializa los estados del protocolo. Todos son
posibles gracias a los campos de la cabecera especficamente reservados con ese
objetivo.
Como es lgico, toda esta evolucin con respecto al protocolo UDP se ve reflejada
en la cabecera de un segmento TCP. Aparte de los ya conocidos campos Puerto
Origen, Puerto Destino y Checksum, aparecen varios ms relacionados con los
mecanismos anteriormente mencionados:
Nmero de Secuencia y Nmero de asentimiento
El nmero de secuencia es el ndice que el primer octeto de datos que se est
enviando, tiene en el buffer donde estn todos los datos a enviar. Su utilidad
en el receptor es la de ordenar los datos del segmento donde corresponda
cuando llegue.
Por su parte, el nmero de asentimiento lo enva el receptor al recibir un
segmento de datos. Contiene el nmero de secuencia que espera recibir en
el siguiente segmento enviado por el emisor, o lo que es lo mismo, que hasta
ese nmero, todos los octetos han sido recibidos.
El orden que siguen los nmeros de secuenciaasentimiento en los sucesivos
envosrespuestas puede complicarse dependiendo de si se pierden paquetes
y se necesita por tanto un reenvo, o llegan duplicados, etc. Es algo que queda fuera del estudio de este tema puesto que es transparente a la aplicacin
y no se puede controlar con el API de Node.
Flags
Para distintos estados y acciones del protocolo que activados indican que
URG el segmento contiene informacin urgente, priorizada respecto al resto
de los datos.
ACK el segmento TCP est siendo utilizado para realizar el asentimiento del
correspondiente segmento recibido y por tanto el nmero de asentimiento que contiene es vlido.
PSH los datos deben ser enviados al receptor inmediatamente. Esto es porque TCP, si lo estima necesario, puede almacenar los datos que la aplicacin le entrega hasta que considere que se pueden enviarlo a travs
del canal, es decir, el envo de datos puede no ser inmediato. Este flag,
66

push, activado en un segmento, har que todos los datos se enven.


RST la conexin debe ser reiniciada porque se ha detectado algn error en el
transcurso de la comunicacin TCP. Se pueden producir mltiples tipos
de errores a lo largo de todos los estados de una conexin, como por
ejemplo, la recepcin de un segmento de asentimiento de datos que an
no se han enviado.
SYN el segmento TCP est intentando establecer una conexin y pretende
sincronizarse.
FIN el emisor no va a enviar ms datos y la conexin se puede liberar.
Tamao de ventana
como parte del mecanismo de control de congestin, este campo indica el
nmero de octetos que el receptor admite a partir del ltimo que ha recibido
correctamente (indicado por el nmero de asentimiento). [47, Chapter 2.6].
El resto del paquete bsicamente lo componen campos opcionales, y la parte de
datos, que tambin es opcional. En TCP, los segmentos no slo se usan para la
transmisin de informacin sino que tambin los hay que estn compuestos por
la cabeceras IP ms TCP. Suelen ser segmentos de sealizacin como los que
negocian el inicio y fin de la conexin, o los de asentimiento, en caso de que el
flujo de datos sea principalmente unidireccional.
A lo largo de una comunicacin sobre TCP entre un cliente y un servidor se pasa
por diversas fases:
Establecimiento de conexin una conexin se establece en tres pasos, a travs de un proceso conocido como three-way handshake. Es aqu donde se
negocian los nmeros de secuencia y asentimiento de ambas partes. En el
caso ms simple y visto desde la parte que inicia el handshake ocurre lo
siguiente:
1. Se enva un segmento con el flag SYN activado y un nmero de secuencia inicial.
2. Se recibe un segmento de asentimiento al anterior con los flags SYN
y ACK activados. SYN indica que se la otra parte enva un nmero de
secuencia vlido y ACK, que acepta el nmero de secuencia que se le ha
proporcionado en el paso anterior.
67

3. Se enva otro segmento de asentimiento con, nicamente, el flag ACK


activado, con el que se acepta el nmero de secuencia recibido en el
paso anterior.
Cualquier variacin del procedimiento que se salga de la lgica propuesta
desembocar en el envo o recepcin de un segmento con el flag RST activado, con el consecuente reinicio de la conexin.
Intercambio de datos una vez establecida la conexin se envan los datos en
paquetes TCP. Durante el intercambio entran en juego varios mecanismos
de transferencia de datos
1. Transmisin fiable: intercambio de segmentos con nmeros de secuencia y asentimiento para que todos se reciban en perfecto orden y sin
duplicidad. Si se produjese la prdida de algn segmento, se activa el
mecanismo de retransmisin. Un segmento se supone perdido si no llega su asentimiento dentro de un intervalo de tiempo que el emisor calcula (retransmission timeout). Una vez vencido el timeout, el segmento
se retransmite.
2. Control de congestin: se realiza un ajuste dinmico del tamao de la
cantidad de datos que un extremo acepta y que notifica en la cabecera
TCP mediante el campo Tamao de ventana. El ajuste de la ventana
se realiza a travs de algoritmos que implementa TCP y estn fuera
de lo que se pretende con el tema que nos ocupa. Sin embargo, hay
un comportamiento interesante de TCP relacionado con el control de
congestin: el algoritmo de Nagle [46, Chapter 6.5.8]. A grandes rasgos
este algoritmo almacena en el buffer de transmisin los datos a enviar
cuando son de pequeo tamao con objeto de evitar malgastar ancho
de banda al enviar ms cabecera que payload y al reducir el tiempo
que se tarda en enviar datos y recibir su correspondiente asentimiento
(latencia). Adems evita llenar demasiado rpido la ventana del receptor.
Como efecto colateral, podran observarse retardos en la ejecucin de la
aplicacin que se comunica con el servidor de tal manera que si son
significativos, cabra la posibilidad de plantearse el desactivarlo.
3. Keep-alive [48, Chapter 4.2.3.6]: esta caracterstica del protocolo es opcional y, de hecho, no est en la RFC original sino en la RFC1122. Es un
mecanismo, por defecto desactivado, para comprobar que una conexin
68

por la que no se han recibido datos o asentimientos en mucho tiempo est todava activa. Para ello se envan peridicamente segmentos TCP sin
datos o con un octeto aleatorio como carga y se espera como respuesta
un ACK, siendo un RST si ha habido interrupcin de la conexin. Estos
envos se realizan despus de que haya pasado un intervalo de tiempo
configurable, o por defecto no inferior a dos horas segn la RFC, sin
actividad en el socket desde la ltima recepcin de un segmento TCP.
Se recomienda, en caso de que se use, que sea por la parte servidora hacia el
cliente para detectar cadas o desconexiones accidentales por parte de ste
y liberar los recursos que se le haban asignado.
Fin de conexin se produce cuando uno de los dos extremos (o ambos a la
vez) no tiene ms datos que enviar (lo que no quita que no pueda seguir
recibindolos, por lo menos hasta que el otro extremo cierre).
Un extremo cierra su conexin enviando un segmento con el flag FIN activado, que ser confirmado con su correspondiente ACK por la otra parte. A
partir del envo de FIN no ser posible enviar ms datos, pero s recibirlos
hasta que llegue un paquete FIN desde el extremo remoto. En este momento,
se confirma con un ACK y ambos extremos se cierran.
Destaca de este procedimiento que hay un momento en que la comunicacin est medio abierta: no se puede enviar pero s recibir. Por tanto, para
cerrar una conexin full-duplex no basta con sealizarlo desde un extremo
sino que el otro debe sealizarlo tambin (si no tiene ms datos que enviar).
Sin embargo, estrictamente hablando, se dice que una conexin est medio
abierta (half-open) si, segn la RFC793, uno de los extremos se ha cerrado sin notificarlo al otro. En este caso se debera producir un reinicio de la
conexin, RST [47, Chapter 3.4].

4.2.

Streams en Node

Un Stream en Node es un objeto que encapsula un flujo binario de datos y provee


mecanismos para la escritura y/o lectura en l. Por tanto, pueden ser readable,
writable o ambas cosas.
En realidad es una interfaz abstracta, con lo cual condiciona a cualquier objeto
69

que quiera comportarse como un Stream a heredar de l y a ofrecer una serie de


mtodos para el manejo de dicho Stream y, adems, condiciona al programador a
implementar esos mtodos de manera que sean capaces de realizar operaciones
sobre el flujo concreto que yace por debajo. Esto se debe a que cada flujo binario
puede tratarse de diferentes maneras y las acciones de lectura/escritura no tienen
porqu ser iguales a las de otro flujo que tambin las ofrezca.
Por ejemplo, una conexin TCP y un archivo del sistema de ficheros en Node se
tratan como Streams, sin embargo, la implementacin de las operaciones sobre
ellos son distintas por la propia naturaleza de cada uno: datos sobre un protocolo
de red vs. datos almacenados en el sistema de ficheros del sistema operativo.
Se sabe que de un Stream se puede leer porque su propiedad stream.readable
es true. La lectura se realiza de manera asncrona, instalando un Listener para el
evento data: stream.on(data, function(datos){}). Los datos que recibe
la funcin de callback son por defecto de tipo Buffer, a menos que se especifique
una codificacin mediante stream.setEncoding(codificacion).
El ciclo de vida de un Stream readable se puede gestionar de manera sencilla con
una serie de mtodos:
stream.pause()
detiene la entrega de datos, es decir, no se emitirn eventos data con
lo que la lectura quedar pausada. El API de Node advierte que pueden
seguir emitindose algunos eventos data, cuya informacin se puede el
programa puede almacenar, despus de invocar al mtodo hasta que la parte
de Entrada/Salida, libuv, de Node procese la seal de parada.
stream.resume()
reanuda la entrega de datos pausada con el mtodo anterior
stream.destroy()
anula toda posibilidad de operaciones Entrada/Salida sobre el socket. En
consecuencia, no se emitirn ms eventos data o end aunque s cabe
esperar un evento close cuando se liberen todos los recursos asociados
al Stream.
Por su parte, se puede escribir en un Stream si su propiedad stream.writable
es true, y se hace con el mtodo stream.write() que adopta dos formas dependiendo de qu tipo es la informacin a escribir:
70

si son datos en el formato por defecto, esto es, los bytes en bruto de un
Buffer, se utiliza stream.write(buffer).
si se escriben datos codificados como texto (por defecto en utf8 pero se
puede cambiar opcionalmente con el argumento codificacion) se emplear
stream.write(string, [codificacion]).
El ciclo de vida de este Stream writable se gestiona con un mtodo que adopta
varias formas:
stream.end()
que finaliza el stream segn corresponda a la naturaleza del Stream. Por
ejemplo, si se trata de un fichero, escribe el carcter de control EOF (End Of
File).
Tiene una variante, que permite escribir datos en el stream antes de cerrar
y luego, cerrarlo. Si los datos son bytes se emplea stream.end(buffer), y
si es texto, stream.end(string, [codificacion]).
stream.destroy()
tiene idntico efecto al del caso del Stream readable: impide que se realicen
operaciones de Entrada/Salida, sin importar que queden datos por escribir.
Si quedan datos por escribir y se desea que se escriban antes de que se
cierre, el API ofrece el mtodo stream.destroySoon() que tambin destruye
el Stream directamente si no hay datos en la cola de escritura.
Para que una clase creada por el programador se comporte como un Stream se
debe importar la clase base Stream contenida en el mdulo stream, heredar de ella
e implementar los mtodos para las operaciones de lectura/escritura y gestin del
Stream:
var Stream = require(stream),
utils = require(utils);
function myNewStream(){
Stream.call(this);
// codigo que implementa el constructor
}
utils.inherit(myNewStream, Stream);
71

myNewStream.prototype.write = function(datos, codificacion){


// implementacion de la funcion
}
La clase base Stream nicamente ofrece el mtodo Stream.pipe():
Stream.pipe(destino, [opciones])
permite conectar un Stream readable, que ser una fuente de datos binarios,
a un destino writable donde se escriben esos datos de manera sincronizada.
Esto lo consigue pausando y reanudando el Stream emisor segn los datos
se van escribiendo o no en el Stream destino. Cuando el emisor finaliza
con el evento end, invoca al mtodo destino.end() a menos que en las
opciones se indique lo contrario:
opciones = { end: false };
La funcin devuelve el Stream destino con el propsito de que puedan encadenarse varios Streams consecutivamente. Por ejemplo, se conectan entre
s las entradas y las salidas de los Streams A, B y C de la manera:
A.pipe(B).pipe(C) // equivale a A.pipe(B); B.pipe(C);

4.3.

TCP en Node

Una aplicacin Node puede hacer uso de TCP importando la librera net:
var net = require("net");
net proporciona al programador el objeto Socket, que representa obviamente un
socket TCP o un socket de dominio UNIX1 , y el objeto Server, que modela un
servidor de conexiones TCP. Son las dos perspectivas desde las que se tratan
las conexiones TCP dentro de Node. Sin embargo, ninguno de los dos objetos
1

Los Unix Domain Sockets son un mecanismo de comunicacin interproceso IPC como lo son,

por ejemplo, las pipes (tuberas). A diferencia de stas, los Unix Domain Sockets son un mecanismo
bidireccional y adems, las operaciones no se hacen a travs de las funciones para el manejo de
ficheros sino a travs de las que se emplean con los sockets. Por tanto, no quedan descritos por un
descriptor de fichero, ni tampoco por una direccin IP sino por una ruta a un archivo del sistema
de ficheros.

72

se instancia directamente: se debe hacer a travs de mtodos especficos para


ellos.
Desde la parte cliente, obtenemos un socket creando una conexin a una ubicacin

concreta

mediante

los

mtodos

net.connect()

net.createConnection() indistintamente. Ambos mtodos tienen varias cabeceras que aceptan distintos argumentos. Las ms generales son:
net.connect(opciones, [function(){ }])
net.createConnection(opciones, [function(){ }])
El argumento opciones contiene la informacin necesaria para establecer
la conexin. En el caso de conexiones de red TCP esto incluye el puerto
port (nica opcin obligatoria), la mquina destino host y la interfaz local
localInterface desde el que realizar la conexin. Por ejemplo:
{ port: 5479,
host: "192.168.1.32",
localInterface: "192.168.1.26" }
En el caso de sockets de dominio UNIX, la nica opcin, obligatoria, es la
ruta path en el sistema de ficheros de la mquina donde corre el script.
Para ambos casos existe una opcin comn, allowHalfOpen, por defecto a
false pero que si se activa permite mantener la conexin medio abierta
cuando el otro extremo cierra su parte, dando al programador la posibilidad de gestionar l mismo qu hacer. A nivel de protocolo, esto significa que
cuando se recibe desde el otro extremo un segmento FIN, no se enva otro
FIN, como se ha visto que se comporta el protocolo en la fase de Fin de Conexin, sino que el programador puede seguir enviando datos (recordar que
half-open implica que s se puede recibir) o cerrar la conexin explcitamente
con socket.end().
El resto de cabeceras son una simplificacin de las anteriores:
net.connect(puerto, [maquina], [function(){ }])
net.createConnection(puerto, [maquina], [function(){ }])
se emplean para conexiones TCP exclusivamente.
net.connect(path, [function(){ }])
net.createConnection(path, [function(){ }])
73

son las cabeceras para emplear sockets de dominio UNIX.


Todos los mtodos emiten el evento connect de tal manera que instalar un listener para ese evento con socket.on(connect, function() ) es equivalente
a invocar a los mtodos pasndoles un callback.
Cuando a travs de estos mtodos se ha obtenido una conexin, es decir, una
instancia de Socket, se dispone de un flujo de datos sobre el que leer y escribir.
Este socket se puede configurar con una serie de mtodos accesores:
socket.setTimeout(timeout, [function(){ }])
activa en el socket un temporizador, desactivado por defecto, que expira tras
timeout milisegundos de inactividad, emitiendo el evento timeout. Este
evento es capturable con el callback opcional del segundo argumento o a
travs de socket.on(timeout, function() ).
El timeout no implica que expire la conexin o que se modifique de alguna
manera. Si se quiere cerrar hay que llamar a los mtodos existentes para
ello, socket.end() o socket.destroy(), detallados ms adelante.
Para desactivarlo de nuevo, se invoca la funcin con el argumento timeout
= 0.
socket.setNoDelay([sinRetardo])
activa o desactiva, dependiendo del valor del argumento booleano
sinRetardo, el algoritmo de Nagle. sinRetardo por defecto es true, con
lo que los datos se envan de inmediato por el socket y, por tanto, indica que
el algoritmo est desactivado.
socket.setKeepAlive([activo], [retardoInicial])
activa o desactiva, con el argumento booleano activo, el mecanismo de
Keep-Alive, por defecto desactivado. El retardoInicial indica en milisegundos el tiempo que debe estar el socket inactivo para que entre en accin.
Si el valor de retardoInicial es cero, se toma como timeout el anterior
valor de retardoInicial o, de no ser posible, el valor por defecto, que es el
que especifica la RFC1122 y que implementan las pilas TCP de los sistemas
operativos (no Node): 2 horas (7200000 milisegundos).
socket.setEncoding()
independiente del protocolo TCP en s (no se modifican parmetros relativos
74

a la conexin) y orientado a la codificacin de los datos que transporta para


su consumo en el programa. Por defecto, la codificacin es la estndar de
Node, utf8.
Una vez conectado, adems, se conocern los datos de la conexin a travs
de:
socket.address()
proporciona la direccin IP address de la interfaz donde est establecido el
socket, la familia family de dicha direccin (IPv4 o IPv6) y el puerto local
port al que est ligado, todo encapsulado en un objeto JSON, como por
ejemplo
{ address: "192.168.1.35",
port: 5556,
family: "IPv4" }
socket.remoteAddress
es la direccin IP del otro extremo del socket
socket.remotePort
es el puerto que emplea el otro extremo del socket
El intercambio de informacin se realiza a travs de operaciones de lectura y
escritura sobre el flujo de datos que el socket es, ya que implementa la clase
Stream, a travs de los mtodos habituales de este tipo de objetos, comentados
en el apartado anterior.
Para escribir datos en una codificacin concreta en un socket se emplea:
socket.write(data, [codificacin], function(){ })
donde la funcin de callback se ejecuta cuando los datos han salido del
buffer para ser enviados a travs del socket.
Por su parte, la lectura se realiza como en cualquier Stream, atendiendo al evento
data:
socket.on(data, function(data){
//codigo para procesar los datos
});
75

Se puede adems gestionar su ciclo de vida con los mtodos habituales para ello,
tanto los de los Streams writable como los de los readable:
socket.pause()
el API de Node lo recomienda junto con resume() para ajustar la tasa de
transferencia en la recepcin de datos (por parte del servidor en los provenientes del cliente) ya que, como se ha explicado, pause() detiene la recepcin de datos.
socket.resume()
reanuda la recepcin de datos detenida con el mtodo pause(). De esta
manera se vuelven a emitir eventos de tipo data.
socket.end([datos], [codificacion])
si se invoca sin argumentos, comienza la fase de final de conexin enviando
un paquete FIN, quedando sta medio cerrada: no se podr escribir en el
socket pero s se podrn recibir datos del otro extremo (si este extremo no
ha finalizado su conexin tambin con end()).
En caso de que se invoque con argumentos, primero se escriben los datos
con

la

codificacion

especificada,

como

con

socket.write(datos,

codificacion), y luego procede como si se invocase a socket.end().


socket.destroy()
inhabilita completamente las operaciones de Entrada/Salida en el socket,
por lo que se usa en los casos en que se haya producido un error.
En la parte servidor, un socket es capaz de vincularse a un puerto sobre el que escuchar conexiones de otras mquinas e interactuar con ellas. Para este propsito
el mdulo net ofrece el mtodo
net.createServer([opciones], [function(socket){ }])
devuelve una instancia de la clase Server configurado con unas opciones
que, hasta la fecha, slo permiten mantener una conexin medio abierta con
allowHalfOpen (por defecto false), que funciona como ya se ha explicado.
opciones = { allowHalfOpen: true };
El segundo argumento es un listener del evento connection, que se emite cada vez que hay una nueva conexin en el servidor. Esta funcin recibe como parmetro el socket que se crea. Siempre se puede atender a
76

connection como con todos los eventos de las instancias de la clase


EventEmitter: server.on(connection, function(socket){ }).
La realidad es que una llamada a net.createServer() es equivalente a instanciar la clase Server directamente:
new Server([opciones], [function(socket){ }]);
El resultado del mtodo es un servidor preparado para escuchar en distintos
dispositivos segn se emplee el mtodo listen():
server.listen(puerto, [maquina], [backlog], [function(){ }])
server.listen(path, [function(){ }])
server.listen(handle, [function(){ }])
Una vez levantado el servidor, se tiene disponible informacin relativa al mismo
con:
server.address()
que funciona de manera idntica a la funcin homnima de la clase Socket,
devolviendo un objeto JSON con tres propiedades: direccin IP address, tipo
de IP family (v4 o v6) y puerto de escucha port. Por ejemplo:
{ port: 6420,
family: IPv4,
address: 127.0.0.1 }
server.connections
es la propiedad que lleva la cuenta del nmero de conexiones concurrentes
que maneja el servidor en ese momento
server.maxConnections
es la propiedad que fija el nmero mximo de conexiones concurrentes en
el servidor, es decir, es el lmite superior de la anterior propiedad. Cuando
se alcanza este nmero, las conexiones se rechazan, abortando el intento de
conexin en la parte cliente.
La gestin del ciclo de vida de un socket servidor es sencilla:
a

travs

del

evento

connection,

server.on(connection,

function(socket){}), se manejan las conexiones entrantes. La funcin de


77

callback recibe el socket correspondiente a la conexin, el cual, obviamente,


es una instancia de Socket y, por tanto, se maneja como tal.
server.close([function(){ }]) impide que el servidor admita nuevas
conexiones, pero no lo destruye hasta que no se hayan cerrado todas. Ser
entonces cuando se emita el evento close que se puede atender con un
callback pasndolo como argumento opcional o instalndolo como Listener
con server.on(close, callback).
Durante la ejecucin del servidor pueden producirse errores que se procesan capturando el evento error:
server.on(error, function(error){ })

4.4.

Aplicacin con Streams TCP

Una aplicacin ejemplo que puede implementarse con TCP es la de un servidor


que acepte conexiones a travs de las cuales reciba comandos que modifiquen su
comportamiento. La salida del comando ser enviada de vuelta al cliente.

4.4.1.

Descripcin del problema

En esta prctica se realizar una modificacin de la aplicacin UDP: se adaptar


el emisor RTP para que enve paquetes RTP a la direccin IP de un cliente en
lugar de hacerlo a la IP de un grupo multicast. Para tal propsito, el cliente debe
conectarse a un puerto del servidor y, a travs de comandos introducidos a travs
de un prompt, manejar la lista de reproduccin del servidor. Se definirn seis
comandos posibles:
list: ofrece una lista de canciones disponibles para escuchar
play: inicia la reproduccin de la lista, por defecto parada
pause: detiene la reproduccin en un punto concreto de una cancin
next: solicita la reproduccin de la siguiente cancin
prev: solicita la reproduccin de la cancin anterior
exit: finaliza la sesin
78

Puesto la aplicacin se basa en un intrprete de comandos, seguir siendo necesario un reproductor de audio que soporte RTP que apunte a la direccin IP de la
mquina desde donde se realice la conexin a la aplicacin.

4.4.2.

Diseo propuesto

Los requisitos de esta aplicacin hacen necesaria una modificacin del diagrama
de mdulos respecto al que modelaba la aplicacin UDP/RTP. En este escenario
se introduce la clase RemotePrompt en el mdulo principal de la aplicacin y ser
donde se implementen las caractersticas que se le han pedido al servidor. El
diagrama que modela el escenario para esta prctica sera el siguiente:

Figura 4.1: Diagrama de Colaboracin de la Solucin TCP

El funcionamiento general de la aplicacin es, a grandes rasgos, escuchar y atender las conexiones que se produzcan en el puerto que elija quien emplee el mdulo (por ejemplo, 2323), rechazando aquellas que ya tengan una sesin establecida. Para conocer este dato, se mantiene una tabla de direcciones IP remotas,
sessionsDB:
var sessionsDB = {};
this.server = net.createServer();

79

this.server.on(connection, function(connection){
var remoteIP = connection.remoteAddress;
connection.write("Welcome to your command line playlist manager, "
+ remoteIP);
if (remoteIP in sessionsDB){
connection.end("Duplicated session, closing.");
return;
};
sessionsDB[remoteIP] = true;
// Logica de la aplicacion referente
// al tratamiento de la conexion
});
Por cada una de las conexiones anteriores se crear una fuente de audio MP3,
MP3Source, que controle el estado de la reproduccin. Esta clase proporcionar
para ese propsito unas funciones que se invocarn segn demanden los comandos. Adems, informar a travs de eventos sobre aspectos relevantes de la
reproduccin que son tiles que el cliente conozca, notificndoselo automticamente. En concreto, mediante el evento track, MP3Source avisar de cundo
se ha realizado una operacin sobre una cancin, bien bajo demanda del cliente
o bien porque sea inherente al transcurso de la reproduccin, como cuando se
pasa a la siguiente pista:
source.on(track, function(trackName){
connection.write("Now playing " + trackName + "\r\n# ");
});
Mediante el otro evento, listEnd, se informa cundo se ha alcanzado el final de
la lista de reproduccin. ste adems activa el mecanismo de cierre automtico
de la aplicacin tras un intervalo de tiempo sin actividad:
source.on(listEnd, function(){
var seconds = 10;
connection.write("End of the list reached.Closing in "
+ seconds
80

+ " seconds\r\n# ");


connection.setTimeout(seconds * 1000, function(){
delete sessionsDB[this.remoteAddress];
connection.end("Your session has expired. Closing.");
});
});
La transmisin de los paquetes RTP se delega en la clase RTPServer que, como en
la prctica anterior sobre UDP, est ntimamente ligada con la clase MP3Source.
Para esta prctica se ha simplificado al mximo este aspecto de la conexin, quedando simplemente en el trozo de cdigo siguiente, sin mayor inters para el
objeto de la prctica:
var rtpserver = new RTPServer();
var udpSocket = udp.createSocket(udp4);
rtpserver.on(packet, function(packet){
udpSocket.send(packet, 0, packet.length, 5002, remoteIP);
});
source.on(frame, function(frame){
rtpserver.pack(frame);
});
Los comandos se reciben a travs del socket de la conexin. Se ha visto que para
recibirlos, y recibir cualquier tipo de datos en general, es necesario atender al
evento data para lo que habr que instalar el correspondiente listener: ser el
encargado de procesar los comandos y actuar en consecuencia sobre la fuente
MP3.
connection.on(data, function(data){
this.setTimeout(0);
// Logica del procesado de los comandos
});
En el cdigo anterior se puede observar que, como medida preventiva, se desactiva
el timeout con el que se cierra el socket, por si hubiera sido activado.
A la hora de analizar los comandos, se debe tener en consideracin que provienen del buffer de red con un retorno de carro que es conveniente limpiar para,
81

posteriormente, proceder a identificarlos:


var command = data.toString(utf8).split("\r\n")[0];
Por lo general, cada comando identifica un mtodo homnimo de la fuente de
audio y la mayora de las veces, no supone ms que esa operacin. En otros
casos, como el del comando list, se formatea la salida para presentarla al cliente
de una manera visualmente ordenada:
switch(command){
case "list":
var playlist = source.list();
this.write("\r\nSongs in the playlist");
this.write("\r\n---------------------");
for (var i=0; i < playlist.length; i++){
var song = playlist[i];
this.write("\r\n"
+ (source.currentTrack() == song? "> " : "
+ song);
}
this.write("\r\n# ");
break;
case "play":
source.play();
break;
case "pause":
source.pause();
break;
case "next":
source.next();
break;
case "prev":
source.prev();
break;
82

")

case "exit":
delete sessionsDB[this.remoteAddress];
this.end("Bye.");
break;
default:
this.write("Command " + command + " unknown\r\n# ");
}
Exceptuando exit, que no acta sobre la fuente de audio, el resto de los comandos provocarn que MP3Source emita el evento track. As, se ofrecer informacin al cliente sobre el resultado de su accin, como se ha comentado durante
el desarrollo de la solucin propuesta.
Por ltimo, sera necesario liberar los recursos ocupados por un cliente una vez
ste haya finalizado su sesin ya sea porque la cierra el cliente (con exit), el
servidor (por listEnd) o se corta la conexin. La mejor manera de conocer esto
es atendiendo el evento close del socket:
connection.on(close, function(){
source.pause();
udpSocket.close();
rtpserver = source = null;
});
Toda la lgica anterior, menos la parte que pone a escuchar al servidor, est contenida en el constructor del objeto RemotePrompt. Aparte del constructor, RemotePrompt tambin posee un mtodo listen(), con el que comenzar la actividad de
la aplicacin, puesto que se encarga de ordenar al servidor comenzar a escuchar
en un puerto:
this.listen = function(port){
this.server.listen(port);
}
Sin embargo, a pesar de tener un constructor, RemotePrompt slo es instanciable
desde el mdulo RemotePrompt.js a travs del mtodo create() que es lo nico
que ste exporta, no pudiendo hacerse de otra manera (por ejemplo con new). El
motivo es que construir la librera de archivos MP3Library, otro de los cometidos
83

de create(), consume un tiempo indeterminado que debe esperarse y durante


el cual, el objeto RemotePrompt est en un estado indefinido. Con create() se
eliminan los errores derivados de esta espera, pues gestiona ella misma el evento
ready de la librera, y ofrece una instancia de RemotePrompt funcional desde
el primer momento. El siguiente fragmento de cdigo explica lo anterior, aunque
no sera necesario conocerlo para el desarrollo de la prctica:
exports.create = function(){
var app;
var library = new nodeMp3.Mp3Library({ basedir: ../data/songs/ });
library.on(ready, function(){
app = new RemotePrompt(this);
// Gestion de metodos invocados de app
// cuando estaba en proceso de creacion
});
// Logica de gestion del objeto
// app para uso del desarrollardor
}
As, ejecutar la aplicacin puede hacerse desde un sencillo script:
var remotePrompt = require(./RemotePrompt);
var app = remotePrompt.create();
app.listen(2323);
o incluso a travs de una sla lnea de cdigo en la lnea de comandos de Node:
> require(./RemotePrompt).create().listen(2323)

4.5.

Objetivos de los Koans

A travs de los Koans desarrollados para esta aplicacin se busca obtener un


conocimiento bsico del servidor TCP que Node pone a disposicin del programador y de las conexiones que genera, tanto de su manejo a travs de sus mtodos
principales como de algunas propiedades destacables de ellas.
84

1. Emplear el mtodo createServer() para disponer de un servidor para conexiones TCP:


this.server = net.___();
2. Hacer que el servidor TCP escuche en un determinado puerto invocando su
mtodo listen():
this.server.___(port);
3. Atender cada una de las conexiones que acepta el servidor mediante la escucha del evento connection que l mismo emite:
this.server.on(___, function(connection){});
4. Escribir en el Stream de la conexin con el mtodo write():
connection.___("Welcome to your command line playlist manager, "
+ remoteIP);
5. Finalizar una conexin a travs del mtodo end() de la misma:
connection.___("Duplicated session, closing.");
6. Activar con setTimeout() el contador de timeout en el socket para realizar
una accin concreta tras ese tiempo:
connection.___(seconds * 1000, function(){
delete sessionsDB[this.remoteAddress];
connection.end("Your session has expired. Closing.");
});
7. Recibir datos del cliente de manera asncrona escuchando el evento data
de la conexin:
connection.on(___, function(data){

4.6.

});

Preparacin del entorno y ejecucin de los Koans

De nuevo, un requisito para poder ejecutar los Koans satisfactoriamente es el


mdulo koanizer, distribuido con la prctica.
85

El fichero que contiene los koans es net-koans.js, que es en realidad el mdulo


RemotePrompt, puesto que es ste el que lleva todo el cdigo asociado al mdulo
net. Al igual que en la prctica anterior debe ser editado y completado, sin dejar
ningn hueco ___.
Para verificar que se han realizado con xito los koans se ejecutan los casos de
prueba con:
$ jasmine-node -verbose spec/
Como resultado de una correcta resolucin de los koans, la prctica es completamente funcional y se puede ejecutar con el comando:
$ node app.js
Para escuchar la msica que se distribuye por RTP, hay que configurar un reproductor tal y como se indic en el apartado Descripcin de la aplicacin del
anterior captulo. Esta vez, sin embargo, los paquetes se reciben en la direccin
IP de la interfaz de la mquina que se conecta al servidor. Cuando se realiza
una conexin al servidor, ste informa de la direccin donde hay que apuntar el
Reproductor. Para conectarse bastar un simple telnet:

Figura 4.2: Telnet al puerto de la aplicacin

Conocida sta, se puede proceder a configurar el Reproductor:


86

Figura 4.3: Reproductor VLC, configuracin

Para comenzar la reproduccin es necesario avisar al servidor con play en el


prompt de la aplicacin donde pueden tambin, introducirse el resto de comandos:

Figura 4.4: Solucin TCP, salida de los comandos


87

4.7.

Conclusin

Completando el problema propuesto como aplicacin de este captulo, se habrn


adquirido los conocimientos necesarios para crear un servidor TCP mediante
createServer() que admita conexiones, atendiento al evento connection,
en un puerto (con listen()). Se debe ser capaz de realizar un intercambio de
datos basndolo en la recepcin con un listener del evento data y el envo,
escribiendo en el Stream correspondiente con write(). Una vez que ambos extremos hayan completado la comunicacin, se debe ser capaz de finalizarla con
end().

88

Captulo 5

Mdulo Http
5.1.

Aspectos de HTTP relevantes para Node

HTTP (Hyper Text Transfer Protocol) es un protocolo de nivel de aplicacin para


el acceso a recursos en entornos distribuidos. Estos recursos estn identificados unvocamente por su URL (Uniform Resource Locator) que es su localizador:
indica la ruta para acceder al l. Las URLs son un subconjunto de los denominados URIs, y un URI (Uniform Resource Identificator) no es ms que, como su
nombre indica, un identificador del recurso. Por tanto, URL y URI son trminos
equivalentes en este caso: un recurso est identificado por su localizacin.
El formato ms tpico de una URL es:
<protocolo>://<maquinaremota>:<puerto>/<ruta>/<al>/<recurso>
[?<query>=<valor>]
Por el tema que nos ocupa, <protocolo> es obvio que es http, aunque podra
ser https si la conexin sobre la que se establece la comunicacin fuese un
canal seguro. La <maquinaremota> es el nombre de DNS o la direccin IP de
la mquina donde corre el servidor HTTP, general, pero no necesariamente, escuchando en el <puerto> 80 que es el asignado por la IANA1 para HTTP. La
<ruta>/<al>/<recurso> es el path, en general relativo en el sistema de ficheros,
del servidor donde se aloja el recurso. Por ltimo, en el caso de que el recurso sea
1

Internet Assigned Numbers Authority es el organismo encargado de fijar de manera oficial los

cdigos y nmeros que se emplean en los protocolos estndar de Internet como rangos de IPs o
nmeros de puerto

89

dinmico, es decir, sea la salida de un programa ejecutable localizado en la URL,


se le pueden pasar parmetros de entrada en la parte opcional de la misma, la
<query>. Una Query comienza en la URL con el signo de interrogacin ? a partir
del cual se concatenan con el smblo & los parmetros de nombre <query> y su
<valor> asociado con un =. Ilustrando todo lo anterior:
http://www.google.com/search?q=nodejs+koans
El escenario de uso del protocolo implica, en su manera ms bsica, a dos actores
que interactan mediante un modelo de intercambio de mensajes denominados
peticin, para la parte del cliente, y respuesta, para la del servidor. Este intercambio no tiene estado, es decir, los mensajes son independientes de anteriores
y posteriores.
Los actores implicados son principalmente el cliente, a travs del Agente de Usuario, que es aquel programa que genera las peticiones, y el Servidor, que las recibe
y es capaz de interpretar el protocolo en el extremo opuesto.
El escenario bsico puede complicarse con la aparicin de elementos intermedios (programas o dispositivos) entre cliente y servidor, como proxies, gateways o
tneles. Un proxy es un intermediario entre cliente y servidor, a menudo transparente para ambos. Por este motivo es capaz de comunicarse con los dos extremos
actuando al mismo tiempo como cliente y como servidor. Su misin es variada
pero principalmente tiene propsitos de seguridad y cach. La funcin de la cach
es optimizar recursos de red, almacenando durante un tiempo los recursos a los
que se accede a travs suyo para entregarlos al cliente si los vuelve a solicitar y
as no tener que pedirlos de nuevo al servidor, ahorrando el tiempo y ancho de
banda que ello supone.
Un gateway es tambin un intermediario pero esta vez de la parte servidora. Su
propsito es recibir las peticiones dirigidas al servidor, adaptarlas para reenvirselas, tanto a nivel de mensaje como a nivel de protocolo si procediera, y devolver
las respuestas de vuelta al cliente.
Un tnel se puede considerar, como explica la RFC [49, Section 1.3], como un
intermediario que acta de enlace entre dos conexiones, sobre las que pueden
ir distintos protocolos. El mecanismo de tunelado permite a la mquina origen,
cliente, establecer una conexin punto a punto a travs del tnel, que dejar de
ser parte de la comunicacin HTTP y se convertir en un elemento transparente,
dejando de existir cuando se cierre la comunicacin en los extremos.
90

5.1.1.

La parte del Cliente

Las peticiones HTTP estn formadas por lneas de texto claro, terminadas con los
caracteres de control <CRLF> (Retorno de carro ms salto de lnea). La primera
lnea, request-line, es siempre obligatoria, y el resto, las cabeceras y cuerpo, aportan informacin adicional. El esquema que sigue la request-line son tres campos
separados entre ellos por un espacio:
<METODO> <URI> <VERSION>
El <METODO> es la accin que se solicita al servidor que realice sobre el recurso
solicitado. Las distintas versiones del protocolo han ido introduciendo mtodos,
si bien, los nicos que todo servidor HTTP debe soportar obligatoriamente segn
la RFC son:
GET
devuelve el recurso identificado por la <URI>. Puede ser esttico o pueden
ser datos generados dinmicamente.
HEAD
devuelve informacin sobre el recurso solicitado siendo dicha informacin
la misma que proporcionara GET pero sin incluir el recurso en s en la
respuesta
Los dos mtodos anteriores se denominan Mtodos seguros [49, Section 9.1.1]
puesto que la accin que se solicita con ellos nicamente tiene como resultado
la obtencin de un recurso del servidor y/o informacin sobre l. Hay otros mtodos que se pueden considerar inseguros porque pueden producirse resultados
potencialmente peligrosos:
POST
solicita al servidor el procesado del cuerpo de la peticin con objeto introducir nueva informacin en el recurso identificado por la URI como, por ejemplo
y segn la RFC2616, aportar datos desde un formulario o introducir informacin en una base de datos.
PUT
solicita al servidor crear o modificar el recurso identificado por la URI.
DELETE
solicita al servidor la eliminacin del recurso identificado por la URI.
91

De todos los anteriores mtodos, GET, HEAD, PUT y DELETE se pueden considerar Mtodos idempotentes, es decir, que los efectos colaterales de varias
peticiones de uno de ellos son los mismos que si se invocase el mtodo una sola
vez.
Del resto de mtodos se puede destacar CONNECT. Con l se insta a un proxy a
establecer un tnel con el endpoint especificado en la URI. El comportamiento del
proxy al recibir el mtodo CONNECT ser realizar una conexin TCP al endpoint,
como si del cliente se tratara, y redirigir el trfico de manera transparente entre
cliente y servidor, sin necesitar HTTP ms que para iniciar el mecanismo de tunelado. De esta manera se puede, entre otras cosas, alternar entre HTTP y HTTP
Seguro cuando hay un proxy HTTP entre cliente y servidor.
A lo largo del tiempo el protocolo HTTP ha sufrido dos actualizaciones desde su
versin original, la 0.9, ahora obsoleta y fuera de uso. La ltima es la 1.1 habiendo mejoras posteriores experimentales, como la RFC2774: An HTTP Extension
Framework. En definitiva, el campo <VERSION> de la request-line se compone de
la cadena HTTP seguida del nmero de versin. En el caso de la ltima sera
HTTP1.1.
La versin 1.1 aade muchas mejoras respecto a la 1.0, entre las cuales se pueden
destacar:
Nuevos mtodos: HTTP 1.0 contempla los mtodos GET, HEAD y POST nicamente, el resto de mtodos comentados anteriormente y alguno ms, como
OPTIONS, pertenecen a HTTP 1.1.
Conexiones persistentes: con HTTP 1.0, cada peticin se realizaba bajo una
conexin establecida con tal propsito. Una vez recibida la respuesta, la
conexin se cerraba. HTTP 1.1 permite conexiones persistentes: sobre una
misma conexin TCP se pueden realizar las peticiones HTTP necesarias, con
el consiguiente ahorro de recursos en las mquinas (cliente, servidores y
elementos de red) y la reduccin de la latencia y la congestin de red, entre
otras cosas.
Control de cach: se introducen mecanismos de control de cach como, por
ejemplo, nuevas cabeceras condicionales que complementan a la existente
If-Modified-Since de la versin 1.0.
Nuevos cdigos de respuesta: HTTP 1.0 tena reservado el rango de los c92

digos 1xx, que devuelve el servidor como informacin al cliente para casos
que pudiesen surgir en un futuro, pero no tena definido ninguno. HTTP 1.1
introduce dos: 100 Continue y 101 Switching Protocols.

En el resto de rangos introduce algunos ms y adems desambigua otros


existentes, como 302 Found con los nuevos 303 See Other y 307 Temporary
Redirect.

La peticin puede constar de campos de cabecera adicionales, explicados al detalle en la RFC, con los que aadir informacin til para el servidor a la hora
de componer una respuesta. Con ellos se puede aportar, entre otros muchos datos:

el tipo MIME de los recursos que acepta el Agente de Usuario, mediante la


cabecera Accept. Un tipo MIME clasifica el formato de un recurso segn su
naturaleza: texto, imagen, audio...

Los tipos MIME estn definidos en la RFC1341, expresan los formatos de la


entidad que, en este caso, acepta el Agente de Usuario y definen varios tipos
y subtipos para clasificar el contenido binario del cuerpo de un mensaje
segn su naturaleza [41]. El formato MIME se expresar entonces como la
dupla tipo/subtipo. Ejemplos de tipos MIME [46] con algn subtipo de los
ms utilizados son:
93

Contenido
Texto

Tipo MIME (tipo/subtipo)

Descripcin

text/plain

Slo texto

text/html

Lenguaje de marcado hipertexto para pginas web

Imagenes

image/gif

Formato de intercambio de
grficos gif

image/jpeg

Imgenes codificadas segn el estndar propuesto por el Joint Photographic


Experts Groups

Audio

audio/mpeg3

Audio en formato MP3 segn el estndar MPEG-1

Vdeo

video/mpeg

Vdeo en el estndar de codificacin del Moving Picture Experts Group

Datos

application/octet-stream

Secuencia binaria genrica

Tabla 5.1: Ejemplos de tipos MIME

qu lenguaje es el preferido y que codificacin adicional y juego de caracteres desea el cliente que se emplee: Accept-Language, Accept-Encoding y
Accept-Charset respectivamente.
la referencia al recurso, identificado por su URI, del que se ha obtenido la
URI de la peticin, es decir, de dnde se proviene: Referer.
el nombre de dominio y puerto de la mquina tal y como aparece en la URL:
Host. Cuando una misma direccin IP tiene asignados en DNS varios nombres de dominio (hosting virtual), esta cabecera obligatoria permite localizar
el recurso que se solicita en la URI.
la versin 1.1 del protocolo incluye un campo de cabecera que se puede emplear para solicitar al servidor un cambio de protocolo a nivel de aplicacin
(nunca a nivel de transporte) en la conexin que se est empleando. Por tanto, no se asegura que el servidor acepte el cambio, ni el cambio se har en
otra de las conexiones que pueda tener en paralelo el cliente con el servidor,
ni mucho menos ser posible cambiar, por ejemplo, de TCP a UDP. S ser
94

posible, y de hecho son las aplicaciones que tiene esta cabecera, securizar
conexines HTTP pasndolas a HTTPS (HTTP sobre TLS) o de HTTP a WebSocket (que se ver en profundidad en el ltimo tema). La existencia de esta
cabecera obliga a que la cabecera Connection tambin est presente y que
su valor sea Upgrade.
en ocasiones se puede pedir al servidor, a travs de la cabecera Expect que
confirme que se va a comportar de la manera que se espera a determinadas
peticiones del cliente.
El final de la cabecera lo marcan los caracteres de control <CRLF> que deben
incluirse siempre. A partir de ah comienza, si existiese y el mtodo empleado lo
permite, del cuerpo del mensaje. En l se transmite al servidor la entidad asociada
al mtodo de la peticin. Por ejemplo, en un POST pueden ser los parmetros que
necesite la aplicacin web. Se sabe de la existencia del cuerpo del mensaje a
travs de la inclusin de una o dos cabeceras que son obligatorias cuando est
presente: Content-Length y/o Transfer-Encoding.
Transfer-Encoding es una propiedad del mensaje, no de la entidad que se enva
en s y se emplea para indicar la transformacin o transformaciones a las que se
ha sometido a dicha entidad para su correcta transmisin. Distintos valores de
esta cabecera sealan cmo se enva el mensaje: tal cual (con identity), comprimido (con gzip, compress o deflate) o dividido en trozos (con chunked). Para
los dos primeros casos, o en caso de que no haya, es necesario dar a conocer al
servidor el tamao del cuerpo del mensaje, no de la entidad a enviar. Por tanto,
este valor se calcula una vez aplicada la transformacin elegida. Sin embargo, en
el caso de la transmisin chunked encoding, el mensaje se divide en fragmentos de
octetos que comienzan con una lnea que contiene el tamao en hexadecimal del
mismo. Para indicar el fin de los bloques, se aade un chunk de tamao 0.
Opcionalmente, se puede incluir la cabecera Trailer. En ella se listan las cabeceras que se podrn encontrar en el denominado Trailer que es la parte que
se transmite despus del mensaje, al recibir todos los chunks y antes de que
se indique fin de la peticin con un <CRLF>. Normalmente esto se emplea para enviar una suma MD5 del mensaje, en la cabecera Content-MD5 del Trailer,
que se ha enviado en el cuerpo de la peticin. Cabe destacar que las cabeceras Transfer-Encoding, Content-Length y la propia Trailer no pueden estar
contenidas en el Trailer.
95

Sirva de ejemplo de peticin la siguiente:


POST /login.php? HTTP/1.1
Accept-language: en-US,en;q=0.8,es;q=0.6
Host: nodejskoans.com
Referer: nodejskoans.com
Content-type: application/x-www-form-urlencoded
Accept: text/html,application/xhtml+xml;q=0.9,*/*;q=0.8
Content-Length: 36
username=arturo&password=nodejskoans

5.1.2.

La parte del Servidor

En la parte del servidor, las respuestas que ste enva al cliente como resultado de
una peticin, como sucede con las peticiones, tambin son lneas de texto claro
que tienen una estructura determinada. La primera lnea, status-line, contiene el
cdigo de respuesta, status-code, en una estructura como la que sigue:
<VERSION> <STATUS-CODE> <REASON PHRASE>
El campo relativo a la <VERSION> del protocolo se rige de la misma manera que
en la peticin
<STATUS-CODE> es un nmero de tres dgitos que define qu tipo de respuesta
se est proporcionando. Hay cinco categoras, dependiendo del resultado de la
peticin en el servidor, enumeradas por el dgito de mayor peso, que indican [49,
Section 6.1.1]:
1xx: informacin La peticin ha sido recibida y entendida, y se procede a procesarla informando al cliente slo con la status-line y alguna cabecera opcional.
En HTTP 1.1 slo hay dos cdigos:
100 Continue
sirve para informar al cliente de que el servidor acepta el tipo de peticin en base a las cabeceras que le est enviando y puede continuar
transmitiendo el cuerpo del mensaje. Es til, por ejemplo, en el caso
de que el cliente tenga que asegurarse de que el servidor va a aceptar
datos que suponen un uso importante de ancho de banda o consumen
96

mucho tiempo en su transmisin. En este caso, el cliente emplear una


cabecera Expect: 100-continue
101 Switching Protocol es la respuesta a una peticin con la cabecera
Upgrade. Expresa la conformidad del servidor para realizar el cambio
de protocolo de aplicacin a los que se solicitan, de manera inmediata
al envo de esta respuesta. El servidor tambin puede forzar al cliente
a solicitar el cambio de protocolo a travs del cdigo de respuesta 426
Upgrade Required.
2xx: xito
La peticin fue recibida y procesada. Todo ha ido como se esperaba.
El cdigo ms tpico de esta categora es 200 OK, que se obtiene cuando todo
ha sido satisfactorio y se devuelve el recurso resultante del mtodo. Si es ste
es GET, se devuelve el recurso y, si es POST, el resultado de la accin.
3xx: redireccin
La peticin no se ha completado y se deben realizar ms acciones para hacerlo. Generalmente indican un cambio de localizacin del recurso. La nueva
localizacin se indica en una cabecera para ese propsito: Location.
Si el cambio es permanente y se ha asignado al recurso una nueva URI, se
emplea 301 Moved Permanently y para peticiones siguientes deben usarse
la ubicacin o una de las ubicaciones proporcionadas en Location.
Si por el contrario el cambio es temporal existen los cdigos 302 Found, 303
See Other (slo HTTP/1.1) y 307 Temporary Redirect (slo HTTP/1.1),
con los que las siguientes peticiones deben seguir hacindose a la URI original de la peticin o a la que indica Location.
Normalmente estos cdigos son transparentes al usuario ya que es el Agente
de Usuario el que los interpreta y realiza la accin procedente.
4xx: error de cliente
La peticin que se ha realizado est mal formada o contiene errores. Hay
muchos casos por los que la peticin puede ser errnea, siendo el ms comn 404 Not Found con el que se indica que el servidor no ha encontrado
el recurso donde indica la URI de la peticin y no se sabe si se ha movido,
ya que en ese caso se enviara un 3xx.
97

Sin embargo, el motivo ms genrico de error es el 400 Bad Request que


indica que la sintaxis de la peticin HTTP es errnea. El Agente de Usuario
debera revisar que se hayan formado bien la request-line y las cabeceras, y
que no contengan errores en sus nombres o valores.
Otros motivos de error pueden estar relacionados con los permisos de acceso
al recurso. Si el recurso est protegido y el usuario no presenta credenciales
o stas son errneas, se obtendr un error 401 Unauthorized y se pedirn las credenciales incluyendo obligatoriamente en la respuesta la cabecera WWW-Authenticate. Un Agente de Usuario se acredita mediante el campo
Authorization en las cabeceras de la peticin. El proceso de Autenticacin
y Autorizacin est descrito con detalle en la RFC2617: HTTP Authentication:
Basic and Digest Access Authentication.
A grandes rasgos, cuando es necesario verificar la identidad del cliente que
accede a determinados recursos, el servidor propone al Agente de Usuario
que acredite que est autorizado presentando su contrasea a travs de un
esquema de autenticacin. ste se indica en la cabecera y puede ser Basic o
Digest. Adems en la misma cabecera se incluye el campo realm que es un
texto que se presenta al usuario y le ofrece una pista sobre qu contrasea
emplear.
Basic es un esquema en desuso porque el Agente de Usuario enva el par
nombre de usuario y contrasea en la cabecera Authorization de la peticin en claro, sin cifrar, slo codificado en base64, y no es recomendable
salvo, quizs, en conexiones seguras HTTPS.
El esquema Digest tampoco ofrece una seguridad alta, pero transmite la
contrasea cifrada con un algoritmo criptogrfico de dispersin que por
defecto es MD5. En su modo ms elemental, la cabecera incluye, aparte
del modo, el campo realm, como en el esquema Basic. A partir de la recepcin de esta respuesta, el cliente incluir en las peticiones la cabecera
Authentication, que consta de varios campos separados por comas y formados por pares clave/valor. Uno de estos campos es response y contiene
la computacin criptogrfica de la password.
En ocasiones el recurso estar protegido y adems el servidor no aceptar credenciales, aunque sean vlidas. En ese caso el error devuelto es 403
Forbidden, aunque si se quiere ocultar el recurso completamente, se puede
98

obtener un 404 Not Found.


5xx: error de servidor
La peticin es correcta pero el servidor ha fallado al procesarla. Los distintos
motivos se indican con un status-code concreto, por ejemplo, 500 Internal
Server Error avisa de un error indeterminado o 501 Not Implemented
indica que el servidor no soporta el mtodo de la peticin, entre otros.
La <REASON-PHRASE> es una breve explicacin textual, comprensible por un
humano, del cdigo de respuesta. La RFC da recomendaciones sobre ellas, pero
son opcionales y pueden cambiarse como se estime oportuno.
Las siguientes lneas a la status-line son las cabeceras de la respuesta, y su propsito es anlogo al de las cabeceras de la peticin: proveen informacin extendida
de la respuesta.
Una cabecera obligatoria, que el servidor debe incluir siempre (con la excepcin
de los cdigos 1xx principalmente), es Date y estampa la fecha de la creacin de
la respuesta por parte del servidor. Su principal utilidad es la gestin por parte
de la chach de las entidades que se transmiten en el mensaje.
La fecha se puede expresar en varios formatos pero el preferido es el que se define
en la RFC1123: Requirements for Internet Hosts Application and Support, que
incluye el da de la semana, da, mes, ao y hora (hh:mm:ss) en la zona correspondiente. Por ejemplo: Sun, 06 Nov 1994 08:49:37 GMT.
Otra de las cabeceras es Set-Cookie. Con ella el servidor inicia el mecanismo que
tiene HTTP para almacenar en la parte cliente pequeas piezas de informacin
referentes al estado de la comunicacin HTTP. Estas piezas de informacin se
conocen con el nombre de cookies y contienen pares <clave>=<valor>;. Cada
cookie se fija con una cabecera Set-Cookie, que adems de la propia cookie
incluye la fecha de expiracin de la misma, por lo que puede haber varias de stas
en una respuesta. Cuando un Agente de Usuario recibe esas cabeceras, almacena
las cookies y est obligado a incluirlas (si no han expirado) en las siguientes
peticiones al dominio, mediante la cabecera de peticin Cookie.
Como parte del mecanismo de Upgrade, el servidor tambin puede incluir la cabecera homnima Upgrade en la respuesta de informacin de cambio de protocolo
(101 Switching Protocol). Esta cabecera contendr la pila de protocolos que se
va a empezar a usar separndolos por comas. El primero de ellos se corresponde
99

con el que se usar en el nivel ms bajo de la pila.


Un ejemplo ms de cabecera de respuesta es la ya comentada WWW-Authenticate.
La ltima parte de una respuesta es la denominada Entidad (Entity) que es el
contenido que se quiere transmitir como resultado de la peticin, normalmente el
recurso en s. La Entidad aade unas cabeceras (cabeceras de entidad) a la respuesta con informacin acerca del contenido que se enva. stas principalmente
hablan de las caractersticas del contenido, entre ellas:
Content-Encoding
indica, cuando aparece, la codificacin adicional a la que se ha sometido el
contenido y por tanto, cmo debe el Agente de Usuario decodificarlo. Habitualmente se trata de compresin con distintos algoritmos (zip, lempel-zivwelch...) que se indican con los valores gzip, compress, deflate...
Content-Length
el tamao en octetos del recurso que se enva. Si se le han aplicado codificaciones adicionales (indicadas con la cabecera anterior), el tamao debe
calcularse despus de aplicarlas.
Content-Type
expresa el formato MIME de la entidad que se enva de la misma manera en
que MIME defini la cabecera del mismo nombre en la RFC1341.
Como ejemplo, se puede observar la respuesta HTTP a la peticin de ejemplo
anterior:
HTTP/1.1 200 OK
Content-Encoding: deflate
Content-Type: application/octect-stream
Date: Mon, 04 Mar 2013 17:23:34 GMT

5.2.

HTTP en Node

Se tienen disponibles las clases que modelan un escenario HTTP bsico importando el mdulo http de la manera habitual:
var http = require(http);
100

El mdulo comprende ambos extremos de una comunicacin HTTP adems de


los mensajes que se intercambian las partes. El extremo servidor ofrece la clase
http.Server
Se puede levantar un servidor HTTP invocando al mtodo http.createServer()
o instanciando directamente la clase http.Server (que es lo nico que hace
createServer())
var httpserver = http.createServer([function(request, response){}]);
El servidor HTTP que ofrece este mdulo se puede considerar como un Servidor
TCP con nuevas funcionalidades o caractersticas ya que realmente hereda de
la clase net.Server 2 . Consecuentemente, se puede atender a los eventos de sta
y utilizar sus mtodos, incluido listen(), para empezar a escuchar conexiones
entrantes, y close() para dejar de escucharlas, que son los ms relevantes. Todo
ello est documentado detalladamente en el captulo anterior.
Ahora no slo se pueden limitar el nmero de conexiones simultaneas sino que
se pueden limitar el nmero de cabeceras de las peticiones para cada una de las
conexiones con httpserver.maxHeadersCount.
El resto de las novedades que introduce el Servidor HTTP son todo eventos, principalmente referidos al ciclo de vida de las peticiones:
con net.Server se saba cundo se produca una nueva conexin escuchando el evento connection. Ahora, la clase http.Server emite un evento
request por cada peticin HTTP que se recibe por las conexiones. Hay
que tener en cuenta que a travs de cada una de las conexiones se pueden
recibir mltiples peticiones y, por tanto, cada conexin emitir mltiples
eventos request.
httpserver.on(request, function(request, response){
// procesar la peticion con opcion de responderla
});
El callback del listener puede definirse de esta manera o pasndolo opcionalmente a http.createServer(). Recibe como argumentos la peticin que
genera el evento, request, y la respuesta, response, que se configurar como se considere necesario y se enviar de vuelta al cliente, junto con el
2

http.js: https://github.com/joyent/node/blob/v0.8.20-release/lib/http.js#L1750

101

cuerpo del mensaje si procede. Ambos mensajes son instancias de las clases
ServerRequest y ServerResponse respectivamente, que se tratan ms adelante, y que bsicamente son un modelo de las peticiones y la respuesta tal
y como las definen las RFCs relacionadas con HTTP.
Para generar el objeto request lo que Node hace internamente es instalar un
listener en el evento connection3 para capturar el socket de la conexin
y monitorizar todo lo que se recibe desde el cliente para procesarlo con un
Parser HTTP de un pool de Parsers. El Parser HTTP se obtiene del binding
node_parser y es quien internamente crea el objeto request.
El objeto response se genera una vez se han procesado las cabeceras HTTP
de la peticin4 . Ambos, request y response, se entregan a la aplicacin
mediante el evento request que se encarga de emitir el Servidor.
los errores en cualquier conexin que haya en el servidor se notifican a travs de clientError que aportar al callback informacin de la excepcin
generada:
httpserver.on(clientError, function(excepcion){ });
Adems, para cada peticin, el servidor detecta automticamente los mecanismos de Continue y Upgrade que se notifican con los eventos checkContinue
y upgrade respectivamente. En ambos casos, si no se atiende el evento, la
conexin se cierra automticamente.
checkContinue se emite cuando un cliente solicita confirmacin para continuar con la peticin a travs de la cabecera Expect: 100-continue, inhibiendo
la

emisin

del

evento

request.

Si

ste

se

atiende

con

server.on(checkContinue, function(request, response){ }) debe generarse adecuadamente la respuesta, empleando mtodos especficos de response
si se admite el cuerpo de la peticin, o enviando el cdigo de respuesta que se considere oportuno, por ejemplo, un 417 Expectation Failed, si se rechaza.
upgrade se emite cuando un cliente solicita una actualizacin de protocolo. La accin a realizar en este caso se define como un manejador del evento,
server.on(upgrade, function(request, socket, head){ }).
3
4

http.js: https://github.com/joyent/node/blob/v0.8.20-release/lib/http.js#L1748
http.js: https://github.com/joyent/node/blob/v0.8.20-release/lib/http.js#L1868

102

Como cuando el cliente pide Upgrade es para cambiar el protocolo de aplicacin


que se est empleando en la conexin, Node pone a disposicin del listener el
socket entre cliente y servidor, con el que se puede trabajar directamente, y un
Buffer, head, que puede contener el primer paquete que el cliente enva con el
nuevo protocolo. Este paquete encapsula cabeceras y datos y es deber del programador interpretarlo correctamente para identificar su contenido. El resto de
paquetes se reciben a travs del socket de la manera habitual en que se reciben
datos en un Stream: atendiendo al evento data:
socket.on(data, function(data){ });
Otro de las caractersticas de HTTP 1.1 que Node soporta especficamente es el
mtodo CONNECT, a travs del evento del mismo nombre, connect. ste se
emite cuando se recibe una peticin con dicho mtodo y, de manera anloga al
evento anterior, se puede escuchar con server.on(connect, function(request,
socket, head){ }) donde los argumentos que se reciben y su funcin son idnticos al del caso de upgrade y, tambin de la misma manera que antes, se
pueden recibir los paquetes que el cliente enva por el tnel hacia el otro extremo:
instalando un manejador del evento data en el socket.
Se debe observar que si se desea envar una respuesta HTTP, sta no se recibe
como argumento en los listeners sino que debe ser escrita directamente en el
socket.

5.2.1.

ServerRequest

El objeto ServerRequest modela la peticin HTTP que el cliente realiza al servidor y


al que se accede mediante los eventos exclusivos de http.Server. ServerRequest
implementa un Stream de tipo readable lo cual tiene toda lgica puesto que la
peticin viene del cliente y no tiene sentido que se pueda modificar, para ello est
la respuesta.
El API de Node advierte que es una clase que no se puede instanciar directamente.
Y en efecto, no se puede instanciar con new ya que, en detalle, los objetos de esta
clase los crea node_http_parser bajo demanda5 .
ServerRequest proporciona mtodos para obtener toda la informacin posible de
la

peticin,
5

empezando

por

el

propio

socket

por

donde

se

enva

http.js: https://github.com/joyent/node/blob/v0.8.20-release/lib/http.js#L1793

103

request.connection, y siguiendo por la imprescindible request-line:


serverRequest.method es una String inmutable que contiene el nombre del mtodo
serverRequest.url contiene la URI de la peticin, que se recomienda parsear
adecuadamente con el mtodo del mdulo url require(url).parse().
serverRequest.httpVersion da la versin del protocolo en una String que contiene exlusivamente el nmero
Las cabeceras de la peticin estn contenidas todas en el objeto JSON
serverRequest.headers y son accesibles teniendo en cuenta que las propiedades de este objeto son el nombre de las cabeceras en minscula.
Si la peticin contiene cuerpo de mensaje, ste ser accesible gracias a que la peticin es un Stream y los datos que se envan a travs del socket pueden recibirse
atendiendo al evento data de la manera habitual:
serverRequest.on(data, function(datos){

});

Esto implica que se tienen a disposicin el resto de mtodos y eventos de la clase


Stream: pause() o resume() y end o close.
Se debe contemplar el caso que la transferencia se haga por chunks, y se sigan
las reglas de una Transfer-Encoding: chunked. Si es as, es posible que existan
cabeceras adicionales contenidas en el Trailer de la peticin, a las que se podr
acceder gracias a la propiedad request.trailers, que es un objeto JSON contenindolas todas. No obstante, por su naturaleza, el Trailer slo estar disponible
una vez se haya recibido toda la peticin y, por tanto, se haya emitido el evento
end.

5.2.2.

ServerResponse

El objeto ServerResponse modela la respuesta HTTP que el servidor enva al cliente. Va ntimamente relacionada con la peticin que la causa, relacin que se refleja
en el cdigo del mdulo porque para instanciar un objeto ServerResponse es necesario que est ligarlo a un objeto peticin request del que se ha procesado las
cabeceras6 . De todas maneras, una ServerResponse se generar automticamen6

http.js: https://github.com/joyent/node/blob/v0.8.20-release/lib/http.js#L1868

104

te y ser puesta a disposicin del programador a travs de uno de los mltiples


eventos de un servidor http.Server. ServerResponse implementa un Stream de
slo escritura, como es lgico para configurar una respuesta adecuada que enviar
al cliente.
Con este objetivo se ofrecen facilidades, para generar desde las cabeceras hasta el
cuerpo, con soporte para el mecanismo de Continue gracias al mtodo
response.writeContinue(), que smplemente enva el mensaje HTTP/1.1 100
Continue al cliente.
Construir una respuesta HTTP se puede hacer ordenadamente siguiendo unos
pasos:
Generar

la

El

response-header.

mtodo

que

puede

usarse

es

serverResponse.writeHead(statusCode, [reasonPhrase],
[cabeceras]). Slo se puede invocar una nica vez y antes de cualquier
otro mtodo de respuesta. A travs de l se fija el cdigo de respuesta con
el argumento statusCode. Opcionalmente pueden fijarse las cabeceras en
notacin JSON.
En realidad, no es estrictamente necesario llamar a este mtodo para construir

una

respuesta.

Se

puede

directamente

escribir

con

serverResponse.write() o serverResponse.end(). Las cabeceras necesarias, denominadas por el API cabeceras implcitas (porque se han generado sin llamar a serverResponse.writeHead()), se calculan y se aaden
automticamente. Si hubiera necesidad de realizar operaciones con estas cabeceras implcitas existen una serie de mtodos para ello, que se invocarn
antes de llamar a serverResponse.write():
los

accesores

server.Response.getHeader(cabecera)

responseHeader.setHeader(cabecera,valor) para obtener y para


asignar una cabecera respectivamente
serverResponse.removeHeader(nombre) para eliminar una cabecera
En los casos en que la respuesta se genera implcitamente, se puede asignar
el status code fijando la propiedad numrica serverResponse.statusCode
antes de enviarse las cabeceras. Si ya se han enviado, contendr el status
code que se envi.
completar

el

contenido

del
105

cuerpo

de

la

respuesta

con

serverResponse.write()
en ocasiones, en una respuesta enviada en bloques o chunks puede desearse
aadir un Trailer con las cabeceras adicionales permitidas. En este caso, se
puede hacer uso de serverResponse.addTrailers() cuyo nico argumento es un objeto JSON de propiedades las cabeceras que se deseen incluir. Si
se invoca este mtodo en una transferencia que no sea chunked, no tendr
ningn efecto.
dar por finalizada la respuesta y dejarla lista para el envo con
serverResponse.end()

5.2.3.

Clientes HTTP

Con Node se pueden realizar peticiones HTTP a un servidor a travs del mtodo
http.request(opciones, function(){})
El argumento opciones puede adoptar dos formas. La ms sencilla es la de un
String que es la URL del recurso al que se pretende acceder.
El segundo formato que se le puede dar a opciones es el de un objeto JSON a
partir del cual se configura la conexin. Como siempre, sta quedar definida
principalmente por la IP o el nombre de dominio del servidor al que se conecta
(opciones.hostname u opciones.host), su puerto (opciones.port) y el interfaz
local desde el que se realiza (opciones.localAddress). Sobre dicha conexin
se realizar una peticin, instancia de la clase ClientRequest que a su vez es
un Stream de slo escritura. Este objeto representa la peticin en curso y se
controlar la evolucin de la comunicacin HTTP manipulndolo a travs de sus
mtodos.
Cuando la conexin ha sido creada, ClientRequest notifica al programador, mediante el evento socket, que dispone de un socket sobre el que realizar el
intercambio peticin/respuesta y que est listo para ello:
clientRequest.on(socket, function(socket){ });
No es necesario atender a este evento para continuar la peticin, sino que simplemente es una notificacin ms de las fases por las que va pasando la comunicacin.
106

Una vez preparado, no sera necesario escribir la cabecera completa en el socket,


ya que la request-line y algunas cabeceras de la peticin se crean automticamente cuando se invoca a http.request(), empleando valores de las opciones que se le
pasan. En este caso, la request-line se configura: fijando el mtodo de la peticin
se a travs de opciones.method, por defecto GET, obteniendo la url del recurso, incluidas queries si procediese, de opciones.path, por defecto / empleando
siempre la versin del protocolo 1.17 .
El resto de cabeceras tambin se fijan opcionalmente con el objeto JSON
opciones.headers, aunque algunas se obtienen de otras opciones, como Host,
que sale de opciones.hostname o de opciones.host (si existen las dos, hostname
es la preferida por motivos de compatibilidad con el mdulo url), o Authorization,
que se computa con opciones.auth.
La cabecera completa se queda en cola de salida para ser enviada cuando comience a generarse el cuerpo de la peticin, si lo hubiera. Mientras sta no
se haya enviado, las cabeceras pueden modificarse con los mtodos accesores
getHeader(nombre), setHeader(nombre, valor) y removeHeader(nombre).
En caso de que haya una entidad que precise enviarse, se har con el mtodo que
el interfaz Stream define: clientRequest.write(datos, [codificacion]).
Completada la peticin, tanto si se ha escrito en el stream como si no, se debe
indicar con clientRequest.end() a menos que se desee cancelar todo el proceso
con clientRequest.abort().
El siguiente paso en la interaccin es esperar a que el servidor enve su respuesta y procesarla. Siguiendo la filosofa de la plataforma, la manera de recibirla
es a travs de eventos que clientRequest puede atender instalando para ellos
los correspondientes callbacks. Tpicamente la respuesta ser de aquellas con los
cdigos de respuesta y las cabeceras ms habituales, que indican que se ha atendido satisfactoriamente o que ha habido algn error, y con alguna entidad como
cuerpo de mensaje. Todo esto se recibe por el cliente como un objeto, instancia
de ClientResponse (tratado ms adelante), a travs del evento response, que
solamente se dispara una vez:
clientRequest.on(response, function(response){ });
El resto de eventos tratan los casos concretos de los mecanismos ya conocidos de
7

http.js: https://github.com/joyent/node/blob/v0.8.20-release/lib/http.js#L1294

107

Continue, Upgrade o Connect, como se ha visto que suele hacerse a lo largo de


toda la librera.
Para

las

respuestas

informativas,

del

grupo

1xx,

se

dispararn

los

eventos continue y upgrade. En el primer caso como resultado de la recepcin de un cdigo de respuesta 100 Continue, y en el segundo, de un 101
Switching Protocols. En ambos, lo normal es que estos cdigos sean el resultado de haberlos solicitado al servidor, como parte de la negociacin, con las
cabeceras Expect: 100-continue o Upgrade. Sin embargo no es condicin indispensable: si, sin incluirlas, el servidor por cualquier motivo s los retornase,
el evento se activara igualmente. No obstante, esta situacin es anmala y no
debera producirse en el transcurso de la comunicacin.
La accin a seguir la determina como siempre la funcin que atiende el evento.
En caso de un 100 Continue, no se necesita ms informacin adicional para
proseguir con la ejecucin del programa y, por tanto, el listener no recibe ningn
argumento:
clientRequest.on(continue, function(){ });
Por el contrario, en respuestas 101 Switching Protocol, es necesario tener una
referencia al socket de la conexin, con objeto de enviar el nuevo protocolo por l,
y tambin tener disponibles los primeros datos que vienen sobre l, si el servidor
los hubiera empezado a enviar:
clientRequest.on(upgrade, function(response, socket, datos){ });
El caso del evento connect, disparado durante el transcurso del mecanismo
Connect, es distinto: para que este evento se emita y pueda ser atendido es necesario que se haya realizado una peticin con el mtodo CONNECT que puede ser
contestada con un status-code cualquiera por parte del servidor. Este cdigo lo recibir el listener del evento, junto con el socket por el que se desarrolla la comunicacin entre ambos extremos de la conexin y los primeros datos connectHead
que se reciben (si es que el extremo opuesto enviara):
clientRequest.on(connect, function(res, socket, connectHead){};
A diferencia de casos anteriores, el tipo de respuesta debe ser procesado por el
programa y el desarrollador debe contemplar los casos que le interesen. Como
ejemplo, si el servidor devolviese un 101 Switching Protocol, esta vez no se
emitira ningn evento upgrade, sera el programa el que tendra que decidir
108

cmo manejarlo. An as, este caso es extrao y tampoco debera producirse en


una comunicacin normal.

5.2.4.

ClientResponse

ClientResponse modela la respuesta del servidor que recibe el cliente como un


Stream de slo lectura con mtodos y eventos para manejar su ciclo de vida. Se
puede ver como el equivalente en la parte de cliente del objeto ServerRequest,
aunque se corresponde con la ServerResponse que genera el servidor.
Es un objeto que ClientRequest recibe como resultado de atender algunos de los
eventos definidos para l como response, upgrade, continue o connect.
Por tanto, va asociado al mismo ClientRequest, no pudiendo ser instanciado directamente: su constructor necesita como argumento la peticin req a la que est
ligada.
Permite obtener la informacin necesaria de cada una de las partes de la respuesta. Para empezar, se accede a los campos de status-line con un par de propiedades
inmutables:
la versin se consulta a travs de clientResponse.httpVersion, que es
idntica a su homnima en serverRequest.httpVersion
clientResponse.statusCode contiene el nmero de tres dgitos del cdigo
de respuesta
El resto de cabeceras se encuentran en la propiedad clientResponse.headers,
siguiendo la lnea marcada por la librera. Al igual que las cabeceras contenidas en el Trailer, accesibles desde response.trailers una vez recibida toda la
respuesta del servidor.
Si la respuesta incluye una entidad, sta estar disponible conforme se vaya recibiendo, de la manera en que se leen los datos en un Stream: con el evento
data.
clientResponse.on(data, function(datos){ });
Por supuesto, los eventos (end, close) y mtodos (pause(), resume(),
setEncoding()) propios de un Stream de slo lectura tambin estn disponibles
para su utilizacin.
109

5.3.

Aplicacin con HTTP

En esta prctica se dotar al servidor RTP de una interfaz web que controle la lista de reproduccin de una IP de broadcast concreta, protegida por contrasea. Se
presentar en una pgina unos sencillos botones de reproduccin, pausa y adelante/atrs junto con una lista de las canciones disponibles. Los efectos de estas
acciones sobre la lista de canciones se notarn en todos los clientes que pertenezcan al grupo de broadcast, con lo que puede usarse como panel de administracin
de la difusin del contenido.

5.3.1.

Descripcin del problema

Los requisitos bsicos de la aplicacin se describen a grandes rasgos a continuacin. Se debe tener en cuenta que el funcionamiento es realmente sencillo.

El cliente administrador deber ser capaz de obtener la interfaz a travs de un


navegador web moderno (por tanto, estar programada en html), y sta debe presentar un formulario cuyos botones realicen las acciones requeridas: play (o pause en caso de que ya se est reproduciendo), siguiente pista y pista anterior. El
resultado de pulsar uno de dichos botones devolver la interfaz web actualizada
con el estado actual del reproductor.
110

Figura 5.1: Interfaz de la Solucin HTTP

Para que slo el administrador de la lista de canciones en una determinada IP


tenga acceso a la interfaz web, sta estar protegida por contrasea. El nombre
de usuario al que se asocia la contrasea, por convencin, ser la IP de broadcast
sobre la que se acta. El servidor de listas de reproduccin debe determinar si
esta contrasea para la IP a la que se est transmitiendo la lista en streaming es
correcta y, si procede, enviarle la interfaz.

Figura 5.2: Autenticacin en la Solucin HTTP


111

5.3.2.

Diseo propuesto

Esta prctica es parecida a la prctica anterior, por lo que habr similitudes en la


forma que tienen los distintos mdulos de la aplicacin para comunicarse entre
ellos. Con la intencin de separar las distintas partes de la aplicacin, se propone
aislar la funcionalidad del servidor HTTP de Listas de Reproduccin en el mdulo
ListHttpServer.js que se relaciona con el resto de mdulos del sistema segn
el siguiente esquema de la arquitectura:

Figura 5.3: Diagrama de colaboracin de la Solucin HTTP

El mdulo que interesa, ListHttpServer.js, proporcionar una funcin especfica para la creacin de este servidor, create(), que simplemente devuelve un
servidor HTTP preparado para escuchar los eventos concretos necesarios para la
prctica y capaz de atender lo que solicitan devolviendo la respuesta correcta al
cliente.
Como la finalidad de un objeto ListHttpServer es controlar el Reproductor asociado a una IP broadcast, ser necesario proporcionarle una lista de IPs de broadcast con los reproductores asociado a ellas. De esta manera con un slo servidor
ListHttpServer se gestionan los reproductores de todas las listas:
var listServer = http.createServer();
exports.create = function(db){
if (db == null) throw new Error(Database cannot be empty);
listServer.broadcastList = db;
return listServer;
}
112

La interfaz del Reproductor es sencilla, posee un mtodo por cada accin que se
quiera realizar sobre l (play, pause. . . ). Los mtodos admiten una funcin de
callback que se ejecutar cuando se haya terminado de realizar la accin, de esta
forma, por ejemplo, se podr enviar la respuesta al cliente con la seguridad de
que la accin que ha solicitado se ha llevado a cabo.
Como ListHttpServer slo tiene como propsito procesar peticiones web con los
mtodos habituales, GET y POST, se atender exclusivamente al evento
request:
listServer.on(request, function(req, res){
switch(req.method){
case GET:
// GET requests processing code
break;
case POST:
// POST requests processing code
break;
}
})
El cdigo asociado al evento ser el que procese el tipo de peticin. Los requisitos
que se exigen a esta aplicacin implican solamente a dos de ellos:
obtener la interfaz y elementos estticos involucra peticiones de tipo GET.
interactuar con la aplicacin mediante un formulario, en este caso los botones del Reproductor, involucra peticiones de tipo POST.
Ante cualquier peticin, la primera decisin a tomar es permitir o denegar el acceso a la pgina principal del reproductor, protegindola con una contrasea. Solamente el administrador de la lista de reproduccin de la direccin de broadcast
concreta debera ser capaz de controlar la lista. Se tomar por convencin que el
nombre de usuario sea la IP que se administra y la contrasea ser password, a
modo de ejemplo. Por ejemplo, si la emisin broadcast se realiza en la direccin
224.0.0.114, habr una tabla usuario/contrasea como la siguiente:
var allowedList = {
"224.0.0.114": "password"
}
113

El tipo de autenticacin a utilizar es la ms sencilla: Basic. Cada vez que se reciba una peticin, se comprobar pues que existe la cabecera Authorization y
que las credenciales que se presentan en ella son las correctas. En caso de no serlo se devolvera el cdigo de estado adecuado para este caso: 401 Unauthorized.

var credentials = req.headers["authorization"];


var userAndPass, broadcastIp, pass;
if (credentials){
userAndPass = new Buffer(credentials.split( )[1],
base64).toString(ascii).split(:);
broadcastIp = userAndPass[0];
pass = userAndPass[1];
}
if (!credentials
|| !(broadcastIp in allowedList
&& broadcastIp in this.broadcastList)
|| allowedList[broadcastIp] != pass) {
res.writeHead(401, "Unauthorized", {
"WWW-Authenticate": Basic realm="List Server Authentication"
});
res.end();
return;
}
Si se determina que el usuario es el administrador legtimo de la lista, se procede
a resolver qu solicita. Las opciones son muy limitadas teniendo en cuenta la
sencillez de la aplicacin. En este sentido, pueden pedirsese:
la pgina web principal escrita en html que contiene la interfaz de control
del Reproductor. Por ser el punto de entrada a la aplicacin, la request-line
esperada es GET / HTTP/1.1. Para identificar este mensaje, debe parsearse
correctamente la request-line anterior y determinar que el path que se pide
es /:
var uri = url.parse(req.url);
var path = uri.pathname;
114

Esta pgina debe ser generada dinmicamente utilizando el estado del reproductor (reproduciendo una pista concreta o pausado) en el momento de
la peticin. Se emplear un mtodo privado del mdulo, writeDocument(),
creado para tal efecto.
writeDocument(res, player) escribe en el Stream de respuesta res los
datos que se solicitan al reproductor player. El formato en que los escribe es
html por lo que, si no ha habido ningn error durante el procesado, se debe
fijar la cabecera Content-Type de la respuesta con el tipo MIME adecuado.
Content-Length tambin debe establecerse:
var content = head + info + form + list + tail;
res.writeHead(200, OK, {
"Content-type": "text/html",
"Content-length": content.length
});
res.write(content);
res.end();
algn fichero esttico, como imgenes u hojas de estilo que apliquen un diseo sobre la pgina principal. Estas entidades se solicitan con una peticin
de tipo GET y se diferencian de las anteriores peticiones en que el path es
distinto de /. La ruta en el sistema de ficheros es relativa al directorio donde
reside el script ejecutable del servidor. Se debe tener muy en consideracin
que no se realiza ningn tipo de control sobre la ruta que se recibe en las
peticiones por lo que la aplicacin es totalmente vulnerable a ataques de
directorio transversal.
Si al buscar el fichero, se obtiene un error, se indicar con el cdigo correspondiente, en este caso un 404 Not Found, indicando que no se ha encontrado el recurso. Si, por el contrario, se ha localizado y se procede a enviarlo,
se dejar que el cdigo de respuesta lo genere Node implcitamente y lo nico que se fijar sern las cabeceras Content-Type y Content-Length con
response.setHeader().
fs.readFile("." + path, function(error, data){
if (error){
res.writeHead(404, Not found);
res.end();
115

return;
}
var fileExtension = path.substr(path.lastIndexOf(".") + 1);
var mimeType = MIME_TYPES[fileExtension];
res.setHeader("Content-Type", mimeType);
if (mimeType.indexOf("text/") >= 0){
res.setHeader("Content-Encoding", "utf-8");
}
res.setHeader("Content-Length", data.length);
res.write(data, "binary");
res.end();
});
que se realice una accin sobre el reproductor. Proviene del formulario que
presenta la interfaz html:
<form method="post" action="/">
<input type="submit" value="prev" name="action">
<input type="submit" value="play" name="action">
<input type="submit" value="next" name="action">
</form>
as que se espera una peticin de tipo POST cuyo contenido es un nico campo action. El valor de action, adems de indicar qu accin tomar, coincide
con el nombre del mtodo a invocar del reproductor que se controla. Con
esta convencin, se pretende simplificar la manera de llamar al mtodo, de
tal forma que slo sea cuestin de:
if (action in player) {
player[action](function(){
writeDocument(res, {group:broadcastIp,
paused:player.paused,
tracks:player.list(),
currentTrack:player.currentTrack()});
});
}
Procesar esta peticin implica que el servidor espera un cuerpo de mensaje de
longitud conocida que, en condiciones normales, est disponible inmediatamente
116

en el servidor porque su tamao es mnimo. An as, no debe darse la peticin


por recibida hasta que el cliente no la finalice. Por tanto se deben atender dos
eventos:
data para recibir los datos del formulario
var body = ;
req.on(data, function(data){
body += data;
})
end como indicador de que debe procesarse el formulario. Esto se traduce
bsicamente en interpretar la query recibida para lo que se har uso del
mdulo querystring del core de Node.
req.on(end, function(){
var action = querystring.parse(body).action;
var player = self.broadcastList[broadcastIp];
if (action in player) {
player[action](function(){
writeDocument(res, player)
})
}
})
Como resultado se espera la misma pgina web que se genera cuando se accede
al reproductor, pero actualizada con el estado en que se encuentre el reproductor
(parado, en el siguiente track. . . ) despus de la accin. A tal efecto ser til de
nuevo la funcin writeDocument() que se invocar en el callback que se pasa
como argumento al Reproductor, puesto que la pgina html se debe enviar una
vez ejecutada la accin.

5.4.

Objetivos de los Koans

A travs de los Koans asociados al tema HTTP, se pretende obtener un conocimiento del mdulo para la creacin de sencillos servidores HTTP y cmo se
abordan aspectos bsicos del protocolo. Para la creacin de aplicaciones ms
complejas existen mdulos de terceras partes que hacen ms fcil el manejo de
117

caractersticas ms avanzadas de HTTP. Alguno de ellos se tratar en el tema


siguiente. Por lo pronto, se analizarn los siete Koans del tema.
1. Saber que request es el principal evento que puede atender un servidor
HTTP:
server.on(___, function(req, res){})
2. Extraer las cabeceras de la peticin que se necesiten del objeto headers
propiedad del objeto request:
var credentials = request.___[authorization];
3. Conocer el mdulo url y ser capaz de diseccionar las URLs que las peticiones
contienen en la request-line con parse()
var url = require(url);
var uri = url.___(req.url);
4. Ser capaces de identificar qu mtodo de la peticin est empleando el cliente
a travs de la propiedad method
switch(req.___){
case GET:
break;
case POST:
break;
}
5. Generar una respuesta implcita incluyendo las cabeceras necesarias con
setHeader()
res.___("Content-Type", "text/css");
6. Conocer el mdulo querystring y ser capaz de diseccionar con l la query de
una peticin
var querystring = require(querystring);
var query = querystring.___(body);
118

5.5.

Preparacin del entorno y ejecucin de los Koans

Como es habitual, se requiere el mdulo koanizer, proporcionado con la prctica,


para poder ejecutar los Koans de manera satisfactoria.
El fichero que contiene los koans es http-koans.js, que es en realidad el mdulo
ListHttpServer, que contiene la totalidad de la lgica que trata con HTTP en la
aplicacin. Como en todas las prcticas anteriores debe ser editado y completado,
sin que quede ningn hueco ___.
Para verificar que se han realizado con xito los koans se ejecutan los casos de
prueba con:
$ jasmine-node -verbose spec/
Como resultado de una correcta resolucin de los mismos, la prctica ser completamente funcional y se podr ejecutar con el comando:
$ node app.js
Como en todas las prcticas que incluyen al protocolo RTP para generar contenido
de audio en streaming, se debe configurar un Reproductor tal y como se indic en
el apartado Descripcin de la aplicacin del captulo Mdulos dgram y buffer.
Puesto que el objetivo de la prctica es la gestin del audio que se emite en un
grupo concreto multicast, hay que volver a fijar la direccin IP a la del grupo que se
ha tomado como ejemplo en la prctica: el 224.0.0.114 con puerto 5002. Huelga
volver a describir los pasos a seguir para hacerlo.
Para realizar operaciones sobre la lista, se debe apuntar un navegador a la URL
http://localhost:8080, en caso de que se est ejecutando en local. A partir
de este punto el resto est suficientemente explicado a lo largo de los apartados
anteriores.

5.6.

Conclusin

En este captulo se ha fijado el manejo a travs del mdulo http de las caractersticas de ms bajo nivel del protocolo HTTP: recepcin de peticiones atendiendo al
evento request identificando en ellas los parmetros imprescindibles como el
mtodo empleado method o sus cabeceras headers. Con toda esta informacin se
119

ha podido ver cmo generar una respuesta adecuada, por ejemplo, estableciendo
tambin unas cabeceras concretas con setHeader().

120

Captulo 6

Express
6.1.

Connect

Connect es un framework middleware, lo que significa, en palabras de su propio


autor [50], TJ Holowaychuk, que es una capa intermedia que proporciona a los
desarrolladores de Node una solucin efectiva de alto rendimiento para un rpido
desarrollo usando componentes intercambiables denominados middleware. Estos
componentes son mdulos, semejantes al resto de los mdulos de Node, que se
pueden encadenar unos con otros en el orden que se desee formando una pila de
mdulos (stack). Este stack es la capa intermedia de la que se hablaba, porque
se sita encima de Node y sus mdulos y debajo de la aplicacin.
El autor divide en dos categoras estos componentes:
Filtros (filters), que son mdulos que se pueden colocar arbitrariamente en el
stack, y que procesan el trfico, tanto entrante como saliente, sin responder
a las peticiones. El ejemplo tpico es el middleware log, que registra el contenido de las peticiones sin responder, dejando que sea la aplicacin la que
provea esa lgica.
Proveedor (provider), representa un endpoint en el stack, lo que implica que la
peticin no progresa en caso de que deba ser atendida por el mdulo. Por
ejemplo, el middleware json-rpc slo responde a peticiones cuyo Content-Type
sea application/json, dejando pasar hasta nuestra aplicacin el resto de
peticiones.
121

Hasta la fecha, en su versin 2.7, Connect posee ms de 20 middlewares que


ofrecen una variada funcionalidad:
logger
Trazas de log, por defecto a la salida estndar pero redirigible a cualquier
Stream. La informacin a registrar se ofrece a travs de tokens a los cuales
puede drseles un formato de presentacin. Tanto los tokens como el formato, aparte de haber algunos predefinidos, pueden ser personalizados por el
desarrollador.
Por ejemplo, el formato tiny loguea el mtodo, la URL, el status, el campo
Content-Length de la respuesta y el tiempo en milisegundos que tarda, a
travs de sus correspondientes tokens:
:method :url :status :res[content-length] - :response-time ms
csrf
Prevencin del ataque Cross-Site Request Forguery [51].
Mediante este ataque, una pgina maliciosa puede enviar una peticin a un
servidor vulnerable si conoce la URL y los parmetros de la query con los que
realizar la peticin. Normalmente la URL se inserta en la pgina maliciosa
como un recurso, de tal manera que ser el navegado el que la solicite sin
intervencin del usuario, por ejemplo:
<img src="http://bank.com/transfer?amount=1000&to=Malicious"/>
Si hay alguna sesin abierta con la pgina vulnerable, el navegador realizar
la peticin a travs de esa sesin, ejecutndose con xito el ataque.
Para mitigarlo en la manera de lo posible, una de las soluciones, que es la
que implementa este middleware, consiste en generar un token por sesin
de usuario, aleatorio e impredecible, que se almacenar en la sesin del
usuario. Este token ser uno de los campos de los formularios con los que
se vaya a realizar una transaccin. Si la transaccin es legtima y se realiza
dentro del marco de la sesin actual, el token del formulario y el almacenado
sern idnticos. Si no, se generar un error 403 Forbidden y se frustar el
ataque.
Dependiente del middleware: sesion y cookieParser.
compress
122

Compresin gzip y deflate de la entidad que se transmite en la respuesta, a menos que se el campo Content-Encoding se haya definido como
identity

(sin

compresin).

Modificar

aadir

la

cabecera

Content-Encoding.
basicAuth
Soporte para la autenticacin HTTP bsica.
Admite por parmetros, o bien un usuario/password especfico contra el
cual realizar la validacin o una funcin que realice el proceso de autenticacin. Esta funcin es susceptible de contener cdigo propio del desarrollador
para la validacin contra una base de datos por ejemplo, y devolver true
cuando el proceso haya sido sastisfactorio.
bodyParser
Procesa el cuerpo de las peticiones con soporte para los tipos MIME
application/json,

application/x-www-form-urlencoded

multipart/form-data.
En realidad es equivalente a utilizar los mdulos json, urlencoded y multipart
aunque no hay que incluirlos explcitamente en el stack.
Dependiente del middleware: urlencoded, multipart y json.
json
Procesa el cuerpo de una peticin web en formato JSON, definido por su
Content-Type: application/json, hacindolo disponible como un objeto
en la propiedad req.body.
urlencoded
Procesa una peticin del tipo application/x-www-form-urlencoded generando el objeto req.body en la peticin.
Este es el tipo MIME que se aplica por defecto a los datos que se envan a
travs de un formulario HTML, mediante peticiones tanto GET como POST.
A estos datos se les aplican unas reglas de codificacin especificadas en el
estndar del W3C que son las que definen la estructura de una query, ya
conocidas, como que los espacios se sustituyen por +, los caracteres no
alfanumricos por su cdigo ascii precedido de % de tal manera que tienen
la forma %HH, o que los pares nombre/valor se separan con & de otros
pares y con = entre ellos [52, Section 17].
123

multipart
Procesa

los

cuerpos

de

las

peticiones

multiparte,

de

tipo

MIME

multipart/form-data.
Este tipo se aplica a los datos que se envan cuando el tamao de stos es demasiado

grande

como

para

enviarlo

como

application/x-www-form-urlencoded. Esto sucede, por ejemplo, con archivos binarios donde para codificar cada octeto, hacen falta tres caracteres
( %HH), lo cual triplica la cantidad de datos a enviar siendo altamente ineficiente.
En los mensajes multiparte, como su nombre indica, hay varias partes separadas con un limitador (una cadena de caracteres tal que no se encuentre en el cuerpo de ninguna de las partes que se transmite). Cada una de
las partes tiene su juego de cabeceras, como su propio Content-Type o su
Content-Transfer-Encoding.

Destaca

sobre

ellas

la

cabecera

Content-Disposition que, aparte de tener valor fijo (form-data) incluye


el atributo name que da nombre a la parte [53].
cookieParser
Procesa el campo Cookie de la cabecera HTTP. De esta manera, podemos acceder a las cookies a travs de las propiedades req.cookies o
req.signedCookies que el middleware incluye en la peticin req. Estas
propiedades son un objeto cuyas claves son el nombre de la cookie y sus
valores, el contenido de las mismas.
Como se ve, se puede activar el soporte para cookies firmadas pasando la
clave al middleware como cadena de caracteres. Esto har que la propiedad
req.secret est tambin disponible en req. Las cookies firmadas [54] son
cookies a las que a su contenido se le aade un cdigo comprobacin en
forma de hash criptogrfico para evitar que se alteren en el cliente. El hash
se calcula con el contenido del mensaje, mediante el algoritmo sha256 y se
codifica posteriormente en base64.
session
Implementa un completo sistema de gestin de sesiones las cuales residiran
en memoria, por tanto, como se advirte, no apto para usar en produccin.
Las sesiones son un mecanismo que se implementa en el lado del servidor
para que la aplicacin web mantenga cierta informacin relativa al cliente y
124

su interaccin con ella. Ya que, como se recordar, HTTP es un protocolo sin


estado y, por tanto, incapaz de cumplir esta funcin.
Con Connect, la informacin se estructura como un objeto JSON que se
guarda en la peticin como la propiedad req.session. En ella se pueden almacenar los datos que se quiera, aadindolos como propiedad suya, con la
seguridad de que estarn disponibles para su uso en posteriores peticiones
provinientes del mismo cliente.
Dependiente del middleware: cookieParser.
cookieSession
Habilita el mecanismo de sesiones pero guardando la informacin en la misma cookie en lugar de la memoria del servidor [55].
Dependiente del middleware: cookieParser.
methodOverride
Sobreescribe el mtodo HTTP, accesible por req.method, de las peticiones que tengan presente el parmetro _method en la query o la cabecera
X-Http-Method-Override. Esta cabecera es la manera que tienen los clientes de un servicio REST de realizar peticiones a este servicio sin que sean
rechazadas por un proxy.
Se debe recordar que las operaciones CRUD (Create-Read-Update-Delete) en
los servicios REST se basan en los mtodos GET, POST, PUT y DELETE de
HTTP. Los dos ltimos consideran no seguros por los proxies, que pueden rechazar la peticin respondindola con un error 405 Method Not Allowed.
La solucin pasa por generar una peticin GET o POST que incluya en la cabecera X-Http-Method-Override el verbo que realmente quiere emplearse
(PUT o DELETE). Si la peticin se realiza desde un formulario html, se puede
incluir el campo oculto _method con el mtodo a usar:
<input type="hidden" name="_method" value="put" />
Este middleware detectar la presencia de cualquiera de las dos soluciones
y sustituir el mtodo de la peticin por su valor, dejando el mtodo original
disponible en req.originalMethod.
responseTime
Aade a la respuesta la cabecera X-Response-Time indicando el tiempo que
125

ha tardado la aplicacin en procesar la peticin.


static
Aporta la capacidad de servir de manera eficiente fichero estticos, como
imgenes u hojas de estilo, por defecto contenidos en el directorio ./public.
Gestiona automticamente los tipos MIME de los ficheros y los errores que
puedan surgir empleando los cdigos de respuesta apropiados.
staticCache
Habilita una pequea cache de archivos servidos por el middleware static.
En total, mantiene en memoria los 128 archivos menores de 256Kb ms
solicitados al servidor, gestionando las cabeceras HTTP relacionadas con la
cach. Ambas cifras son por defecto y pueden ser cambiadas a travs de las
opciones de staticCache.
No se aconseja su uso, puesto que estar deprecado a partir de la versin
3.0 de Connect.
directory
Genera un listado de los directorios y archivos contenidos en un directorio
raz dado, y lo enva en html, JSON o texto plano, segn acepte el agente de
usuario.
vhost
Facilita la creacin de hosts virtuales basados en nombre [56].
En este caso, los hosts virtuales son aplicaciones web que corren en una
misma mquina con una sola direccin IP. Se accede a cada host virtual
creando un alias (CNAME) en el DNS apuntando a la mquina. El nombre
del host es una de las cabeceras HTTP (Host) y es en lo que se basa este
middleware para dirigir la peticin.
favicon
El favicon es un icono que los navegadores solicitan al servidor de la pgina
o aplicacin web para identificarla en la barra de direcciones, pestaa del
navegador y marcadores.
Este middleware ofrece el icono que est en el path que se pasa por parmetro. Si no se especifica, muestra uno por defecto.
Es til porque el navegador realiza la peticin http://www.hostname.domain:
126

port/favicon.ico automticamente. Sin el middleware la peticin llega al


controlador de las rutas que puede direccionar mal la peticin o no saber
cmo manejarla, produciendo con toda probabilidad un error.
limit
Establece un lmite para el cuerpo de las peticiones. El lmite se pasa por parmetro en cifra o como una cadena de caracteres, soportando expresiones
en kb, mb o gb, por ejemplo, 1.3mb.
query
nicamente hace disponible el objeto req.query como propiedad de la peticin req. Hace uso de la librera url del core de Node y del mdulo qs para
procesar la query.
errorHandler
Adapta las trazas generadas por un Error al tipo MIME que el cliente solicite:
html, json y text (por defecto).
Est pensado slo para entornos de prueba (test), no para produccin.

6.2.

Express

Express, en palabras de sus creadores, es un framework de desarrollo de aplicaciones web minimalista y flexible para Node.js 1 . A pesar de lo que pueda pensarse del trmino minimalista, Express ofrece caractersticas muy potentes, entre
las que caben destacar:
robusto sistema de enrutamiento de peticiones
soporte para generar pginas HTML dinmicas a partir de plantillas con
capacidad de utilizar varios motores de renderizado de vistas
Construido encima de Connect, establece una fina capa de software que enriquece de manera sencilla, elegante y transparente las caractersticas que expona
el mdulo HTTP, abstrayendo de l detalles muy concretos, simplificndolos y
ampliando el rango de caractersticas manejables del protocolo HTTP al desarrollador.
1

http://expressjs.com/

127

Para comenzar a usar Express, como todos los mdulos de Node, debe solicitarse
con
var express = require(express);
que importa el objeto factora para crear aplicaciones Express, cuyo uso difiere
ligeramente del que se ha visto anteriormente parar require (situar bajo un namespace las clases y funciones del mdulo). Ahora, invocando esta funcin se
genera directamente una aplicacin web:
var app = express();
A partir de aqu se dispone de una aplicacin de caractersticas avanzadas, como
las descritas al inicio, que se ejecuta en un entorno, con sus correspondientes
variables de entorno. Adems, sirve como proxy de Connect, es decir, se puede
hacer uso de las caractersticas de Connect invocando el mtodo que corresponda
a travs de la variable que representa la aplicacin Express, en este ejemplo,
app.
Las variables del entorno determinan el comportamiento de varios aspectos de
la aplicacin, sirviendo como configuracin de la misma. Se determina a travs
de ellas si el script est corriendo en el entorno de produccin o de desarrollo,
modo por defecto a menos que el programador o la variable de ejecucin de Node
process.env.NODE_ENV digan lo contrario.
Estas variables se consultan y modifican con:
los mtodos accesores app.get(nombre) y app.set(nombre, valor) para asignar y consultar los valores.
los mtodos app.enable(nombre) y app.disable(nombre) para habilitar
o deshabilitar variables booleanas.
los mtodos app.enabled(nombre) y app.disabled(nombre) que se pueden emplear para consultar si se est utilizando o no la variable nombre.
Las variables que pueden modificarse con estos mtodos corresponden a distintas
caractersticas de la aplicacin que se irn desgranando posteriormente. De una
manera rpida, son las siguientes:
128

mbito

Variables
jsonp callback name

Generacin de respuestas en notacin JSON

json replacer
json spaces

Rutas de las peticiones segn su URL

case sensitive routing


strict routing
view cache

Sistema de generacin de la parte de interfaz (vistas)

view engine
views

Tabla 6.1: Algunas de las variables configurables en los ajustes de Express

Ajustados los valores del entorno de la aplicacin, se puede proceder a configurar


la aplicacin en s. En una primera fase, debe determinarse la cadena de middleware que se quiere emplear en la aplicacin. Este middleware son los componentes que provee Connect, ya que Express los pone a disposicin del programador.
Tambin pueden ser funciones a medida de las necesidades del desarrollador, escritas por l mismo. La configuracin se har con el mtodo app.use([ruta],
funcion) que se invocar las veces necesarias hasta apilar todo el middleware. Es importante saber que la ejecucin del middleware se realiza por orden de
configuracin: primero el que se configura antes. Como ejemplo:
app.use(express.favicon());
app.use(express.static(__dirname + /public));
app.use(express.cookieParser());
La siguiente fase pasa por configurar opcionalmente el motor de renderizado de
plantillas para la generacin dinmica de pginas HTML. Para ello:
se configuran las variables de entorno relacionadas con el renderizado:
views es la ruta del directorio donde se alojan las plantillas que contienen el cdigo de la vista. Por defecto es /views
view engine es el motor de renderizado, el encargado de procesar las
plantillas y generar a su salida el cdigo html
view cache habilita o deshabilita el cacheado de plantillas ya compiladas. Tener en cach una plantilla compilada acelera el renderizado de
la pgina (por eso est activado en produccin)
129

se declaran las variables locales, que son aquellas comunes a todas las plantillas de la aplicacin
se configura qu motor de renderizado va a emplearse para las diferentes
extensiones (en caso de que se usen varios) del recurso que se solicita. El
mtodo para ello es app.engine(extension, funcionDeRenderizado)
La funcionDeRenderizado debe tener una cabecera concreta que admita
los argumentos (ruta, opciones, callback) que Express espera que tenga para
que funcione correctamente. De hecho, muchos motores tienen un mtodo
por convencin que es __express(). Por ejemplo, el motor Jade:
app.engine(jade, require(jade).__express); //CUIDADO: dos
//guiones bajos
Como se ha comentado, Express es un framework muy flexible, por lo que
admite varios motores, siempre que cumplan el requisito anterior. Por defecto se emplea Jade2 .
Por ltimo, para tener la aplicacin perfectamente funcional, deben definirse las
rutas a las que un cliente puede acceder, con qu mtodos HTTP puede hacerlo
y el comportamiento que se espera de la aplicacin. Para ello Express ofrece su
slido sistema de enrutamiento en el que cada mtodo HTTP es una funcin de la
aplicacin cuyo primer argumento es la ruta sobre la que se realiza la peticin (sin
tener en cuenta la parte de la query). Esto significa que en la prctica existirn
las funciones app.get(ruta, [callbacks...], callback), app.post(ruta,
[callbacks...], callback), app.put(ruta, [callbacks...], callback),
app.delete(ruta, [callbacks...], callback), etc...
El array de callbacks intermedios contiene funciones middleware que actuarn
sobre la ruta en acciones muy concretas. El ltimo argumento callback es el
manejador de la ruta y contiene la lgica a ejecutar cuando se realiza una peticin
a ella.
Si una ruta admite todos los mtodos entonces puede abreviarse con app.all().
La potencia de Express en este mbito radica en que las rutas pueden ser expresiones regulares. De hecho, una ruta identificada por una String se compila
a una expresin regular. Adems, se pueden parametrizar mediante tokens. Los
2

http://jade-lang.com/

130

tokens representarn partes variables dentro de la estructura concreta de una


URL. Con esto se da flexibilidad a la definicin de la ruta y adems se hace disponible esa parte a la lgica de la aplicacin a travs de la propiedad params del
objeto Request que modela la peticin. Un token se define con un identificador
que comienza con dos puntos :. Cuando llega una peticin, Express analiza
la URL y extrae de ella el valor del o los tokens. Ejemplo de ruta parametrizada
puede ser:
/users/:uid/friends/:friendname
que abarcara rutas como
/users/34123/friends/arturo o /users/87345/friends/irene.
Una vez definidas las rutas, stas quedan reflejadas en el objeto app.routes
desde donde se pueden consultar.
La funcin de callback que atiende las peticiones es similar a los listeners del
mdulo http: admite como argumentos la peticin y la respuesta de la transaccin.
Express modela ambas como los objetos Request y Response que son una versin
enriquecida de los objetos ServerRequest y ServerResponse del mdulo http de
Node, es decir, las propiedades de ambos sern las que tienen ambas clases ms
las que el distinto middleware que se ha configurado con Connect y el propio
Express hayan aadido.

6.2.1.

Request

Request es el modelo que Express proporciona para las peticiones HTTP. A travs
de l, el desarrollador tiene acceso de una manera sencilla a gran variedad de
informacin que el cliente enva en las cabeceras, sin tener que incluir lgica
adicional para procesarlas.Request proporciona soporte para las siguientes:
Accept
Express disecciona los tipos que el cliente acepta en la peticin y construye
req.accepted, un array donde estn todos ordenados segn prioridad del
cliente. El desarrollador puede comprobar si el cliente acepta unos tipos determinados (separados por comas) con req.accepts(tipos), que devuelve
el tipo mejor soportado de todos.
Accept-Charset
131

Express disecciona esta cabecera y genera el array req.acceptedCharset


con los juegos de caracteres que admite el cliente ordenados por preferencia.
Tambin ofrece la facilidad req.acceptsCharset(charset) para determinar si el cliente aceptara un charset concreto.
Accept-Language
al igual que en los dos casos anteriores, Express disecciona esta cabecera para obtener un array ordenado segn las preferencias del cliente con los
idiomas

preferidos

de

ste.

De

manera

anloga,

con

req.acceptsLanguage(idioma) se puede consultar si el cliente acepta un


determinado idioma.
Content-Type
de existir esta cabecera, con req.is(tipo) se puede comprobar si el cuerpo
de la peticin es ese tipo.
Cookie
a travs del middleware cookieParser() de Connect, Express disecciona
las cookies de la peticin y las almacena en req.cookies y en
req.signedCookies en caso de que sean seguras.
Host
la informacin que Express extrae de esta cabecera es el nombre del host
req.host al que se dirige la peticin, sin especificar el puerto en caso de ser
ste distinto de 80. Adems, todos los subdominios que haya en el Host se
meten ordenados en req.subdomains
Last-Modified y ETag
el cliente emplea estas cabeceras en peticiones que impliquen entidades que
tenga cacheadas, llamadas cache-conditional requests. La RFC2616 [49, Section 13.3.4] establece las pautas que un cliente debe seguir para incluirlas
en una peticin. En base a stas, Express determina si la entidad solicitada que el cliente posee es reciente, req.fresh es true, u obsoleta, con
req.stale.

6.2.2.

Response

Response es el modelo con el que Express modela las respuestas HTTP.


132

Como con el mdulo http de Node, se puede establecer el status code de la respuesta, esta vez a travs del mtodo res.status() en lugar de modificar la propiedad res.statusCode de la clase ServerResponse del mdulo http. Hay que
recordar que modificar el status code slo poda hacerse en los casos en que la
respuesta tena cabeceras implcitas, es decir, se haba comenzado a generar la
respuesta sin establecer explcitamente unas cabeceras. Adems en ese caso y en
este, slo poda modificarse el status code si la respuesta no se haba comenzado
a enviar an. Es por ello que el res.status() que ofrece Express es encadenable:
a continuacin de l se pueden invocar el resto de mtodos de res.
Adems, en el caso de tener que realizar redirecciones, Express ofrece facilidades
con res.redirect([responseCode], url), que redirecciona al cliente a la url
(absoluta o relativa a la aplicacin) con un cdigo 302 Found por defecto, pero
que puede cambiarse con el argumento responseCode.
Algunas cabeceras se pueden manejar directamente a travs de mtodos especficamente hechos para ello:
Content-Type
Express establece muchas veces el tipo MIME del contenido que se enva de
manera automtica pero el criterio del desarrollador prevalece si se emplea
la funcin res.type(tipo). Adems, si la clase del tipo es texto (text/*),
el juego de caracteres se puede especificar con la propiedad res.charset.
Cookie
se puede enviar una cookie al cliente con res.cookie(nombre, valor,
[opciones])
El resto de cabeceras se deben fijar con los mtodos accesores res.get(cabecera)
y res.set(cabecera, valor). En este ltimo caso se pueden fijar ms de una
si se pasa un nico argumento con las cabeceras en notacin JSON.
A la hora de enviar el cuerpo con la entidad solicitada, Express ofrece varias formas de hacerlo dependiendo de cmo se est generando la respuesta. La manera
ms general es con res.send([statusCode], cuerpoRespuesta) que enviar
respuestas que no sean de tipo Stream, es decir, ya estn calculadas y se pueden
enviar inmediatamente, por ejemplo, un objeto JSON o un Buffer. No entran en
esta categora archivos o pginas dinmicas.
Si lo que se pretende es enviar un archivo a travs del Stream, Express proporcio133

na

un

mtodo

especfico

para

ello:

res.sendfile(ruta, [opciones],

[function(error){}]). Este mtodo enva el archivo ubicado en la ruta del


sistema de ficheros (o relativa a opciones.root, si se especifica) fijando automticamente el tipo segn la extensin del mismo y su tiempo de cach si se indica
en opciones.maxAge.
Para el caso de archivos html dinmicos, que necesitan ser procesados por algn
motor de renderizado existe el mtodo res.render(vista, [locales],
function(error, html){}), al que se le indica como primer argumento el nombre de la vista.
La principal motivacin de las pginas dinmicas es la de incluir trozos de cdigo
que se ejecuten durante el renderizado. Este cdigo, en su mbito de ejecucin,
puede hacer uso de variables compartidas con la lgica de la aplicacin y cuyo
valor se fija durante la ejecucin de los mtodos de la misma. Estas variables,
las locales, sern visibles para la pgina dinmica siempre que se pasen como
argumento a res.render() en un objeto JSON.

6.3.

MongoDB

La base de datos no relacional MongoDB es uno de tantos proyectos ligados al


movimiento NoSQL. Este movimiento, como su nombre indica, provee soluciones
alternativas a las bases de datos donde se emplea SQL (Structured Query Language) como lenguaje de consulta. Con SQL se realizan peticiones a bases de datos
donde la informacin se estructura en base a modelos de datos relacionales: las
distintas entidades que representan esos modelos se almacenan en tablas entre las que se establecen relaciones. Una caracterstica muy importante de estas
bases de datos es cmo tratan las transacciones (grosso modo, conjuntos de operaciones) que se realizan sobre los datos. stas siguen un principio cuyo nombre
indica cmo deben ser: ACID (Atomic Consistency Isolate - Durability). Segn este principio, una transaccin debe ejecutarse completamente o no hacerlo
(Atomicidad), dejando la base de datos ntegra, no corrupta (Consistencia), sin interferir con otras transacciones (Aislamiento) y dejando los datos resultantes en
un estado permanente (Durabilidad).
Sin embargo, el modelo de datos de NoSQL es mucho ms flexible, existiendo varias formas de organizar la informacin: clave-valor, clave-documento o BigTable,
134

por citar algunas. Esta flexibilidad se ve reflejada tambin en la manera de realizar las consultas: ahora, en lugar de usar un lenguaje declarativo, perfectamente
preciso, como SQL, se deja a la lgica de la aplicacin realizar las operaciones
sobre los datos directamente, con la intencin de tener un mayor control sobre el
rendimiento de la consulta.
Son precisamente caractersticas como stas la que suponen sacrificar aspectos
del principio ACID, por ejemplo, la Atomicidad, slo garantizada a nivel de documento, o el Aislamiento, ya que cualquier dato ledo por el cliente puede estar
modificado por otro cliente concurrente [57]. An as, es cierto que algunos de
los mecanismos se suelen poner a disposicin del desarrollador para mitigar lo
mximo posible esta carencia [58, Chapter 13].
Decantarse por el uso de NoSQL frente a SQL es una cuestin de anlisis del problema: en general, se recomienda usar una base de datos no relacional cuando
se tienen grandes cantidades de datos y/o se encuentran dificultades para modelarlos segn un esquema. De otra manera, SQL es una tecnologa muy madura
con muchsimos aos de investigacin y desarrollo sobre ella [58].
MongoDB entra dentro de la categora de bases de datos orientadas a documentos
[59]: las entidades no son filas en una tabla sino un conjunto de pares clave-valor
(campos), denominado documento, que a su vez se organiza en colecciones. Las
consultas se realizan solicitando documentos cuyos campos cumplan uno una
serie de criterios determinados.
Los autores del proyecto definen MongoDB como si fuera una base de datos relacional a la que nicamente se le ha cambiado el modelo de datos manteniendo
otras caractersticas que tradicionalmente han funcionado muy bien, como ndices o updates. Los puntos fuertes que destacan de MongoDB, segn los autores
[60], son:
flexibilidad, por usar como modelo de datos documentos en notacin JavaScript
(JSON). Estos documentos no tienen un esquema fijo, como sucede con los
documentos XML, ms rgidos en el sentido que los elementos deben ajustarse al esquema establecido para ellos, y como sucede en las bases de datos
relacionales, donde un elemento a insertar en una tabla debe cumplir las
condiciones fijadas para las columnas de dicha tabla.
potencia, por incorporar mecanismos propios de bases de datos relacionales que
se suman al resto de ventajas. Entre ellos, los autores destacan los ndices
135

secundarios, consultas dinmicas, ordenacin o operaciones upsert (update si el documento existe, insert si no)
velocidad, porque agrupa toda la informacin realcionada en documentos en
lugar de en tablas como ocurre en las bases de datos relacionales
escalabilidad horizontal, es decir, posee mecanismos para que sea muy fcil
y seguro, sin interrupciones de servicio, aadir ms mquinas corriendo
MongoDB al cluster
facilidad de uso, por la sencillez con la que se instala y configura MongoDB,
teniendo una instancia corriendo en muy pocos pasos.
Los criterios seguidos en el proyecto para elegir MongoDB sern el primero y el
ltimo de los puntos anteriores ya que, de momento, no se busca gran potencia ni
escalabilidad. Adems sirve de introduccin a una tecnologa que, a pesar de sus
detractores, sigue creciendo y ganando popularidad, habindose adoptado en sitios y aplicaciones web de gran popularidad como Craiglist3 o Foursquare4 .
La integracin entre MongoDB y Node se hace a travs de Mongoose5 , uno de los
drivers de la base de datos para esta plataforma. Mongoose ofrece al desarrollador
una forma sencilla de modelar los objetos de su aplicacin y realizar sobre ellos
las operaciones que permite MongoDB.
Usar Mongoose en una aplicacin web es bastante fcil y slo requiere un par de
pasos:
instalar el driver, por supuesto habiendo instalado ya Node y Express, con
npm install mongoose
importar el mdulo en el cdigo de la aplicacin de la manera habitual
var mongoose = require(mongoose);
conseguir una conexin con el servidor MongoDB
mongoose.connect(IPServirdor, nombreBaseDeDatos);
mongoose.on(error, function(error){});
3

http://www.craiglist.org
https://foursquare.com
5
http://mongoosejs.com/
4

136

A partir de este punto, si no se ha obtenido ningn error, el desarrollador ser


capaz de crear objetos a partir de un modelo. Este modelo sigue un esquema
con el que se guardarn sus instancias en la base de datos. Un esquema puede
verse como la definicin de una coleccin ya que define la forma que tendrn los
documentos de dicha coleccin.
Todo lo anterior no implica que se incurra en una contradiccin con la caracterstica schemaless (sin esquema) que MongoDB propone, sino que un esquema
es una manera de estructurar los datos frente a la libertad que MongoDB ofrece
para hacerlo o no. Lgicamente, en la solucin a un problema real es deseable
uniformidad en el modelo de datos. An as, estos esquemas se pueden modificar
de una manera fcil y rpida.
Por tanto, el ltimo paso antes de realizar operaciones con la base de datos
es:
// Generar el esquema de una coleccion
var miEsquema = mongoose.Schema({miPropiedad: tipoDeDato});
// Generar el modelo del documento como un constructor Javascript
var MiModelo = mongoose.Model(MiModelo, miEsquema);
Por supuesto, este patrn de cdigo se utilizar tantas veces como sea necesario
para hacer uso de todos los modelos que se hayan estimado necesarios en la
arquitectura de la aplicacin.
En este momento la lgica de la aplicacin puede hacer uso de los documentos
de la coleccin instancindolos de la manera clsica:
var miModelo = new MiModelo({miPropiedad: valor});
La instancia creada posee mtodos que interaccionan con MongoDB. Los ms
comunes son:
miModelo.save([function(error, miModelo){}])
con el que se guardar el documento miModelo en la base de datos, tanto
como si es de reciente creacin como si se est realizando una actualizacin
(update). Emplear este mtodo es ms usual si previamente se ha realizado
una bsqueda o se acaba de instaciar el objeto, ya que en caso de querer
actualizarlo se recomienda utilizar el mtodo MiModelo.update() sobre el
modelo del documento, no sobre la instancia en s.
137

miModelo.remove([function(error, miModelo){}])
con el que se borra el documento de la base de datos. An resultando con
xito, el callback opcional recibe una copia del objeto recin eliminado como
argumento.
Asimismo, algo fundamental es la localizacin de documentos a travs de consultas (queries), una operacin que se hace a travs del modelo del documento en
lugar de la instancia del mismo, a diferencia de los casos anteriores:
MiModelo.find(query,
[condiciones],
[opciones],
[function(err, resultSet){}])
o si slo se quiere encontrar un documento:
MiModelo.findOne(query,
[campos],
[opciones],
[function(err, resultSet){}])
El argumento query contiene las condiciones que deben cumplir los documentos
para formar parte del resultado de la bsqueda. En concreto se refieren a valores
de uno o varios campos de ste.
El argumento campos es una String opcional con el nombre de los campos del
documento, separados por un espacio, que se quieren obtener si es lo que se
desea en lugar de obtener el documento completo.
Las opciones por su parte no estn relacionadas con la finalidad de la bsqueda
en s sino con el resultado de ella, por ejemplo, limitarla a un nmero determinado
de resultados o ordenarlos con algn criterio.
Un ejemplo de query sobre un documento que representa a un usuario, podra
ser:
UserModel.find(
{username : /^a\w+/, age: {$gt: 30}, accountType: premium},
username email,
function(error, resultSet){}
);
138

que devolver como resultado un conjunto resultSet de nombres de usuario y


su email asociado de cuentas premium de usuarios cuyo nombre comience por
la letra a y tengan edades superiores a 30 aos.
La funcin de callback del cuarto argumento tambin es opcional y si no est
presente, el mtodo find() slo definira la consulta, que se ejecutara ms tarde
con el mtodo exec():
var consulta = miModelo.find({});
// Lineas de codigo adicionales
consulta.exec(function(error, resultSet){}));
Las funciones relativas a bsqueda son encadenables, es decir, pueden concatenarse con funciones que filtren el resultado, de tal manera que la consulta
anterior podra escribirse tambin as:
UserModel.find(
{username : /^a\w+/, accountType: premium},
username email)
.where(age)
.gt(30);
Esta mnima introduccin a Mongoose debera ser suficiente para comprender la
dinmica de uso de la base de datos. El API del driver es extenso y muy flexible
pero muy sencillo de utilizar, por lo que se recomienda su lectura en los casos en
que las consultas y operaciones requieran mayor detalle.

6.4.

Aplicacin de ejemplo

Se va a realizar una versin reducida de la popular red de micro-blogging Twitter a la que se denominar Whizr. As, un usuario de Whizr ser un whizr y las
publicaciones que haga en su perfil sern los whizs. A travs de los tres objetivos propuestos se pretende alcanzar un grado de funcionalidad tal que la haga
utilizable.
Como paso previo, debe tenerse una instancia de MongoDB corriendo en la mquina. En ella se crear la base de datos, a la que se llamar whizr, que emplear
la aplicacin. Para generarla, basta con conectarse con el cliente mongo a MongoDB e introducir a travs del prompt el comando use whizr.
139

Aunque no sera necesario, ya que MongoDB lo hara implcitamente, se crearn


las collecciones que se usarn a lo largo del desarrollo. Una contendr los usuarios, los whizrs, y otra las publicaciones, los whizs. Los dos comandos necesarios
para ello son:
db.createCollection("whizrs");
db.createCollection("whizs");

Figura 6.1: Creacin de las Colecciones en el prompt de MongoDB

6.4.1.

Clon de Twitter, objetivo 1: autenticacin y control de sesiones

6.4.1.1.

Descripcin del Objetivo

El primer objetivo que se debe alcanzar a la hora de crear una red social es aquel
que permite la propia existencia de la red: la posibilidad para un usuario de darse
de alta y acceder a ella.
Por tanto, lo que se pretende alcanzar con este caso de uso es que el usuario:
sea capaz de registrarse o hacer login en la aplicacin, a travs de la pgina
de inicio
acceda a la pgina de su perfil, una vez registrado o satisfactoriamente autenticado
140

tenga la posibilidad de terminar su sesin, una vez haya accedido a su perfil


Estas acciones implican la existencia de:
una pgina principal cuya ruta por defecto sera /.
una

pgina

de

perfil

de

usuario

cuya

ruta

sera,

por

ejemplo,

/nombreDeUsuario
tres endpoints que atiendan las peticiones de crear una cuenta y hacer login
y logout. En este caso determinados por las acciones /register, login
y /logout
Hay que tener en cuenta que el acceso a los endpoints, stos descritos y los futuros, debe ser restringidos en funcin de qu derechos posea el usuario sobre los
mismos, es decir, qu acciones puede o no realizar en funcin de si est logueado
o no o si es el propietario de la cuenta. Para ello se necesita hacer uso de un
mecanismo de control de sesiones (creacin, mantenimiento y destruccin) y la
lgica necesaria para proteger dichos endpoints en base a ese mecanismo.
Por ltimo, se debe articular un modelo de datos que permita manejar los usuarios en este sencillo caso de uso.

6.4.1.2.

Diseo propuesto al Objetivo implementado con Express

Express proporciona un script, express, para el scaffolding que desde lnea de


comandos se encarga de generar la estructura de directorios de la aplicacin.
Esta estructura es muy sencilla, se compone de tres directorios: public, para
los recursos estticos como hojas de estilo o imgenes, routes, con la lgica de
aplicacin que se defina en cada ruta, y views que contine las plantillas con las
vistas que se renderizarn desde las rutas.
Las opciones del comando express son limitadas pero suficientes para conseguir
el mximo grado de funcionalidad en una aplicacin web. Para esta prctica seran tiles las opciones -e, que aade soporte para el motor de renderizado ejs, y
-s, que aade soporte para sesiones.
En definitiva, y como se acaba de comentar, express generar el scaffolding que
indica su salida:
141

Figura 6.2: Scaffolding creado por Express

Para esta aplicacin no se emplear la estructura generada por express, ya que


es muy sencilla, aunque ser una versin ligeramente modificada, por simplicidad, y respetando al mximo las convenciones seguidas por el comando. Para
aplicaciones ms complejas se recomienda el uso del comando.
Como es habitual en las aplicaciones para Node, se empieza importando los mdulos que se van a emplear y, como paso siguiente, se crea una instancia del
servidor Express:
var express = require(express),
ejs = require(ejs);
var app = express();
Antes de empezar a escribir la lgica de la aplicacin se debe configurar el middleware que se va a utilizar teniendo en cuenta los requisitos. stos son:
servir contenido esttico: la pgina de inicio, la hoja de estilo y cdigo javascript para el lado del cliente
necesidad de una capa de manejo de sesiones
facilidades para procesar los parmetros de los formularios en peticiones
POST. Se recuerda que el Content-Type de este tipo de peticiones es
application/x-www-form-urlencoded
142

a priori puede no ocurrrse la necesidad, pero los navegadores actuales solicitan el icono de favoritos, favicon.ico, a la aplicacin lo que interferir
sin duda al controlador de las rutas y con mucha probabilidad causar un
error, as que se incluir esta capa en la pila middleware
Teniendo esto en cuenta, la configuracin que queda es:
app.configure(function(){
app.use(express.favicon());
app.use(express.static(__dirname + /public));
app.use(express.cookieParser());
app.use(express.session({ secret: "Node.js koans" }));
app.use(express.bodyParser());
});
El siguiente paso ahora es definir las rutas y las acciones a realizar cuando se
invoquen pero antes, y puesto que las acciones involucran trabajar con el modelo
de datos de los usuarios de la aplicacin, debe definirse este modelo. Se har en
un mdulo aparte que se importar en el cdigo principal de la aplicacin, sin
capas intermedias que abstraigan la base de datos por dos pequeos motivos:
uno, mantener el foco sobre Node y, dos, para no introducir ms elementos en la
arquitectura de una aplicacin que a priori se antoja muy sencilla.
var whizrSchema = new Schema({
name

String,

username

String,

password

String,

email

String,

emailHash

String

});
Los campos que componen el modelo son los que se aceptan en el formulario
de registro ms uno adicional que contiene el hash MD5 de la direccin de correo
normalizada en minsculas. El motivo es meramente esttico: ese hash sirve para
solicitar al API del servicio de fotos de perfil, o avatares, Gravatar6 una imagen
de perfil para el usuario en caso de tener cuenta en dicho servicio. Esta manera
de obtener automticamente una imagen de perfil introduce una ruta adicional
auxiliar /username/profilepic que atender las peticiones de las imgenes,
6

http://www.gravatar.com

143

redirigindolas a la URL de Gravatar.


Volviendo a las rutas, como se ha descrito anteriormente, se necesita una pgina
principal, servida desde la ruta base /, que ser esttica puesto que su objetivo
es presentarnos un par de formularios:
uno para registrarse, proveyendo los campos username, password, email y
nombre y apellidos
y otro para acceder a la cuenta de usuario, con la identificcin basada en el
username y la password
Estos formularios envan los datos con el mtodo POST a los endpoints /register
y /login. Ambos aceptan parmetros y son rutas de acceso general (no restringido nicamente a usuarios logueados en el sistema) y se encargan de comprobar
que los campos cumplen las caractersticas que se les exigen redirigiendo al usuario donde proceda: en caso de xito, a la pgina del perfil de usuario, y en caso
de error, /register enva un cdigo HTTP 400 Bad Request y /login un
cdigo HTTP 404 Not Found.
Para la accin de registro /register los campos sern vlidos si el nombre
de usuario, username, no est repetido en la base de datos y no excede los 10
caracteres, la password tiene entre 5 y 10 caracteres, y la direccin de correo es
una direccin vlida, es decir, contiene un smbolo @ y un dominio. El nombre
con los apellidos tampoco debern superar los 15 caracteres.
Para la accin de acceder /login, el nombre de usuario debe estar contenido
en la base de datos y adems la password asociada a l debe ser tambin la que
est en la base de datos.
Cuando las condiciones impuestas en la ejecucin de las acciones anteriores se
verifican correctamente, se genera una sesin donde se almacenarn en una variable los datos del usuario para posterior acceso. Chequear la existencia de esta
variable y su contenido servir para saber si una sesin es vlida y quin es el
usuario que la ha iniciado, til para determinar qu permisos tiene.
app.post(/register, function(req, res){
var username = req.param(username);
var password = req.param(password);
var name = req.param(name);
var email = req.param(email);
144

if ( username.length <= 0 ||
username.length >= 10 ||
password.length <= 0 ||
password.length >= 10) {
res.redirect(400); // error
}
models.Whizr.findOne({ username: req.params.username },
function(err, doc){
if (err) {
res.send(Error, 500);
}
if (doc != null) {
res.send(Error, 404);
}
var whizr = new models.Whizr();
whizr.name = name;
whizr.username = username;
whizr.password = password;
whizr.email = email;
var hash = crypto.createHash(md5);
hash.update(email);
whizr.emailHash = hash.digest(hex);
whizr.save(function(err){
if (err) {
res.send(Error, 500);
}
res.redirect(/ + username);
});
});
});

145

app.post(/login, function(req, res){


// En caso de estar logueados
if (req.session.whizr != undefined) {
res.redirect(/ + req.session.whizr.username);
}
models.Whizr.findOne( { username: req.param(username) },
function(err, doc){
if (err) {
res.send(Error, 500);
}
if ( doc.password == req.param(password) ) {
req.session.whizr = doc;
res.redirect(/ + doc.username);
} else {
res.send(Unauthorized, 401);
};
})
});
Una vez establecida la sesin, se realiza una redireccin 302 a una pgina dinmica, servida en la ruta /nombreDeUsuario y generada con el motor de renderizado ejs. Esta pgina muestra la informacin bsica de la cuenta del usuario y
ofrece la posibilidad de terminar la sesin con la accin logout.
La ruta POST restante, /logout, no recibe parmetro alguno y tiene restringida su invocacin nicamente a usuarios logueados en el sistema. Un resultado
satisfactorio de la accin redirige al usuario a su perfil por el mtodo tradicional,
un cdigo 302, y un resultado fallido genera un error.
Como realizar peticiones a determinadas rutas de la aplicacin debe protegerse,
como es este caso, se ha optado por hacer uso de una funcin middleware que
verifique que exista una sesin activa para el agente de usuario, o lo que es lo
mismo, que en la sesin exista el objeto whizr, creado cuando el proceso de login
se realiza satisfactoriamente. De no existir sesin alguna, el usuario recibir el
error apropiado para estos casos: 401 Unauthorized.
La filosofa de la funcin es la misma que cuando se disea un nuevo middleware
para Connect: una funcin que recibe en sus parmetros los objetos peticin,
146

req, y respuesta, res, y tambin el siguiente middleware o funcin a ejecutar.


Por tanto, se invocar antes de la funcin que procesa la peticin.
var checkAuth = function(req, res, next){
req.session.whizr?
next() :
res.send(Unauthorized, 401);
}
Se aade al stack middleware pasndola por parmetro al manejador de la ruta
post():
app.post(/logout, checkAuth, function(req, res){
var redirect = req.session.whizr.username;
req.session.destroy();
res.redirect(/ + redirect);
});
Si el proceso de registro o autenticacin ha sido correcto, la redireccin se hace a
la URL del perfil de usuario que presenta la pgina del perfil generada dinmicamente. Por ello es imprescindible configurar:
app.register(.html, require(ejs));
app.set(view engine, ejs);
app.set(view options, { layout: false });
Esta URL es una ruta parametrizada mediante el token :username que Express
resuelve dinmicamente aplicando la lgica que se ha programado para ella.
El ltimo detalle del caso de uso es el comportamiento de la aplicacin cuando
se accede, mediante el mtodo GET, a la URI /nombreDeUsuario sin que el
cliente tenga establecida una sesin activa. Aqu, la aplicacin debe simplemente
mostrar el perfil asociado a dicho nombre de usuario, y, de nuevo, la posibilidad
de registrarse o hacer login en la aplicacin. No se ofrecer ninguna opcin ms
excepto las que se vayan introduciendo en posteriores casos de uso.
app.get(/:username, function(req, res){
var username = req.params.username;
models.Whizr.findOne({ username: username }, function(err, doc){
147

if (err || doc == null) {


res.send(Not found, 404);
};
res.render(home.html, { name: doc.name,
username: doc.username,
whizr: req.session.whizr });
});
});

Por ltimo, se habilita la aplicacin para que acepte peticiones en el puerto (arbitrario) 8080:

app.listen(8080);

Se puede apuntar el navegador, en el caso de que se est sirviendo la pgina desde


la misma mquina, a la url http://localhost:8080 y ver el resultado:

Figura 6.3: Pgina principal de la aplicacin con Express


148

6.4.1.3.

Objetivos de los Koans

La primera parte de los Koans de este capitulo cubre aspectos relacionados con
la configuracin y los fundamentos ms bsicos del desarrollo de una aplicacin
con Express.
1. Configurar una aplicacin app Express indicando el uso de determinados
componentes middleware como favicon:
app.configure(function(){
app.___(express.favicon());
// Resto de configuracion
});
2. Habilitar a la aplicacin app para que sea capaz de atender peticiones realizadas con el mtodo HTTP GET en una determinada ruta con app.get():
app.___(/, function(req, res){
// Logica del endpoint
});
3. Enviar respuestas que no sean resultado de peticiones a recursos estticos
ni contenido generado a travs de un Stream, sino simplemente una respuesta sencilla, a travs de send():
if (err || doc == null) {
res.___(404, Not found);
}
4. Devolver contenido html dinmico a las peticiones que lo soliciten, empleando el motor de renderizado definido para la aplicacin a travs del mtodo
render():
res.___(home.html, { name: doc.name,
username: doc.username,
whizr: req.session.whizr } )
5. Habilitar a la aplicacin app para atender correctamente peticiones realizadas con el mtodo HTTP POST en la ruta que se defina previamente con
app.post()
app.___(/login, function(req, res){
149

// Codigo con la logica del endpoint


});

6.4.1.4.

Preparacin del entorno y ejecucin de los Koans

Siguiendo el mismo procedimiento que en las anteriores prcticas, es necesario


importar el mdulo koanizer, proporcionado en las dependencias, para poder ejecutar los Koans de manera satisfactoria.
El fichero que contiene los koans es express-koans.js, que lo nico que exporta es la propia aplicacin Express, lista para ponerse a escuchar. De la misma
manera que en prcticas anteriores este fichero debe ser editado y completado,
sin que quede ningn hueco ___.
Para verificar que se han realizado con xito los koans se ejecutan los casos de
prueba con:
$ jasmine-node -verbose spec/
La correcta ejecucin de las anteriores pruebas resulta en una aplicacin totalmente funcional y ejecutable mediante el comando:
$ node app.js
que nicamente pone la aplicacin Express a escuchar en el puerto 8080:
require(./express-koans).listen(8080);
Para realizar operaciones sobre la lista, se debe apuntar un navegador a la URL
http://localhost:8080, en caso de que se est ejecutando en local. A partir
de este punto el resto est suficientemente explicado a lo largo de los apartados
anteriores.
Cabe indicar que es completamente necesario que exista una instancia de MongoDB corriendo en el sistema. Por lo general, en entornos Unix, se puede instalar
como un daemon o servicio que arranca al inicio del sistema. De no ser as, la
base de datos siempre puede levantarse con el comando mongod, en principio sin
parmetros si no se ha tocado la configuracin que por defecto ofrece:
$ mongod
150

En caso contrario, lo ms comn es que la instalacin haya alterado la ruta al


directorio donde se almacenan los datos de la base de datos. Por defecto sta es
/data/db pero podra localizarse en /var/lib/mongodb de tal manera que ahora
el comando sera:
$ mongod -dbpath /var/lib/mongodb

6.4.2.

Clon de Twitter, objetivo 2: publicacin de whizs y follow y


unfollow de otros usuarios

6.4.2.1.

Descripcin del Objetivo

El segundo objetivo que se debe alcanzar a la hora de crear una red social es,
precisamente, el que da sentido al concepto: otorgar a los usuarios la posibilidad
de crear relaciones entre ellos y de generar contenidos.
Lo que ahora se pretende es que el usuario:
pueda publicar un whiz y que ste aparezca en su pgina de perfil
tenga la posibilidad de, cuando visite el perfil de otro usuario, indicar a la
aplicacin que comienza o deja de seguirlo con vistas a futuras interacciones
entre ellos.
Esto, bsicamente, introduce tres nuevos endpoints que respondan a las acciones
/whiz, /follow y /unfollow. Es obvio que el acceso a estas rutas slo
debera estar permitido a usuarios autenticados en el sistema, por lo que se har
uso de los mecanismos ya implementados para protegerlas.

6.4.2.2.

Diseo propuesto al Objetivo implementado con Express

La introduccin de tres nuevas acciones implica, adems de las funciones respectivas en el servidor para su procesamiento, la inclusin de tres nuevos formularios
en el lado del cliente con los que enviar mediante POST los datos que las nuevas
funcionalidades requieran:
Un formulario sencillo que enve los whizs al servidor. Bastar con un campo
de longitud de texto mxima de 140 caracteres y un botn para enviar.
151

Dos formularios que constan nicamente de un botn, que enven al servidor


el nombre de usuario de la persona a la que se desea seguir o dejar de seguir.
Estos formularios deben ser excluyentes, es decir, si no se sigue al usuario,
en su perfil debe aparecer el formulario Follow, y si se sigue, el formulario
Unfollow.
Como los whizs aparecen en la pgina de perfil del usuario, aquella definida por la
ruta /nombreDeUsuario, no es necesario aadir nuevos elementos de presentacin html, ni estticos ni dinmicos. Bastar con modificar los existentes.
En base a esto, los formularios se pueden incluir en la pgina del perfil que
es, de hecho, el lugar idneo para hacerlo. Las restricciones que se encuentran
son:
que slo aparezcan cuando el usuario est logueado. Obviamente, un usuario no autenticado no debe ser capaz de publicar un whiz ni seguir o dejar
de seguir a otro.
que un usuario no pueda seguirse a s mismo. Se deben evitar situaciones
absurdas.
Los datos que envan cada uno de ellos servirn para actualizar la base de datos,
con lo que el modelo de datos de la aplicacin deber ser extendido y actualizado. Se puede introducir el esquema de una nueva entidad que represente a un
Whiz:
var whizSchema = new Schema({
text

{ type:String },

author

{ type:String },

date

{ type:Date, default: Date.now }

});
As mismo, se debe reflejar que el usuario posee una lista de los otros usuarios
a los que sigue con lo que se hace necesaria la extensin del esquema Whizr
mediante un nuevo campo, following, donde se enumeren los nombres de usuario
de aquellos Whizrs a los que se sigue:
var whizrSchema = new Schema({
name

String,

username

String,

password

String,
152

email

String,

emailHash :

String,

following :

[String]

});
Los nuevos endpoints sern los encargados de actualizar la base de datos segn
su cometido. Los resultados de la accin que debe esperar el usuario son:
si ha invocado /whizr, redireccin a la pgina de su perfil en caso de xito
al guardar en la base de datos el texto del whiz con una referencia al autor.
En caso de que el whiz no pueda publicarse, se genera un error de servidor
500 Internal Error.
app.post(/whizr, checkAuth, function(req, res){
var text = req.param(whiz);
if ( text == null || text.length

== 0 || text.length >= 140) {

res.send(400, Error);
}
var whiz = new models.Whiz();
whiz.text = text;
whiz.author = req.session.whizr.username;
whiz.save(function(err){
if (err) {
res.send(500, Error);
}
res.redirect(/ + req.session.whizr.username);
});
});
si ha hecho /follow o /unfollow satisfactoriamente, habr redireccin a la pgina del perfil del usuario al que ha empezado o dejado de seguir.
Si fracasa, por ejemplo porque no se suministre el nombre de usuario, se
debe obtener un error 404 Not Found.
En el caso concreto de /unfollow, no slo es necesario una actualizacin de la
base de datos, sino tambin de la informacin sobre usuarios seguidos que existe
en la sesin con objeto de evitar inconsistencias. Se sabe que en la sesin est
replicada toda la informacin del perfil del usuario contenida en la base de datos
pero en lugar de refrescarla desde ella, se har al vuelo desde el mismo callback
153

del mtodo que la actualiza:


models.Whizr.update(
{username: whizr },
{ $pull: { following: unfollow } },
null,
function(err, numAffected){
if (!err) {
// actualiza la sesion evitando realizar
// una peticion a la base de datos
var following = req.session.whizr.following;
following.splice(following.indexOf(unfollow), 1);
res.redirect(/ + unfollow);
}
});

6.4.2.3.

Objetivos de los Koans

La segunda parte parte de los Koans de este capitulo est referida a conceptos de
nivel medio y ms orientados a la parte de base de datos:
1. Comprender la mecnica de una funcin middleware y su ciclo de vida, en
concreto, el momento de delegar en el siguiente middleware invocndolo con
next():
var checkAuth = function(req, res, next){
req.session.whizr? ___() : res.send(Unauthorized, 401);
}
2. Aplicar el filtro middleware anterior a las rutas en que tenga sentido. En este
caso, sobre los endpoints que necesiten autorizacin para invocarse, como
logout, que se modifica respecto a su versin en el Objetivo#1:
app.post(/logout, ___, function(req, res){
var redirect = req.session.whizr.username;
req.session.destroy();
res.redirect(/ + redirect);
});
154

3. Actualizar elementos de la base de datos con el mtodo update() que ofrecen los modelos definidos con Mongoose:
models.Whizr.___( {username: req.session.whizr.username},
{$addToSet: { following: followTo }},
null,
function(err, numAffected){
//Acciones post-actualizacion
}
);
4. Emplear junto con el mtodo update() alguno de la gran cantidad de operadores que estn disponibles en MongoDB y que tambin son utilizables a
travs de Mongoose, como $pull:
models.Whizr.update( {username: req.session.whizr.username },
{___: { following: unfollow } },
null,
function(err, numAffected){
//Acciones post-actualizacion
}
);

6.5.

Conclusin

Con la finalizacin de este captulo se debe haber conseguido la capacidad de usar


y configurar cualquier middleware que Connect incluye. A partir de ellos se podr
empezar a construir un aplicacin web completa con Express, que atienda con
get() o post() a peticiones hechas con los distintos verbos HTTP sobre aquellas
rutas que se parametricen para ellos.

155

156

Captulo 7

Socket.IO
7.1.

Qu es Socket.IO?

Una de las caractersticas ms relevantes de Node, como se ha sealado en la


Introduccin a la Plataforma, es su idoneidad para aplicaciones en tiempo real
gracias a su eficiencia en el procesamiento de conexiones masivas. Ejemplos de
este tipo de aplicaciones son los chats o los juegos multijugador.
La librera ms famosa, estndar de facto en Node, para este tipo de aplicaciones
es Socket.IO. En palabras de sus creadores, Socket.IO pretende hacer posible las
aplicaciones en tiempo real en cada navegador y dispositivo mvil, difuminando las
diferencias entre los diferentes mecanismos de transporte [61].
En otras palabras, Socket.IO es una capa intermedia en la comunicacin ClienteServidor que abstrae el mecanismo de transporte (la manera en que se realiza la
conexin) de dicha comunicacin, empleando en el cliente el mejor soportado y
ms adecuado de entre todos los posibles. Consecuentemente, Socket.IO no es
slo una librera para Node sino que lo es tambin para la parte cliente.

7.1.1.

Socket.IO pretende hacer posible las aplicaciones en tiempo


real

Socket.IO no slo ofrece un adaptador de capa de transporte sino que aade


ms caractersticas propias de las aplicaciones en tiempo real como heartbeats,
timeouts y disconnection support [62].
157

Estas tres caractersticas son parte del mecanismo del control del estado y recuperacin de la conexin para que, en caso de prdida de la misma, se pueda
re-establecer. Una por una:
Los heartbeats son mensajes que se intercambian cliente y servidor en intervalos peridicos de tiempo negociados. El propsito es detectar cundo una
conexin se ha interrumpido. En el caso de Socket.IO son una caracterstica
del nivel de transporte y su funcionamiento es muy sencillo: cuando el servidor recibe un mensaje heartbeat, espera un tiempo heartbeatInterval
para enviar otro y vuelve a esperar durante un tiempo heartbeatTimeout
en recibir otro heartbeat, con lo que repite el proceso. Si pasado ese tiempo
no hay respuesta por parte del cliente, da la conexin por perdida.
Por su parte, el cliente tambin espera un tiempo heartbeatTimeout cada
vez que recibe un paquete, independientemente si es de tipo heartbeat o no,
para dar la conexin por cerrada.
Los timeouts, como, por ejemplo, el heartbeatTimeout, son el lmite de tiempo mximo, negociado entre el cliente y el servidor, que debe transcurrir
antes de dar una conexin como perdida.
El soporte a la desconexin es la capacidad que tiene el objeto Socket del
cliente Socket.IO cuando detecta, a travs del mecanismo de heartbeats, que
la conexin entre cliente y servidor se ha interrumpido y, como respuesta,
comienza a realizar intentos de reconexin con el servidor. Estos intentos se
realizan peridicamente en intervalos de tiempo de duracin fijada opcionalmente por el cliente, hasta un mximo de intentos que puede ser infinito.
El tiempo reconnectionDelay entre intervalos se duplica en cada intento de
reconexin segn un algoritmo exponential back off.
Estas caractersticas dan soporte a un API orientada al intercambio de eventos
entre las dos partes.

7.1.2.

en cada navegador y dispositivo mvil

La intencin de esta librera es ofrecer un soporte universal de comunicacin en


tiempo real, sea cual sea el agente de usuario que se utilice con las caractersticas
que cada uno de ellos ofrece. Hasta el momento de la elaboracin de este captulo,
versin 0.9, se soportan los navegadores Internet Explorer 5.5+, Safai 3+, Google
158

Chrome 4+, Firefox 3+ y Opera 10.61+ para escritorio, e iPhone Safari, iPad Safari,
Android Webkit y WebOS Webkit para mviles.

7.1.3.

difuminando las diferencias entre los diferentes mecanismos


de transporte

La parte de cliente de Socket.IO, cuando se conecta, decide qu modo de transporte es el que va a utilizar de todos los disponibles o de aquellos que el usuario
elija opcionalmente. Se entiende por modo de transporte a la tecnologa o a la
tcnica empleada para realizar la conexin Cliente-Servidor segn est soportada
por el agente de usuario. Hay una lista bastante amplia que cubre los distintos
tipos de agente de usuario (viejos navegadores, navegadores para mviles. . . ). Los
diferentes mecanismos de transporte, ordenados segn preferencia de utilizacin
por la librera, que Socket.IO soporta son:
Websocket
Bajo el nombre de Websocket se engloba tanto el protocolo Websocket, definido en la RFC6455 [63], como el API que permite a las pginas web el uso
del mismo [64].
Este protocolo bsicamente habilita en el agente de usuario, no necesariamente el contexto exclusivo, una comunicacin full-duplex Cliente-Servidor
sobre una nica conexin TCP. La motivacin del protocolo es conseguir la
bidireccionalidad sin los inconvenientes que HTTP presentara para obtenerla, como son el mayor nmero de conexiones por cliente desde el agente de
usuario (no slo una), o como el overhead que aade, y con el beneficio de poder utilizar la infraestructura existente (proxies, filtrado, autenticacin. . . ).
Por tanto el protocolo es, conceptualmente, una capa sobre TCP independiente de HTTP, no basado en l, exceptuando que el handshake se realiza
mediante el mecanismo HTTP Upgrade, y que tiene alguna caracterstica en
comn como emplear los puertos 80 y 443, este ltimo para comunicaciones
cifradas.
Una vez establecida la conexin, los mensajes se intercambian en paquetes, frames, con una pequea cabecera y la carga puede ser binaria o texto
codificado en UTF-8 [63, Section 5.6].
El API de Websocket es un sencillo conjunto de mtodos y variables de es159

tado que permiten manejar un WebSocket desde un agente de usuario. En


concreto ofrece:
un constructor Websocket() y un mtodo de finalizacin close(). Al
constructor hay que especificarle como parmetro la URL del servidor,
similar a las de HTTP, en las que cambia el protocolo del esquema por ws
o wss (socket seguro). Una vez conectado, podemos consultar el estado
de la conexin en la propiedad readyState, que ser uno de cuatro
posibles: Connecting, Connected, Closing o Closed.
mtodos para el manejo de eventos: onOpen(), onError(), onMessage()
y onClose(). Cuando se reciben datos, el atributo binaryType es el que
indica cmo deben ser tratados esos datos, por ejemplo, como blob o
como un arraybuffer.
la manera de enviar datos con send() y saber con el atributo
bufferedAmount cuntos estn encolados a la espera de enviarse.
R Flash
R Socket
Adobe
R con su tecnologa Flash ,
R provee un entorno multiplaLa empresa Adobe ,

taforma para la creacin y ejecucin de aplicaciones y contenido multimedia


para sistemas operativos y, sobre todo, navegadores web. Por tanto, las pginas web pueden contener embebido contenido Flash, archivos de extensin
.swf, con el que el usuario puede interactuar a travs del browser.
Este contenido puede programarse con un lenguaje de programacin, ActionScript, que, entre una variada gama de objetos, ofrece su propia implementacin de sockets TCP. A travs de sta, Socket.IO ofrece, para la comunicacin con el servidor, su propio archivo .sfw que simula un WebSocket.
Este archivo est incluido en el mdulo Socket.IO y en caso de ser necesario, se sirve automticamente al cliente que lo insertar dinmicamente en
la pgina web.
ActionScript y la implementacin que se hace de WebSocket en este lenguaje
queda fuera del campo de estudio del proyecto y no se profundizar ms
en l. Es importante resear que los sistemas operativos mviles de Apple
no permiten este tipo de contenido y, desde Agosto de 2012, la tienda de
aplicaciones Google Play no dispone del intrprete de Flash para Android,
con lo que su uso est muy limitado.
160

AJAX Long Polling


Una tcnica usada para simular una interaccin con el servidor en tiempo
real es AJAX Polling, consistente en abrir una conexin AJAX cada cierto
intervalo de tiempo con objeto de recibir datos del servidor. Si el intervalo es
lo suficientemente pequeo, la sensacin es la de comunicacin en tiempo
real. Sin embargo, el coste que introduce reducir este intervalo es:
Latencia HTTP: establecer una conexin remota conlleva un tiempo de
latencia del orden tpicamente de los 200 milisegundos pudiendo ser
mucho mayor segn qu circunstancias. En este escenario se obtiene
una degradacin del rendimiento de la aplicacin, lo cual se agrava si el
servidor no genera nuevos datos.
Desperdicio de recursos: si el servidor no tiene datos que enviarnos, se
produce trfico de red y ciclos de peticin/respuesta para nada.
Para optimizar ambas desventajas, se introduce una modificacin en la manera que se realizan las peticiones AJAX. En este caso, si el servidor no tiene
nada que enviar, la conexin se mantiene abierta esperando, como en AJAX
Polling. En el momento que enva algo y se procesa, en lugar de esperar un
intervalo de tiempo para establecer la conexin, se realiza inmediatamente
otra peticin AJAX. De esta manera se reduce drsticamente la latencia y
el trfico de red anteriormente mencionados. Esto es lo que se conoce como
AJAX Long Polling [65].
AJAX Multipart Streaming
Esta tcnica permite mantener una conexin abierta con el servidor si se fija
el tipo MIME de la respuesta como multipart/x-mixed-replace. Este tipo
lo introdujo Netscape en 1995 como una caracterstica de su navegador a la
que llamo server push con la que pretenda que habilitar a los servidores a
enviar nuevas versiones de los elementos de una pgina web.
Sin embargo, los nicos navegadores que aceptan esta tcnica son los que
estn basados en Gecko, el motor de Mozilla, como, por ejemplo, Firefox [66].
Forever Iframe
Es otra de las tcnicas que permiten mantener abierta una conexin permanente con el servidor empleando una etiqueta html <iframe> de modo que
se permite embeber una pgina web dentro de otra. Esta pgina se enva del
servidor con dos cabeceras concretas:
161

Con esto se consigue que el navegador considere la pgina como un ente


infinito (de ah el nombre Forever Iframe) y, como todos los navegadores modernos, la interpretar segn la vaya recibiendo. Conociendo esto, conforme
haya datos disponibles, el servidor enviar etiquetas <script> en las que
se llame a una funcin del frame padre que ser la que reciba y procese la
informacin del servidor.
Esta tcnica crea un canal unidireccional Servidor-Cliente. La comunicacin
Cliente-Servidor se realiza a travs de peticiones HTTP POST normales sobre
conexiones Keep-Alive [67].
Un punto negativo de este mtodo es que altera la experiencia de usuario
porque la existencia de este iframe supone que el navegador muestre continuamente un icono de carga o la barra de progreso.
JSONP Polling
Esta tcnica de Polling utiliza JSONP en lugar de JSON. JSONP significa
JSON with Padding [68]. Se emplea para evitar la limitacin impuesta por
los navegadores con su poltica de mismo dominio de origen por la cual
el navegador restringe el intercambio de datos entre una pgina web y un
servidor slo si ambos pertenecen al mismo dominio, con objeto de evitar
problemas con cdigo Javascript.
Se solicita al servidor que recubra los datos con una llamada a una funcin presente en el espacio de ejecucin del navegador capaz de procesarlos
correctamente como la siguiente:
function handleData (datosJSON){
// Procesamiento de los datos del servidor
}
Este recubrimiento es a lo que se llama padding y se incluye como parmetro
en la query de la URL que proporciona los datos, por ejemplo:
http://dataserver.com/webservice?callback=handleData
Esta URL se emplea como fuente, atributo src, en una etiqueta script de
HTML [69] porque esta etiqueta no posee la restriccin que se impone al
resto de peticiones cross-domain.
El resultado de la peticin para este ejemplo concreto debera ser:
162

handleData( {datosJSON} )
que se inserta y ejecuta dentro de la etiqueta <script> invocando al callback que se ha establecido en la pgina. Este callback procesa el resultado
evitando los errores que se produciran si el servidor simplemente hubiera devuelto el objeto datosJSON, porque hubiera sido interpretado como un
bloque de cdigo (est entre llaves), no como un objeto, por otra parte, innacesible al resto del cdigo de la pgina por no estar asignado a variable
alguna [70].
De esta manera, aunque no es una tcnica que elimine el riesgo de cdigo malicioso, puesto que puede existir un servicio web comprometido, se
resuelve el problema de estandarizacin cross-domain [71].

7.2.
7.2.1.

Usando Socket.IO
Servidor

La manera de utilizar Socket.IO en la parte de servidor con Node es importndolo


con require, como con todos los mdulos, y asocindolo a un servidor HTTP,
que es uno de los parmetro que acepta el mtodo listen() (til sobre todo si se
trabaja con otros mdulos para desarrollo web como Express):
var server = require(http).createServer(),
io = require(socket.io).listen(server);
server.listen(8080);
O bien indicando al mtodo listen() el puerto donde escuchar y l mismo se
encarga de crear el servidor:
var io = require(socket.io).listen(8080);
A partir de entonces la aplicacin est preparada para recibir conexiones y hacer
disponible el conjunto de los sockets conectados en la propiedad io.sockets.
Todos estos sockets poseen varios atributos, sobre todo referentes al entorno y
al estado de la conexin, de los cuales destaca uno, el identificador de cliente id,
accesible como: socket.id. Es nico y se genera en el proceso de handshake con
el cliente, por lo que es conocido por ambos.
163

Antes de ello, el cliente debe superar un proceso de autenticacin, en la primera


fase del handshake. Este proceso es opcional y configurable por el programador
asignando una funcin al mtodo authorization():
io.configure(function(){
io.set(authorization, function(handshakeData, callback){
// Aqui el algoritmo de autenticacion
callback(null, true);
});
});
La funcin recibe todos los datos referentes al handshake en handshakeData, que
es un objeto que la librera genera en este proceso, en los cuales puede basarse
para autorizar o no la conexin. Una vez determinado esto, se invoca al callback
cuyo primer argumento es un error, en caso de haberse producido, y el segundo
indica si el proceso ha sido satisfactorio, true, o no, false. En el primer caso, el
cliente recibir un evento normal connect y en el ltimo caso el evento que se
dispara es error.
Indicar

que,

durante

la

autenticacin,

se

puede

manipular

el

objeto

handshakeData aadiendo o quitando atributos. Estas modificaciones quedarn


reflejadas posteriormente puesto que este objeto se sigue usando despus del proceso. Los atributos que se aadan estarn luego disponibles en el propio socket,
en socket.handshake, durante el evento connecting. Los que se quiten, y es
importante remarcarlo, dejarn de estar disponibles para futuras acciones sobre
ellos, como por ejemplo procesos de autenticacin posteriores [72].
La librera permite atender a eventos generados en los sockets autorizados. Estos
eventos se pueden separar en dos grupos: los definidos por Socket.IO y los que
define el usuario. Los tres primeros son [73]:
io.sockets.on(connection, function(socket){})
Es la manera que existe de obtener la referencia a un socket la primera vez
que se conecta. Una vez se tenga, a travs del callback, se pueden establecer
en l los eventos que se deseen.
socket.on(message, function(message){})
Se emite cuando se recibe un mensaje a travs del mtodo del socket cliente
socket.send(). El listener del evento recibe como argumento el mensaje
que enva el servidor.
164

socket.on(disconnect, function(){})
Se espera que se emita este evento cuando un socket se desconecta, en
principio porque el cliente desconecta aunque tambin es posible que se
emita cuando lo haga el servidor.
La manera que tiene el programador de definir eventos es sencilla:
socket.on(evento, [function(datos, callback){}])
evento se sustituir por el nombre que se le haya dado. Cada vez que el
cliente emita uno de ellos se ejecutar la funcin annima.
Esta funcin recibe como primer argumento los datos que enva el cliente, si
acaso enva, para procesarlos.
El segundo argumento es un callback opcional. De existir, es una funcin a
la que se llama pasndole los argumentos que admita con los valores que se
deseen. Su misin es la de asentimiento: se invoca en el servidor y se ejecuta
en el cliente, que es donde est definida y donde, por tanto, produce efecto.
Adems, en los sockets se puede almacenar informacin asociada al cliente que
se puede gestionar con dos mtodos accesores:
socket.set(propiedad, propertyValue , function(){})
Asigna la propiedad al socket y le asigna el valor propertyValue. El callback se ejecuta una vez se realiza el proceso de asignacin.
socket.get(propiedad, function(error, propertyValue){})
Intenta obtener del socket el valor de la propiedad ejecutando el callback
tanto si lo consigue, pasndoselo en el parmetro propertyValue, como si
no, especificando el error en el parmetro error.
Estos mtodos, y, por tanto, la informacin almacenada a travs de ellos, slo son
accesibles desde la parte del servidor, no desde el cliente.
En muchas ocasiones, ser necesario o ms til y/o cmodo trabajar con colecciones de sockets agrupados segn algn criterio especfico. Se ofrece para ello
dos maneras de particionar el conjunto de total: mediante namespaces o mediante
rooms (o salas).
Namespaces
se emplean cuando se quiere que los mensajes y eventos se emitan en un
endpoint en particular que se especifica como parte de la URI empleada por
165

el cliente cuando se conecta, por ejemplo, http://example.com/chat. El


beneficio que se obtiene es el de multiplexar una nica conexin, es decir,
en lugar de usar varias conexiones Websocket, se usar la misma.
El cliente solicita la conexin al endpoint indicndolo en el mtodo connect().
El

servidor

manejar

los

diferentes

endpoints

con

el

mtodo

io.of(endpoint) que devuelve el endpoint indicado. Sobre ste se podr


trabajar como si del conjunto global de sockets se tratara. Sirva de ejemplo:
varnamespace1 = io
.of(/endpoint_1)
.on(connection, function(socket){
namespace1.emit(event, { newSocket: socket.id });
});
var namespace2 = io
.of(/endpoint_2)
.on(connection, function(socket){
socket.emit(event, { hello: socket.id });
});
Al igual que en el espacio global, sobre un namespace se puede definir un
proceso de autenticacin. Se hace a travs del mtodo encadenable
authorization() del namespace:
var namespace = io
.of(/endpoint)
.authorization(function(handshakeData, callback){
handshakeData.miToken = autorizado;
callback(null, true);
})
.on(connection, function(socket){
console.log(socket.handshake.miToken);
});
En caso de que falle la autenticacin, a diferencia de las rooms, los namespaces no emiten el evento connect_failed que se puede manejar en el
cliente. No vara en caso de xito donde el cliente seguir recibiendo un
166

evento connect.
Rooms
[74] bsicamente son etiquetas que se asignan a los sockets para crear grupos de ellos pudiendo un socket tener varias. Es una manera fcil de realizar
el particionado de los clientes. A diferencia de los namespaces, en los que el
cliente solicitaba formar parte, esta divisin se realiza en el servidor como
mecanismo disponible para el programador y transparente al cliente .
Otras diferencias con los namespaces son que no tiene soporte nativo para
la autenticacin, y que las salas son parte de un namespace mientras que
los namespaces, sin embargo, son parte del scope global [75].
El manejo de las salas es extremadamente sencillo:
el mtodo socket.join(room) aade un cliente que se eliminar con
socket.leave(room)
el mtodo io.sockets.in(room).emit(event, data) genera un
evento que reciben todos los clientes de esa sala
Desde el cdigo del servidor, se tiene disponibles la lista de rooms que se
van creando en el atributo io.sockets.manager.rooms. Esta tabla hash
contiene las salas como claves y, asociado a cada una de estas claves, el
array de clientes pertenecientes a esa room. No obstante resulta ms sencillo
acceder a salas y clientes con los mtodos:
io.sockets.clients(room)
para obtener las conexiones a una room
io.sockets.manager.roomClients[socket.id]
para obtener la lista de salas a las que pertenece un cliente determinado. La lista es un objeto cuyos atributos, todos de valor true, son el
nombre de cada una de las salas.
Hay que destacar que el nombre de las salas manejado de las maneras anteriores aparecer precedido del carcter /.

7.2.2.

Cliente [2]

Del lado del cliente, Socket.IO se importa como cualquier fichero .js:
167

<script src="/socket.io/socket.io.js"></script>
En este caso, ser la propia Socket.IO la que se encargue de servir la librera al
cliente siempre que la aplicacin web y Socket.IO corran en el mismo servidor
y puerto. Para evitar problemas de cach con el navegador, esta forma admite
nmero de versin de esta manera:
<script src="/socket.io/socket.io.v0.9.10.js"></script>
En caso contrario:
<script src="http://<uri:port>/socket.io/socket.io.js"></script>
Yendo ms all, se puede especificar otra ubicacin para la librera y servirla por
cualquier otro medio, teniendo en cuenta que hay que:
1. clonar el proyecto correspondiente al cliente, socket.io-client, desde github1 :
git clone git://github.com/LearnBoost/socket.io-client.github
2. copiar el contenido de /dist en la ubicacin desde donde se va a a servir.
Para la versin 0.9.10 son cuatro ficheros. A saber: WebSocketMain.swf,
WebSocketMainInsecure.swf, socket.io.js y socket.io.min.js
Una vez inicializada la librera, se puede trabajar con un socket conectndolo al
servidor:
var socket = io.connect(host, options)
El host es, lgicamente, el servidor donde corre la aplicacin basada en Socket.IO, aunque la funcin connect() est programada de tal manera que si no
se proporcionan argumentos, se realizar un autodescubrimiento del servidor basndose en la URL del documento que se ha obtenido del servidor.
El argumento host tambin puede indicar a qu namespace va a conectarse el
socket simplemente indicando el endpoint como parte de la URI o incluso directamente, ya que connect() autodetectar el host.
var chat = io.connect(http://example.com/chat);
var news = io.connect(/news);
Conectarse a un namespace limitar los mensajes y eventos que se reciban a los
emitidos por los clientes en dicho endpoint.
1

https://github.com/

168

A travs del segundo argumento (opcional), options, se pueden modificar un


amplio rango de parmetros relativos a la conexin. Por defecto, el objeto que
contiene las opciones es:
{
"force new connection": false,
"resource": "socket.io",
"transports": ["websocket",
"flashsocket",
"htmlfile",
"xhr-multipart",
"xhr-polling",
"jsonp-polling"],
"connect timeout": 5000,
"try multiple transports": true,
"reconnect": true,
"reconnection delay": 500,
"reconnection limit": Infinity,
"max reconnections attempts": 10,
"sync disconnect on unload": false,
"auto connect": false,
"flash policy port": 10843,
"manualFlush": false
}
El significado de los distintos parmetros y su utilidad se detallan a continuacin:
force new connection
cuando se aade como opcin, permite a un cliente crear varias conexiones
bajo distintos sockets, de otra manera, si se intentase abrir una conexin se
trabajara con la ya establecida.
resource
identifica el endpoint donde realizar conexiones, por defecto es el espacio
global socket.io pero puede ser el de cualquier namespace.
transports
es la lista por orden de preferencia de transportes que se quiere que se
169

intente utilizar para realizar la conexin. Por defecto, estn todos, pero puede
restringirse a unos cuantos.
connect timeout
son los milisegundos que tiene Socket.IO para conectarse al servidor mediante un transporte antes de reintentarlo. Hay que tener en cuenta que
algunos transportes necesitan ms tiempo que otros para conectar por lo
que conviene no modificar este parmetro.
try multiple transports
flag que autoriza los intentos de conexin con el resto de modos de transporte en caso de connect timeout.
reconnect
flag que indica si se debe o no intentar la reconexin automtica en caso de
desconexin.
reconnection delay
factor de espera en milisegundos antes de volver a intentar una reconexin
con el servidor. Forma parte de un algoritmo de tipo exponential back off que
fija los milisegundos totales del siguiente intento segn la frmula N delay
con N=1, 2, 3. . . nmero de intento.
reconnection limit
como el anterior parmetro, reconnection delay crece segn un algoritmo
exponencial a cada intento de reconexin, se introduce ste otro parmetro,
reconnection limit, para marcar un lmite mximo, tambin en milisegundos, que marca hasta dnde puede aumentar.
max reconnections attempts
nmero de intentos de reconexin a travs del modo de transporte elegido
antes de realizar el definitivo y, en caso de volver a fallar, empezar a probar
con el resto de modos de transporte seleccionados.
sync disconnect on unload
realiza una desconexin del socket antes de detener la ejecucin de la librera, como sucede, por ejemplo, al cerrar o recargar la pgina web en el
navegador. Por defecto es false, con lo que el servidor detectar a travs de
los heartbeats que se ha interrumpido la conexin.
auto connect
170

conecta automticamente con el servidor cuando se crea el socket.


flash policy port
configura el puerto donde se consulta el archivo de polticas de Flash (flash
policy file) [76]. Estos archivos definen qu privilegios de conexin alcanzan
los sockets que crea Flash, por tanto slo aplica en caso de que el transporte
sea flashsocket. Flash usa por defecto el puerto 843, pero Socket.IO lo ha
cambiado a 10843 para que no sea un puerto de root (aquellos que estn por
debajo de 1024).
manualFlush
activando esta opcin se deja a criterio del programador cundo invocar el
mtodo socket.flushBuffer() que enva los datos al servidor.
Cuando se invoca el mtodo connect() comienza el proceso de handshake, definido en el protocolo de Socket.IO [77], entre cliente y servidor que se inicia con
una peticin POST automtica a ste del tipo
http://example.com/socket.io/1/
El servidor reconocer el intento de establecer una conexin por la ausencia de
los campos transport id y session id propios de una URL perteneciente a una
conexin ya establecida.
Si la negociacin ha sido satisfactoria se recibir un cdigo 200 OK con un cuerpo
de respuesta que incluye, separados por dos puntos :, cuatro campos:
El identificador de sesin, session id, que es el mismo que identifica al
socket en el servidor, socket.id
Opcionalmente, el timeout en segundos de espera de los heartbeats,
heartbeatTimeout. Su ausencia en la respuesta indica que ni cliente ni
servidor esperan heartbeats
El timeout en segundos de cierre de la conexin connection closing timeout.
Marca cuanto tiempo tarda en cerrarse el transporte una vez que el socket
se considera desconectado
La lista de transportes soportados por el servidor
Por ejemplo, 4d4f185e96a7b:15:10:websocket,xhr-polling.
171

Una vez recibido la respuesta a la negociacin el cliente selecciona el modo de


transporte intersecando la lista de los que puede usar l y la lista que recibe del
servidor e intentando conectar con ellos uno a uno hasta tener xito. Normalmente debera ser el primero, pero puede depender, por ejemplo, de si la conexin es
cross-domain.
Completado este proceso, la forma de las URL que utiliza Socket.IO es como sigue:
<esquema>:// <host>/ <namespace>/ <versin protocolo>/ <id
transporte>/ <id sesin>/ [?<query>]
esquema
indica el protocolo que se usa, en principio, http o https, pero puede actualizarse a ws o wss
host
la mquina que ejecuta el servidor Socket.IO, por defecto ser aquella que
haya servido la pgina cargada por el cliente
namespace
el namespace donde se conecta el cliente. Si ste no indica ninguno, por
defecto ser socket.io
versin protocolo
hasta el momento, el protocolo Socket.IO est en la versin 1. No confundir
con la versin del mdulo, que la que se emplea aqu es la 0.9.10
id transporte
el identificador del transporte que se haya acordado sobre el que realizar la
conexin:

xhr-polling,

xhr-multipart,

htmlfile,

websocket,

flashsocket, jsonp-polling. Cada uno, como es obvio, se corresponde


con uno de los mtodos detallados al inicio.
id sesion
es el identificador de cliente que el servidor asigna al socket durante el proceso de handshake.
query
es un parmetro opcional a travs del cual podemos pasar informacin adicional al servidor respetando las reservadas por Socket.IO:
172

t
timestamp, usado para evitar la cach en viejos agentes de usuarios
disconnect
usado para la desconexin
En el cliente son conocidas las claves que se envan en la query y, para acceder a ellas desde el servidor, se pueden consultar como atributos del objeto
socket.handshake.query.
Pueden sin embargo recibirse otros dos cdigos que indican que el proceso no se
ha terminado de realizar satisfactoriamente, son:
401 Unauthorized
Cuando el servidor rechaza la conexin en base a las credenciales (cabecera
Cookie o cualquier otro mtodo) que presenta el cliente para ser autorizado.
503 Service Unavailable
Cuando se rechaza la conexin por razones como exceso de carga en el servicio.
Una vez se tenga una instancia de socket conectada se tendr informacin de su
estado a travs de varias propiedades:
options
el objeto que contiene todas las opciones fijadas para el socket
connected
booleano que indica si el socket est conectado o no
connecting
booleano que indica si el socket est conectando o no
reconnecting
booleano que indica si el socket est reconectando o no
transport
referencia a la instancia del transporte que se emplea en la comunicacin
y se puede utilizar a travs de los mtodos:
send(mensaje, function(){})
Enva la cadena de datos mensaje. Cuando el servidor la recibe, se ejecuta
173

automticamente la funcin de asentimiento pasada como segundo argumento.


emit(evento, {datosJSON}, function(params){})
Genera el evento en el endpoint donde est conectado y enva al resto
de clientes en el endpoint los datos en notacin JSON datosJSON. El tercer
argumento es una funcin de asentimiento que puede invocarse desde el cdigo del servidor, por decisin del programador, a diferencia del asentimiento
de send() que, como se ha comentado, es de ejecucin automtica.
disconnect()
Finaliza la conexin con el servidor.
De la misma manera que en el servidor, los sockets en la parte cliente pueden
reaccionar a mltiples eventos, algunos definidos por Socket.IO y otros por el
programador de la aplicacin. Entre los ya establecidos[2]:
socket.on(connect, function(){})
Emitido cuando el socket se conecta satisfactoriamente.
socket.on(connecting, function(transport_type){})
Cuando el socket est intentando conectar con el servidor, tanto la primera
vez como en una reconexin. Se facilita por parmetro el tipo de transporte
a travs del cual se est intentando realizar la conexin. Si el socket est intentando

reconectarse,

este

evento

se

emite

despus

del

evento

reconnecting y antes del evento connect.


socket.on(connect_failed, function(){})
Se emite cuando expira el timeout despus del ltimo intento de conexin si
est activada la opcin connect timeout. Si try multiple transports es
la opcin activada slo se emite una vez probados todos los posibles medios
de transporte.
Tambin se emite cuando el mecanismo de autenticacin, definido por el
programador, de un namespace deniega el acceso a dicho namespace.
socket.on(message, function(message){})
Emitido cuando se recibe un mensaje del servidor.
socket.on(close, function(){})
Se emite cuando se cierra la conexin. Los autores de la librera nos advier174

ten de que puede emitirse bajo desconexiones temporales conocidas, como


sucede con las tcnicas de Polling, donde se realiza una desconexin cuando
se han recibido los datos del servidor para inmediatamente abrir otra que
espere los siguientes.

socket.on(disconnect, function(){})
Cuando el socket se desconecta.

socket.on(reconnect,function( transport_type,
reconnectionAttempts){})
Se emite una vez la conexin se haya reestablecido y slo si est activada la
opcin

reconnect.

Los

argumentos

que

recibe

el

listener

son

transport_type, que indica con qu modo de transporte se ha realizado


la reconexin, y reconnectionAttempts, que indica el nmero de intentos
realizados para reconectar.

socket.on(reconnecting, function(reconnectionDelay,
reconnectionAttempts){})
Se emite en cada uno de los intentos de reconexin, e indica el nmero
de intentos que lleva y el tiempo de espera hasta el siguiente.

socket.on(reconnect_failed, function(){})
Emitido cuando han fallado todos los intentos de reconexin y no ha podido
establecerse la reconexin con el servidor.

Pero no es on() el nico mtodo para trabajar con eventos. Socket tiene disponible
tambin:

once(evento, function(){})
Permite atender a un evento una sola vez despus de lo cual se elimina el
listener.

removeListener(evento, functionName)
Elimina el manejador de eventos functionName del evento. functionName
no puede ser, por tanto, una funcin annima.
175

7.3.

Aplicacin con Socket.IO

Para comprender mejor el funcionamiento de la librera se va a implementar una


versin de un juego sencillo de memorizacin.

7.3.1.

Descripcin del juego

El juego es un clsico de los juegos de mesa. Consiste en una serie de cartas con
una imagen, iguales dos a dos, que se colocan boca abajo sobre un tablero. Por
turnos, los participantes deben descubrir dos de ellas buscando que sean pareja.
Si las cartas descubiertas en el turno no son iguales, se vuelven a dar la vuelta y
se cede el turno al otro participante. Si son iguales, se dejan a la vista y el jugador
vuelve a descubrir otras dos. Los jugadores deben intentar memorizar la posicin
de las cartas que se van descubriendo con objeto de conseguir descubrir el mayor
nmero de parejas posibles siendo el ganador el que ms parejas averige.

7.3.2.

Objetivos perseguidos

Con la realizacin de la aplicacin se persigue conocer los siguientes aspectos del


mdulo Socket.IO:
creacin de sockets y subscripcin de los mismos a eventos, tanto del sistema como definidos por las necesidades de la aplicacin
funciones de asentimiento en cliente
almacenamiento de datos asociados con el cliente durante la comunicacin
utilizacin de rooms para agrupar y gestionar clientes de una manera sencilla, donde la gestin implica:
agregar un cliente a una sala
emitir eventos en la sala
176

7.3.3.

Diseo propuesto

De acuerdo con las sencillas reglas explicadas anteriormente, una partida del
juego puede modelarse como un objeto Game que contiene el estado del juego
entre los dos participantes. Consecuentemente la informacin que una instancia
este objeto almacena es:
qu cliente jug el ltimo turno, lastTurn
cul es la ltima carta que se ha descubierto en ese turno, lastCard
el orden de las cartas del tablero, cardsMap. Este orden se determina al
azar: en el constructor de la clase, se inspecciona el directorio de imgenes
y a cada una de ellas se le asignan dos identificadores unvocos
Un nuevo juego comienza cuando se conecta un jugador mediante un navegador
web. A travs de un formulario de la pgina, enva su nombre de usuario cuando
pulsa el botn Play! lo que origina una nueva conexin en el servidor.
Ver Implementacin con Socket.IO. Punto 1
Este cliente genera un evento joins, que seala que quiere unirse a una partida
con su nombre de usuario:
joins = { username }
Ver Implementacin con Socket.IO. Punto 2
Si el nombre no se acepta, por ejemplo porque el otro participante se llame igual,
porque el cliente intente conectarse dos veces con el mismo nombre o porque se
haya recibido un nombre en blanco, se produce un error en forma de evento que
supone fin de conexin.
error = { code, extendedInfo }
Ver Implementacin con Socket.IO. Punto 3
Si el nombre es correcto, se inicializan los datos del cliente, concretamente nombre y puntuacin, guardndolos en el socket
Ver Implementacin con Socket.IO. Punto 4
y se prepara la partida comprobando la condicin de inicio de la misma y se
asiente enviando al cliente su propio nombre como confirmacin.
177

Ver Implementacin con Socket.IO. Punto 5


La consideracin que se debe tener en cuenta para comenzar una partida es si
hay o no otro jugador esperando.
No hay nadie esperando: el sistema tendr conocimiento de que no hay un
cliente a la espera porque la variable waitingRoom es null. Se genera entonces una nueva partida instanciando el objeto Game. Esta instancia se almacena en un hashmap (un simple objeto Javascript) asignndole una clave
o identificador de juego unvoco, obtenido gracias al mdulo node-uuid, y se
actualiza waitingRoom con esa clave.
if (!waitingRoom){
startingPlayer = username;
waitingRoom = uuid.v1();
room2game[waitingRoom] = new models.Game();
// TODO: Agregar el socket a una sala
}
Adems, se utilizar el identificador, que servir de etiqueta, para crear una
sala donde agrupar a los dos clientes para que se desarrolle el juego.
Ver Implementacin con Socket.IO. Punto 6
Si hay alguien esperando: el sistema lo sabr porque waitingRoom contiene
un identificador de juego a travs del cual podemos aadir al nuevo cliente
a la sala creada para la partida e, inmediatamente, comenzar el juego.
room2game[waitingRoom].lastTurn = username;
// TODO: Agregar el cliente a la sala
// TODO: Generar el evento de inicio start
waitingRoom = null;
startingPlayer = null;
Se marca el inicio a travs del evento start, difundido a ambos participantes.
Con l se informa de los nombres de los stos en orden de conexin ya que el que
lo haya hecho primero ser quien comience descubriendo las cartas.
start = { [players] }
Ver Implementacin con Socket.IO. Punto 7
178

El desarrollo del juego transcurre segn las normas anteriormente descritas: por
turnos se van descubriendo cartas de dos en dos intentando formar parejas para puntuar. Cada pareja que se descubra proporciona dos puntos al jugador y,
adems, la oportunidad de descubrir otro par de cartas.
Las interacciones entre los usuarios y el servidor de juegos se producen como
intercambio de mensajes. Se modelarn estos mensajes como eventos que se disparan en el socket, bien por cada uno de los clientes o bien por el servidor en
respuesta a ellos. De ellos, el principal es el evento discover, que tanto el
cliente como el servidor pueden emitir.
Emitido por el cliente, el evento discover enva al servidor el identificador de carta
elegida por el jugador y que es la posicin de la misma en el tablero.
discover = { id }
El cdigo de manejo del evento en el servidor contiene la lgica del desarrollo del
juego y gestiona discover recuperando, gracias al identificador del socket, en qu
room se ha generado
Ver Implementacin con Socket.IO. Punto 8
A travs de sta, se conocer cual es el estado de la partida: si no ha acabado o
si la carta no est descubierta ya, en cuyo caso se habr borrado del atributo del
tablero cardsMap de la instancia de Game, que traduce el identificador a una de
las imgenes.
var id = card.id
var game = room2game[ roomId ];
if (game == undefined || !(id in game.cardsMap)) return;
Slo se procesarn los eventos discover que provienen del jugador que tiene
el turno, ignorando el resto. Sabremos qu usuario es gracias al socket
Ver Implementacin con Socket.IO. Punto 9
Ver Implementacin con Socket.IO. Punto 10
El servidor emite el evento discover a los jugadores cuando ha verificado lo
anterior y ha realizado la traduccin del identificador. La informacin que contiene el evento es la ruta de la imagen para que los clientes puedan mostrarla en los
navegadores.
179

discover = { id, src }


Ver Implementacin con Socket.IO. Punto 11
Si se ha descubierto la primera carta del turno, el modelo de juego memoriza cul
ha sido, no por su identificador sino por la ruta de la imagen en el servidor, y
queda a la espera de la siguiente.
var lastId = game.lastCard;
if (lastId == null || lastId == id) {
game.lastCard = id;
return;
};
Si es la segunda carta la que se descubre, una vez traducida, se compara con la
recin descubierta. Aqu slo caben dos opciones:
que las cartas sean diferentes: se pasa turno, actualizando el modelo de
juego con el cliente que ha jugado el turno, lastTurn, y se limpia lastCard,
primera carta que sali en la mano.
game.lastTurn = username;
game.lastCard = null;
Hecho esto se enva el mensaje de fallo, fail, que
fail = { [ids], src }
Ver Implementacin con Socket.IO. Punto 12
que sean iguales: se eliminan del cadsMap del juego los id de las cartas,
por si por algn error se enva alguna al servidor, poder ignorar la peticin
comprobando su presencia:
if (game.cardsMap[lastId] == game.cardsMap[id]){
delete game.cardsMap[lastId];
delete game.cardsMap[id];
game.lastCard = null;
// TODO: Generar evento de acierto
// TODO: Recalcular puntuacion y notificarla a los clientes
}
180

Se notifica a los clientes mediante el evento success, que incluye las id


de la pareja. El cliente debe dejarlas descubiertas en la interfaz.
success = { [ids] }
Ver Implementacin con Socket.IO. Punto 13
Posteriormente, se actualiza la puntuacin del jugador sumando dos puntos y se notifica a los jugadores con el evento score, que informa de la
puntuacin actualizada del jugador que ha ganado el turno:
score = { username, points }
Ver Implementacin con Socket.IO. Punto 14
Despus de cada turno, se comprueba si el juego ha terminado consultando el
modelo. La condicin para que haya acabado es que se hayan eliminado todos
los identificadores del cardsMap, comprobacin que se deja a cargo del objeto
Game.
if (game.isOver()) {
// TODO: Notify the end to clients
delete room2game[roomId];
}
Si se ha finalizado, se comunica a los jugadores mediante finish, que es un
mensaje sin contenido.
finish = {}
Ver Implementacin con Socket.IO. Punto 15

7.3.4.

Implementacin con Socket.IO

Los puntos concretos de la aplicacin que se refieren a Socket.IO se describen en


detalle a continuacin:
1. El servidor recibe una nueva conexin
Cuando se recibe una nueva conexin, Socket.IO genera uno de los pocos
eventos definidos en el servidor al que el cdigo deber atender:
181

io.sockets.on(connection, function(socket){
socket.on(...);
socket.on(...);
});
Este evento proporciona el socket de la conexin que se podr manipular,
bsicamente para aadir los manejadores al resto de eventos que la parte
cliente pueda generar en l.
2. Evento de usuario joins
Este evento est definido por el programador. Notifica cundo un cliente
solicita unirse a una partida. Tiene como mensaje asociado el nombre de
usuario del jugador.
socket.on(joins, function(message, callback){
var username = message.username;
// TODO: Gestionar la incorporacion a un nuevo juego
});
3. Errores
A lo largo de la aplicacin, se pueden encontrar situaciones que no puedan
manejarse con el cdigo y sea necesario notificar al cliente mediante el evento
error. Se han definido uno para el juego que se genera mediante emit() y
se produce cuando cliente no proporciona nombre de usuario o se conectan
dos clientes con el mismo nombre:
socket.emit(error,
{ code: 0, extendedInfo: No username provided }
);
4. Almacenando datos asociados a un cliente en el socket
En los sockets de los clientes se puede almacenar informacin como si de
una sesin HTTP se tratara. Para el juego la utilidad es poder guardar el
nombre de usuario y su puntuacin actualizada.
socket.set(username, username);
socket.set(score, 0);
5. Callback de asentimiento
El segundo argumento de la funcin que maneja el evento joins es un
callback de asentimiento. Cuando el cliente genera un evento con
182

emit(joins, message, callback) da opcin al servidor a invocar una


funcin callback que se ejecutar en el propio cliente.
En el caso del juego, se emplea para asentir que el nombre de usuario se ha
recibido y aceptado sin errores:
socket.on(joins, function(message, callback){
var username = message.username;
if (username == || username == null){
// TODO: Generar evento de error
} else {
// TODO: Chequear condiciones de inicio de partida
// Asentimos al cliente
callback(username);
}
});
6. Creacin de rooms y asignacin de sockets
Las salas se crean dinmicamente cuando se agrega el primer socket a ella.
Por tanto bastar con:
socket.join(waitingRoom);
7. Evento de usuario start
start es un evento definido por el programador para avisar a los clientes de que comienza el juego. Una vez conoce el nombre de ambos participantes, el servidor lo emite a los dos integrantes de la sala invocando a
io.sockets.in(). Enva los dos nombres como parmetro, siendo el primero de ellos el que comienza el juego:
io.sockets.in(waitingRoom).emit(start,
{ players: [startingPlayer, username]}
);
8. Gestin de rooms asociadas a sockets
Cuando se necesita obtener informacin acerca de las salas a las que pertenece un determinado cliente, se debe hacer uso de ciertas estructuras del
Manager

de

Socket.IO.

En

concreto

del

hashmap

io.sockets.manager.roomClients que contiene, asociado a cada identificador de cliente, arrays de etiquetas de todas las salas a las que pertenece
183

el citado cliente.
Sealar que a cada una de esas etiquetas le precede el carcter / por lo
que, para hacer uso de ellas, se debe quitar.
var roomId;
for (roomId in io.sockets.manager.roomClients[socket.id]){
if (roomId != ) break;
};
roomId = roomId.substring(1);
var room = io.sockets.in(roomId);
9. Evento de usuario discover (emitido por el cliente)
ste es el segundo evento definido por el programador que el cdigo atiende.
Se recibe cuando el usuario descubre una carta en su turno de juego.
socket.on(discover, function(card){
var id = card.id;
// TODO: Revisar el estado del turno del juego
});
10. Recuperacin de un socket los datos asociados a un cliente
El complemento a almacenar informacin en un socket es poder recuperarla. Al principio de la aplicacin se inicializa con el nombre de usuario y la
puntuacin. Ms tarde se podr acceder a estas variables desde cualquier
parte del cdigo con socket.get() que como segundo argumento admite
una funcin de callback que se invocar cuando la informacin est disponible para usarse:
socket.get(username, function(err, username){
// TODO:
});
socket.get(score, function(err, score){
// TODO: actualizar puntuacion y volver a guardarla
// TODO: informar a los jugadores de la puntuacion
});
11. Evento de usuario discover (emitido por el servidor)
Este evento definido por el programador notifica a los participantes cul
184

es la ruta de la imagen en el servidor que deben usar para sustituir en el


navegador cuando se descubre una carta.
room.emit(discover,
{ id: id, src: game.cardsMap[id] }
);
12. Evento de usuario fail
fail es un evento definido por el programador que se genera cuando
se han descubierto dos cartas que no son iguales. Contiene la informacin
necesaria para que el cliente pueda volver a ocultarlas: los identificadores
de las cartas descubiertas que emplea la parte cliente y la ruta de la imagen
con las que sustituirlas:
room.emit(fail,
{ ids: [ lastId, id ], src: "images/back.png" }
);
13. Evento de usuario success
Un evento ms de los definidos por el programador que indica a los participantes cundo se ha encontrado una pareja. Enva los identificadores de
cliente de las cartas de esa pareja para que la parte cliente las descarte como
opciones de juego.
room.emit(success, { ids: [lastId, id] });
14. Evento de usuario score
score es otro de los eventos definidos por el programador, esta vez para
notificar a los clientes las puntuacin actualizada de uno de los participantes
por lo que incluye el nombre de usuario del jugador y cul es su puntuacin
actual.
room.emit(score, { username: username, score: score } );
15. Evento de usuario finish
El ltimo de los eventos definidos por el programador es finish, que simplemente se emite para notificar el final del juego y que los clientes puedan
cerrar las conexiones. Es un evento sin informacin adicional, con lo que
simplemente se ejecuta
room.emit(finish);
185

7.4.

Objetivos de los Koans

Mediante los Koans asociados al tema de Socket.IO se persigue reforzar los puntos
comentados en la Implementacin con Socket.IO de la prctica. Algunos de ellos
son redundantes por lo que se han seleccionado los ms representativos, por
ejemplo la emisin de eventos es una accin recurrente cuyas apariciones en
el cdigo son iguales, y que abarquen los aspectos principales con intencin de
afianzarlos. Por objetivos, stos son:
1. Conocer que bajo el evento de servidor connection se notifica la conexin
de un cliente
io.sockets.on( ___ , function(socket){
socket.on(...);
socket.on(...);
});
2. Definicin de eventos de usuario e instalacin de listeners para ellos con
on()
socket.___(joins, function(message, callback){
// TODO: Gestionar la incorporacion a un nuevo juego
});
3. Llamada desde el servidor a una funcin callback de asentimiento definida
en el cliente
socket.on(joins, function(message, callback){
var username = message.username;
// TODO: Gestionar la incorporacion a un nuevo juego
___(username);
});
4. Conocer que con in() se emiten eventos en una sala concreta
io.sockets.___(waitingRoom).emit(start,
{ players: [startingPlayer, username] }
);
186

5. Conocer que con emit() se emiten eventos definidos por el programador


socket.___(error,
{ code: 0, extendedInfo: No username provided }
);
6. Conocer que roomClients es la estructura almacena la relacin entre sockets y rooms
var roomId;
for (roomId in io.sockets.manager.___[socket.id]){
if (roomId != ) break;
};
7. Conocer que mecanismo de almacenamiento y recuperacin de informacin
en un socket se hace a travs de set() y get()
socket.___(username, function(err, username){
// TODO: Verificar que el turno corresponde al jugador
});
socket.___(score, function(err, score){
score += 2;
socket.___(score, score);
room.emit(score, { username: username, score: score } );
});

7.5.

Preparacin del entorno y ejecucin de los Koans

Con objeto de poder ejecutar los Koans satisfactoriamente, la prctica depende de


los mdulos socket.io y node-uuid.
$ npm install socket.io@0.9.9 node-uuid@1.3.3
Adems, con objeto de realizar las pruebas, las dependencias que se necesita son
jasmine-node y socket.io-client.
$ npm install socket.io-client@0.9.10 jasmine-node@1.0.26
Otro de los mdulos que se emplean, pero que viene con la prctica, es koanizer,
187

que adapta el cdigo para que puedan insertarse los huecos que caracterizan a
un koan.
Si, opcionalmente, se ha completado con xito la prctica, sta puede ejecutarse
y jugarse desde un navegador si se instala el mdulo express.
$ npm install express@3.0.0beta7
Todos los koans estn contenidos en el fichero socket.io-koans.js que es el
que habr que editar y completar, sin que quede ningn hueco ___ (tres guiones
bajos seguidos).
Una vez resueltos, puede verificarse que la solucin es correcta corriendo las
pruebas con:
$ jasmine-node -verbose spec/
Adems, como se ha comentado, una correcta resolucin de la prctica la hace
jugable si se ejecuta la aplicacin web mediante
$ node app.js
y se apunta el navegador a la URL
http://localhost:5555/

7.6.

Conclusin

Se concluye el captulo despus de haber alcanzado una serie de sencillas metas,


cuyo fin es el de, de nuevo, conocer cmo se recibe una conexin basada en el
protocolo de Socket.IO, atendiendo al evento connection y cmo el servidor
puede gestionarla a travs de namespaces o rooms. Otro de los aspectos que ha
sido una constante a travs de los captulos es cmo se realiza el intercambio
de datos ente clientes, a travs de eventos que con emit() genera el servidor, y
cmo estos clientes pueden almacenar esos datos en la propia conexin gracias
al mtodo get() del socket.

188

Captulo 8

Conclusin y trabajos futuros


Llegados a este punto, se puede afirmar que se ha establecido una base bastante
slida con la que comenzar a crear aplicaciones para redes en la plataforma Node,
si bien algunos tpicos avanzados han quedado fuera del libro. Sin embargo no es
la finalidad abarcar todos los aspectos de Node y sus mdulos, inmenso de por s,
sino fijar unos conocimientos con los que el seguir descubriendo la plataforma se
convierta en algo ms sencillo y rpido. No slo a nivel de libreras sino de detalles de funcionamiento interno, indispensables para convertirse en un verdadero
experto sobre ella.
No obstante, es importante resaltar que Node es una tecnologa inmadura pero
que evoluciona constante y rapidsimamente, por lo tanto, es bastate factible que
algunos temas aqu cubiertos hayan quedado o queden pronto desactualizados.
De hecho, en el momento de escribir estas lneas, Node iba por la versin 0.10.4.
Cuando se comenz el proyecto, sera la 0.6.13 la vigente. Se ha tratado por tanto
de evolucionar con Node en la medida de lo posible, lo que ha llevado a que sea
la versin 0.8.20 de la que finalmente se hable. Lo mismo ocurre con los mdulos
de terceras partes aqu explicados: Connect, Express y Socket.IO.
Queda as, el trabajo de seguir actualizando el libro, con las nuevas caractersticas y modificaciones de las actuales que se puedan producir. Tambin es deseable
introducir nuevos captulos con mdulos existentes que no se han tratado, como
tls/ssl o https, o mdulos que vayan surgiendo (que seguro que los habr y en
cantidad), como domain. Todo, por suspuesto, acompaado de su propia aplicacin y diseo sugerido para ella, que sirva de aprendizaje a travs de, cmo no,
Koans.
189

Por ltimo, se dejar todo el cdigo generado en manos de la comunidad, que


ser juez imparcial de la utilidad de todo lo aqu expuesto. Si resulta aceptado, indudablemente surgirn fallos en el cdigo que debern ser corregidos, y
sobre todo, lo ms atractivo, surgirn propuestas que mejoren sustancialmente
el diseo propuesto a cada uno de los problemas, con el beneficio que conlleva:
aprendizaje.

190

Apndice A

Listados
A continuacin se incluyen los listados de todos los mdulos ordenados por captulos y completos, sin los huecos de los Koans, como referencia.

A.1.

Mdulos dgram y Buffer

Koans para el mdulo Buffer, a travs de la clase RTPProtocol:


var events = require(events),
util = require(util),
koanize = require(koanizer);
koanize(this);
// Standard and RFC set these values
var REFERENCE_CLOCK_FREQUENCY = 90000;
// RTP packet constants and masks
var RTP_HEADER_SIZE = 12;
var RTP_FRAGMENTATION_HEADER_SIZE = 4;
var SAMPLES_PER_FRAME = 1152; // ISO11172-3
var SAMPLING_FREQUENCY = 44100;
var TIMESTAMP_DELTA = Math.floor(SAMPLES_PER_FRAME
191

* REFERENCE_CLOCK_FREQUENCY
/ SAMPLING_FREQUENCY);
var SECONDS_PER_FRAME = SAMPLES_PER_FRAME / SAMPLING_FREQUENCY;
var RTPProtocol = function(){
events.EventEmitter.call(this);
this.setMarker = false;
this.ssrc = Math.floor(Math.random() * 100000);
this.seqNum = Math.floor(Math.random() * 1000);
this.timestamp = Math.floor(Math.random() * 1000);
};
util.inherits(RTPServer, events.EventEmitter);
RTPProtocol.prototype.pack = function(payload){
++this.seqNum;
// RFC3550 says it must increase by the number of samples
// sent in a block in case of CBR audio streaming
this.timestamp += TIMESTAMP_DELTA;
if (!payload) {
// Tried to send a packet, but packet was not ready.
// Timestamp and Sequence Number should be increased
// anyway cause interval callback was called and
// thats like sending silence
this.setMarker = true;
return;
}
var RTPPacket = new Buffer(RTP_HEADER_SIZE
+ RTP_FRAGMENTATION_HEADER_SIZE
+ payload.length);
// version = 2:
// padding = 0:

10
0
192

// extension = 0:
// CRSCCount = 0:

0
0000

/*
KOAN #1
should write Version, Padding, Extension and Count
*/
RTPPacket.writeUInt8(128, 0);
// Marker = 0:

// RFC 1890: RTP Profile for Audio and Video


//

Conferences with Minimal Control

// Payload = 14: (MPEG Audio Only)

0001110

RTPPacket.writeUInt8(this.setMarker? 142 : 14, 1);


this.setMarker = false;
// SequenceNumber
/*
KOAN #2
should write Sequence Number
*/
RTPPacket.writeUInt16BE(this.seqNum, 2);
// Timestamp
/*
KOAN #3
should write Timestamp...
*/
RTPPacket.writeUInt32BE(this.timestamp, 4);
// SSRC
/*
KOAN #3
...SSRC and...
*/
RTPPacket.writeUInt32BE(this.ssrc, 8);
// RFC 2250: RTP Payload Format for MPEG1/MPEG2 Video
193

// 3.5 MPEG Audio-specific header


/*
KOAN #3
...payload Format
*/
RTPPacket.writeUInt32BE(0, 12);
payload.copy(RTPPacket, 16);
this.emit(packet, RTPPacket);
};
module.exports = exports.RTPProtocol = RTPProtocol;
Koans para el mdulo dgram, a travs de la clase Sender:
var dgram = require(dgram),
koanize = require(koanizer);
koanize(this);
var Sender = function(options){
var options = options || {}
this.port = options.port || 5000;
this.broadcastAddress = options.broadcastAddress || 224.0.0.14;
this.stats = {
txPackets : 0,
txBytes : 0
};
/*
KOAN #1
should create udp sockets properly
*/
this.txSocket = dgram.createSocket(udp4);
this.rxSocket = dgram.createSocket(udp4);
};
Sender.prototype.start = function(){
/*
194

KOAN #2
should make udp server listening sucessfully
*/
this.rxSocket.bind(5001);
};
Sender.prototype.broadcast = function(packet){
var self = this;
/*
KOAN #3
should send a message correctly
*/
this.txSocket.send(packet,
0,
packet.length,
this.port,
this.broadcastAddress,
function(err, bytes){
++self.stats.txPackets;
self.stats.txBytes += bytes;
});
};
Sender.prototype.enableStats = function(enable){
var self = this;
if (enable){
/*
KOAN #4
should attend incoming packets from clients
*/
this.rxSocket.on(message, function(msg, rinfo){
var stats = new Buffer(JSON.stringify(self.stats));
/*
KOAN #5
should response to clients with stats messages
*/
dgram.createSocket(udp4).send(stats,
195

0,
stats.length,
5002,
rinfo.address);
})
}else{
this.rxSocket.removeAllListeners();
}
};
Sender.prototype.end = function(){
this.rxSocket.close();
this.txSocket.close();
}
exports.Sender = Sender;

A.2.

Mdulos net y Stream

Koans para los mdulos net y Stream, a travs de la clase RemotePrompt:


var mediaLibraries = require(./Mp3Library),
sources = require(./Mp3Source),
RTPServer = require(./RTPServer),
udp = require(dgram),
net = require(net),
koanize = require(koanizer);
koanize(this);
var RemotePrompt = function(library){
var sessionsDB = {};
/*
KOAN #1
should instantiate a TCP Server
196

*/
this.server = net.createServer();
this.listen = function(port){
/*
KOAN #2
should be able to listen to incoming connections
*/
this.server.listen(port);
};
this.close = function(){
this.server.close();
};
/*
KOAN #3
should attend incoming connections
*/
this.server.on(connection, function(connection){
var remoteIP = connection.remoteAddress;
/*
KOAN #4
should write in connection socket
*/
connection.write("Welcome to your command line playlist manager, "
+ remoteIP);
if (remoteIP in sessionsDB){
/*
KOAN #5
should be able to close connections
*/
connection.end("Duplicated session, closing.");
return;
};
sessionsDB[remoteIP] = true;
197

var source = new sources.Mp3Source(library);


var rtpserver = new RTPServer();
var udpSocket = udp.createSocket(udp4);
rtpserver.on(packet, function(packet){
udpSocket.send(packet, 0, packet.length, 5002, remoteIP);
});
source.on(frame, function(frame){
rtpserver.pack(frame);
});
source.on(track, function(trackName){
connection.write("Now playing " + trackName + "\r\n# ");
});
source.on(listEnd, function(){
var seconds = 10;
connection.write("End of the list reached.Closing in "
+ seconds
+ " seconds\r\n# ");
/*
KOAN #6
should trigger a unactivity timeout on the socket
*/
connection.setTimeout(seconds * 1000, function(){
delete sessionsDB[this.remoteAddress];
connection.end("Your session has expired. Closing.");
});
});
/*
KOAN #7
should receive incoming data from connections
*/
connection.on(data, function(data){

198

// disable timeout, in case it was set


this.setTimeout(0);
var command = data.toString(utf8).split("\r\n")[0];
switch(command){
case "list":
var playlist = source.list();
this.write("\r\nSongs in the playlist");
this.write("\r\n---------------------");
for (var i=0; i < playlist.length; i++){
var song = playlist[i];
this.write("\r\n"
+ (source.currentTrack() == song? "> " : "
+ song);
}
this.write("\r\n# ");
break;
case "play":
source.play();
break;
case "pause":
source.pause();
break;
case "next":
source.next();
break;
case "prev":
source.prev();
break;
case "exit":
delete sessionsDB[this.remoteAddress];
this.end("Bye.");
break;
default:
this.write("Command " + command + " unknown\r\n# ");
}
});
199

")

connection.on(close, function(){
source.pause();
udpSocket.close();
rtpserver = source = null;
});
connection.write("\r\nNow, point your player to:\r\n\trtp://"
+ remoteIP
+ ":5002\r\n# ");
});
};
exports.create = function(){
var actions = [];
var app;
var library = new mediaLibraries.Mp3Library;
library.on(ready, function(){
app = new RemotePrompt(this);
for (var i = 0; i< actions.length; i++){
actions[i].apply(app);
};
actions = undefined;
});
// TO COMPLETELY IGNORE:
// some kind of ugly, lame & messy code mixing promise pattern
//

with proxy pattern.

//
// This offers an object with same listen method as RemotePrompt
// class invocable when app is not ready yet. It offers too an
// onListening method that will install a callback on listening
// event on server property of RemotePrompt; this method should
// not be in RemotePrompt class itself but its useful for
// testing purposes. Its also chainable.
200

return new function(){


var _defer = function(callback){
if (actions){
actions.push(callback);
} else {
callback.apply(app)
}
};
var self = this;
this.listen = function(port){
_defer(function(){
this.listen(port);
})
};
this.close = function(){
_defer(function(){
this.close();
})
};
this.onListening = function(callback){
_defer(function(){
this.server.on(listening, callback);
});
return self;
}
}
};

A.3.

Mdulo http

Koans para el mdulo http a travs del objeto listServer


201

var http = require(http),


url = require(url),
fs = require(fs),
querystring = require(querystring),
koanize = require(koanizer);
koanize(this);
var listServer = http.createServer();
/*
KOAN #1
should make the server to response incoming requests
*/
listServer.on(request, function(req, res){
var self = this;
/*
KOAN #2
should make the server to properly read the requests headers
*/
var credentials = req.headers["authorization"];
var userAndPass, broadcastIp, pass;
if (credentials){
userAndPass = new Buffer(credentials.split( )[1], base64)
.toString(ascii)
.split(:);
broadcastIp = userAndPass[0];
pass = userAndPass[1];
}
if (!credentials
|| !(broadcastIp in allowedList
&& broadcastIp in this.broadcastList)
|| allowedList[broadcastIp] != pass) {
res.writeHead(401, "Unauthorized", {
"WWW-Authenticate": Basic realm="List Server Authentication"
});
res.end();
202

return;
}
/*
KOAN #3
should make the server to use url module
*/
var uri = url.parse(req.url);
/*
KOAN #4
should make the server to identify the request method
*/
switch(req.method){
case GET:
var path = uri.pathname;
if ( path == / ){
var player = this.broadcastList[broadcastIp];
writeDocument(res, {group:broadcastIp,
paused:player.paused,
tracks:player.list(),
currentTrack:player.currentTrack()});
} else {
fs.readFile("." + path, function(error, data){
if (error){
res.writeHead(404);
res.end();
return;
}
var fileExtension = path.substr(path.lastIndexOf(".")+1);
var mimeType = MIME_TYPES[fileExtension];
/*
KOAN #5
should make the server to include new header in
implicit requests
*/
res.setHeader("Content-Type", mimeType);
if (mimeType.indexOf("text/") >= 0){
203

res.setHeader("Content-Encoding", "utf-8");
}
res.setHeader("Content-Length", data.length);
res.write(data, "binary");
res.end();
});
}
break;
case POST:
req.setEncoding(utf8);
var body = ;
req.on(data, function(data){
body += data;
})
req.on(end, function(){
/*
KOAN #6
should make the server to use querystring module
*/
var query = querystring.parse(body);
var action = query.action;
var player = self.broadcastList[broadcastIp];
if (action in player) {
player[action](function(){
writeDocument(res, {group:broadcastIp,
paused:player.paused,
tracks:player.list(),
currentTrack:player.currentTrack()});
});
} else {
res.writeHead(500, "Internal Server Error");
res.end();
}
204

});
break;
default:
res.writeHead(501, "Not Implemented");
res.end();
break;
}
});
var writeDocument = function(res, doc){
var head = <html><head>
+<link rel="stylesheet"
+ type="text/css"
+ href="/static/style.css">
+</head><body>;
var tail = </body></html>;
var info = <p>Playlist for group + doc.group + </p>;
var form = <form method="post" action="/">;
form += <input type="submit" value="prev" name="action">;
form += doc.paused?
<input type="submit" value="play" name="action">
:<input type="submit" value="pause" name="action">
form += <input type="submit" value="next" name="action"></form>
var trackList = doc.tracks;
var list = "<ul>";
for(var i=0, l=trackList.length; i<l; i++){
var track = trackList[i];
list += (<li
+ (track == doc.currentTrack?
class="currentTrack"
: )
+ >
+ track
+ </li>);
205

};
list += "</ul>";
var content = head + info + form + list + tail;
res.writeHead(200, OK, {
"Content-type": "text/html",
"Content-length" : content.length
});
res.write(content);
res.end();
}
var allowedList = {
"224.0.0.114": "password"
}
var MIME_TYPES = {
"png" : "image/png",
"css" : "text/css",
"js" : "text/javascript",
"html" : "text/html"
}
exports.create = function(db){
if (db == null) throw new Error(Database cannot be empty);
listServer.broadcastList = db;
return listServer;
}

A.4.

Mdulo Express

Koans para el objetivo no 1 del mdulo Express


var express = require(express),
crypto = require(crypto),
206

koanize = require(koanizer);
koanize(this);
var models = require(./models/whizr.js);
var app = express();
app.engine(.html, require(ejs).__express);
app.set(view engine, ejs);
app.configure(function(){
/*
KOAN #1
The Application must be properly
configured to serve favicon
*/
app.use(express.favicon()); // avoid call twice the routes
app.use(express.static(__dirname + /public));
app.use(express.cookieParser());
app.use(express.session({ secret: "Node.js koans" }));
app.use(express.bodyParser()); // to parse post params
});
/**********************
PROFILE & HOME
*
***********************/
/*
KOAN #2
Application must handle index page
*/
app.get(/, function(req, res){
res.sendfile(./views/index.html);
});
app.get(/:username, function(req, res){
207

var username = req.param(username);


models.Whizr.findOne({ username: username },
function(err, doc){
if (err || doc == null) {
/*
KOAN #3
Application must be able to generate simple responses
*/
res.send(404, Not found);
} else {
/*
KOAN #4
Application must be able to produce dynamic responses
according to a view
*/
res.render(home.html, { name: doc.name,
username: doc.username,
whizr: req.session.whizr } )
}
});
});
app.get(/:username/profilepic, function(req, res){
models.Whizr.findOne({ username: req.params.username },
function(err, doc){
if(err){
res.send(Not Found, 404);
} else {
res.redirect("http://www.gravatar.com/avatar/"
+ doc.emailHash
+ "?s=100");
}
});
});
/******************************
REGISTER & LOGIN & LOGOUT *
208

*******************************/
/*
KOAN #5
Application must handle action endpoints for forms
*/
app.post(/login, function(req, res){
//In case were logged in
if (req.session.whizr != undefined) {
res.redirect(/ + req.session.whizr.username);
}
models.Whizr.findOne({ username: req.param(username) },
function(err, doc){
if (err) {
res.send(Error, 500);
return;
}
if ( doc.password == req.param(password) ) {
req.session.whizr = doc;
res.redirect(/ + doc.username);
} else {
res.send(Unauthorized, 401);
};
} )
});
app.post(/logout, function(req, res){
if (req.session.whizr) {
var redirection = req.session.whizr.username;
req.session.destroy();
res.redirect(/ + redirection);
} else {
res.send(Unauthorized, 401);
209

}
});
app.post(/register, function(req, res){
var username = req.param(username);
var password = req.param(password);
var name = req.param(name);
var email = req.param(email);
if ( username.length <= 4
|| username.length >= 10
|| password.length <= 4
|| password.length >= 10) {
res.redirect(400, /); // error
return;
}
models.Whizr.findOne({ username: username }, function(err, doc){
if (err || doc != null) {
res.send(Error, 500);
return;
}
var whizr = new models.Whizr();
whizr.name = name;
whizr.username = username;
whizr.password = password;
whizr.email = email;
var hash = crypto.createHash(md5);
hash.update(email);
whizr.emailHash = hash.digest(hex);
whizr.newMentions = 0;
whizr.save(function(err){
if (err) {
res.send(Error, 500);
};
210

req.session.whizr = whizr;
res.redirect(/ + username);
});
});
});
module.exports = exports = app;
Koans para el objetivo no 2 del mdulo Express
var express = require(express),
crypto = require(crypto),
koanize = require(koanizer);
koanize(this);
var models = require(./models/whizr.js);
var app = express();
app.engine(.html, require(ejs).__express);
app.set(view engine, ejs);
app.configure(function(){
app.use(express.favicon()); // avoid call twice the routes
app.use(express.static(__dirname + /public));
app.use(express.cookieParser());
app.use(express.session({ secret: "Node.js koans" }));
app.use(express.bodyParser()); // to parse post params
});
/**********************
INDEX & PROFILE
*
***********************/
app.get(/, function(req, res){
res.sendfile(./views/index.html);
});
211

app.get(/:username, function(req, res){


var username = req.params(username);
models.Whizr.findOne({ username: username },
function(err, doc){
if (err || doc == null) {
res.send(Not found, 404);
return;
}
models.Whiz.find({ author: username },
function(err, docs){
if (err || doc == null) {
res.send(Not found, 404);
}
res.render(home.html, { name: doc.name,
username: doc.username,
whizs: docs,
whizr: req.session.whizr });
} ).sort({ date: -1 });
});
});
app.get(/:username/profilepic, function(req, res){
models.Whizr.findOne({ username: req.params.username },
function(err, doc){
if(err){
res.send(Not Found, 404);
};
res.redirect("http://www.gravatar.com/avatar/"
+ doc.emailHash
+ "?s=100");
});
});
212

/******************************
REGISTER & LOGIN & LOGOUT *
*******************************/
app.post(/login, function(req, res){
//In case were logged in
if (req.session.whizr != undefined) {
res.redirect(/ + req.session.whizr.username);
}
models.Whizr.findOne({ username: req.param(username) },
function(err, doc){
if (err) {
res.send(Error, 500);
}
if ( doc.password == req.param(password) ) {
req.session.whizr = doc;
res.redirect(/ + doc.username);
} else {
res.send(Unauthorized, 401);
};
})
});
// Filter to check authentication in routes that could need it
var checkAuth = function(req, res, next){
/*
KOAN #1
Filter should be chainable to others
*/
req.session.whizr? next() : res.send(Unauthorized, 401);
}
/*
213

KOAN #2
Application must allow or deny access to certain endpoints
*/
app.post(/logout, checkAuth, function(req, res){
var redirect = req.session.whizr.username;
req.session.destroy();
res.redirect(/ + redirect);
});
app.post(/register, function(req, res){
var username = req.param(username);
var password = req.param(password);
var name = req.param(name);
var email = req.param(email);
if ( username.length <= 4
|| username.length >= 10
|| password.length <= 4
|| password.length >= 10) {
res.redirect(/); // error
return;
}
models.Whizr.findOne({ username: username },
function(err, doc){
if (err || doc != null) {
res.send(Error, 500);
return;
}
var whizr = new models.Whizr();
whizr.name = name;
whizr.username = username;
whizr.password = password;
whizr.email = email;
var hash = crypto.createHash(md5);
hash.update(email);
whizr.emailHash = hash.digest(hex);
214

whizr.newMentions = 0;
whizr.save(function(err){
if (err) {
res.send(Error, 500);
return;
}
req.session.whizr = whizr;
res.redirect(/ + username);
});
});
});

/*************
WHIZEAR *
**************/
app.post(/whizr, checkAuth, function(req, res){
var text = req.param(whiz);
if ( text == null
|| text.length

== 0

|| text.length >= 140) {


res.redirect(Error, 404);
return;
}
var whiz = new models.Whiz();
whiz.text = text;
whiz.author = req.session.whizr.username;
whiz.save(function(err){
if (err) {
res.send(Error, 500);
return;
}
res.redirect(/ + req.session.whizr.username);
215

});
});
/**********************
FOLLOW & UNFOLLOW *
***********************/
app.post(/follow, checkAuth, function(req, res){
var followTo = req.param(username);
if (followTo.length == 0
|| followTo == null
|| followTo == req.session.whizr.username){
res.send(Error, 500);
return;
}
/*
KOAN #3
Application must be able to update users profiles
*/
models.Whizr.update( { username: req.session.whizr.username },
{ $addToSet: { following: followTo } },
null,
function(err, numAffected){
if (!err) {
req.session.whizr.following.push(followTo);
console.log(req.session.whizr.following);
res.redirect(/ + followTo);
}
});
});
app.post(/unfollow, checkAuth, function(req, res){
var unfollow = req.param(username);
if (unfollow.length == 0 || unfollow == null || unfollow == whizr){
res.send(Error, 500);
return;
216

}
/*
KOAN #3
Application must be able to update users profiles
according to certain conditions
*/
models.Whizr.update( { username: req.session.whizr.username },
{ $pull: { following: unfollow } },
null,
function(err, numAffected){
if (!err) {
// update the session
var following = req.session.whizr.following;
following.splice(following.indexOf(unfollow), 1);
res.redirect(/ + unfollow);
}
});
});
module.exports = exports = app;
// To ignore: for testing purposes
app.get(/:username/following, function(req, res){
models.Whizr.findOne({username: req.param(username)},
function(err, doc){
if(err){
res.send(Not Found, 404);
};
res.send(200, { following: doc.following });
});
});

A.5.

Mdulo Socket.IO

Koans para el mdulo Socket.IO:


217

var socketio = require(socket.io),


uuid = require(node-uuid),
Game = require(./models/Game.js),
util = require(util),
koanize = require(koanizer);
koanize(this);
var room2game = {},
waitingRoom = null,
startingPlayer = null,
io = null;
exports.createGame = function(server){
io = socketio.listen(server);
io.set(log level, 1);
/*
KOAN #1
Server must be able to receive incoming connections
*/
io.sockets.on(connection, function(socket){
/*
KOAN #2
The server must be able to properly
act to joins messages from client
*/
socket.on(joins, function(message, callback){
var username = message.username;
if (username
&& username !=
&& startingPlayer != username){
socket.set(username, username);
socket.set(score, 0);
/*
KOAN #3
As result of the joins message, the Server must acknowledge
218

it sending the username back to the client


*/
callback(username);
if (!waitingRoom){
startingPlayer = username;
waitingRoom = uuid.v1();
room2game[waitingRoom] = new Game();
socket.join(waitingRoom);
} else {
room2game[waitingRoom].lastTurn = username;
socket.join(waitingRoom);
/*
KOAN #4
Having two players in a room, the server must be able to
notify both the start of the game
*/
io.sockets.in(waitingRoom)
.emit(start,
{players: [startingPlayer, username]});
waitingRoom = null;
startingPlayer = null;
};
} else {
/*
KOAN #5
The server must handle properly faulty inputs
*/
socket.emit(error,
{ code: 0,
msg: Invalid username });
}
});
socket.on(discover, function(card){
var socket = this;
var id = card.id;
219

var roomId = ;
/*
KOAN #6
The server must be able to know what room the client is in
*/
for (roomId in io.sockets.manager.roomClients[socket.id]){
if (roomId != ) break;
};
roomId = roomId.substring(1);
var room = io.sockets.in(roomId);
var game = room2game[ roomId ];
if (game === undefined || !(id in game.cardsMap)) {
return;
}
/*
KOAN #7 (I)
The socket must obtain any client info from the socket
*/
socket.get(username, function(err, username){
if (err) {
console.log("Discover: username error", err);
process.exit();
}
if (game.lastTurn != username){
room.emit(discover,{id: id, src: game.cardsMap[id]})
var lastId = game.lastCard;
if (lastId == null || lastId == id) {
game.lastCard = id;
return;
};

220

if (game.cardsMap[lastId] == game.cardsMap[id]){
delete game.cardsMap[lastId];
delete game.cardsMap[id];
game.lastCard = null;
room.emit(success, { ids: [lastId, id] });
/*
KOAN #7 (II)
The socket must obtain any client info from the socket
and, once updated, save in it again
*/
socket.get(score, function(err, score){
score += 2;
socket.set(score, score);
room.emit(score,
{username: username, score: score});
});
} else {
game.lastTurn = username;
game.lastCard = null;
room.emit(fail, {ids: [ lastId, id ],
src: "images/back.png"});
};
if (game.isOver()) {
room.emit(finish);
delete room2game[roomId];
}
}
});
});
// To ignore: testing purposes
socket.emit(connectionDone);
});
return server;
221

}
exports.endGame = function(){
if (io) {
io.server.close();
}
}

222

Bibliografa
[1] Ryan dahl, July 2010. http://www.youtube.com/watch?v=F6k8lTrAE2g.
[2] Guillermo Rauch.

Socket.IO client.

https://github.com/LearnBoost/

socket.io-client.
[3] Oleg Podsechin. Ryan dahl interview. http://dailyjs.com/2010/08/10/
ryan-dahl-interview/.
[4] Node.JS. http://nodejs.org.
[5] POSIX Austin Joint Working Group. Portable operating system interface (posix(r)). http://standards.ieee.org/findstds/standard/1003.1-2008.
html.
[6] Core modules.

http://nodejs.org/api/modules.html#modules_core_

modules.
[7] Kris Kowal.
domination.

Commonjs effort sets javascript on path for world


http://arstechnica.com/web/news/2009/12/

commonjs-effort-sets-javascript-on-path-for-world-domination.
ars.
[8] Modules/1.1.1. http://wiki.commonjs.org/wiki/Modules/1.1.1.
[9] Isaac Z. Schlueter. Node v0.8.0. http://blog.nodejs.org/2012/06/25/
node-v0-8-0/.
[10] A Costello. Rfc 3492-punycode: A bootstring encoding of unicode for internationalized domain names in applications (idna). Network Working Group.
Disponvel na Internet em http://www. ietf. org/rfc/rfc3492. txt, 2003.
223

[11] Marc Lehmann. libev - a high performance full-featured event loop written
in c. http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod.
[12] ECMA. ECMA-262: ECMAScript Language Specification. ECMA (European Association for Standardizing Information and Communication Systems), Geneva, Switzerland, fifth edition, June 2011.
[13] W3C DOM Working Group et al. Document object model, 2002.
[14] David Flanagan. JavaScript: the definitive guide. OReilly Media, Incorporated, 2006.
[15] Functional

programming.

http://www.haskell.org/haskellwiki/

Functional_programming.
[16] Tim Caswell. Why use losure http://howtonode.org/why-use-closure.
[17] Cade Metz.
re.

The node ahead: Javascript leaps from browser into futu-

http://www.theregister.co.uk/2011/03/01/the_rise_and_rise_

of_node_dot_js/.
[18] Octane javascript benchmark.

http://octane-benchmark.googlecode.

com/svn/latest/index.html.
[19] Licencia new bsd o bsd de 3 clusulas.

http://opensource.org/

licenses/BSD-3-Clause.
[20] Google. Chrome v8 introduction. https://developers.google.com/v8/
intro.
[21] Garbage

collection.

http://en.wikipedia.org/wiki/Garbage_

collection_%28computer_science%29#Stop-the-world_vs.
_incremental_vs._concurrent.
[22] Google.

V8 embedders guide.

https://developers.google.com/v8/

embed.
[23] Felix Geisendrfer. The node.js scalability myth. https://speakerdeck.
com/felixge/the-nodejs-scalability-myth.
[24] Microsoft. Designing for scalability. http://msdn.microsoft.com/en-us/
library/aa291873(v=vs.71).aspx.
224

[25] Alex Payne. Node and scaling in the small vs. scaling in the large, July 2010.
http://al3x.net/2010/07/27/node.html.
[26] Ryan Dahl. Ryan dahl: Introduction to node.js. www.youtube.com/watch?
v=M-sc73Y-zQA.
[27] libuv. https://github.com/joyent/libuv.
[28] Ryan Dahl. libuv status report. http://blog.nodejs.org/2011/09/23/
libuv-status-report/.
[29] Mikito Takada. Understanding the node.js event loop. http://blog.mixu.
net/2011/02/01/understanding-the-node-js-event-loop/.
[30] Marc Lehmann. libeio - truly asynchronous posix i/o. http://http://pod.
tst.eu/http://cvs.schmorp.de/libeio/eio.pod.
[31] Isaac

Z.

Schlueter.

How

to

module.

http://howtonode.org/

how-to-module.
[32] Isaac Schlueter.

Which callbacks are sent to the event loop?

https:

//groups.google.com/d/topic/nodejs/qluCAFK5zp4/discussion.
[33] Juan Antonio de la Puente. Introduccin a los sistemas de tiempo real, 2007.
http://laurel.datsi.fi.upm.es/~ssoo/STR/Introduccion.pdf.
[34] Brian

Cantrill.

in

production.

Instrumenting

the

real-time

web:

Node.js

http://www.slideshare.net/bcantrill/

instrumenting-the-realtime-web-nodejs-in-production.
[35] Felix Geisendrfer.

Convincing the boss.

http://nodeguide.com/

convincing_the_boss.html.
[36] Christopher Kent, Jeffrey C Mogul, et al. Fragmentation considered harmful,
volume 17. 1987.
[37] Z Albanna, K Almeroth, D Meyer, and M Schipper. Rfc3171: Iana guidelines
for ipv4 multicast address assignments. Technical report, Technical report,
Internet Engineering Task Force (IETF), 2001.
[38] Ascii. http://en.wikipedia.org/wiki/ASCII.
225

[39] Joel Spolsky. The absolute minimum every software developer absolutely,
positively must know about unicode and character sets.

http://www.

joelonsoftware.com/articles/Unicode.html.
[40] David H Crocker. Rfc 822, standard for the format of arpa internet text messages, august 1982. URL http://www. cis. ohio-state. edu/htbin/rfc/rfc822.
html, 23:3841.
[41] Network Working Group et al. Mime (multipurpose internet mail extension).
Technical report, RFC 1341, avril, 1993.
[42] H SHULZRINNE, S CASNER, R FREDERICK, et al. Rfc3350. RTP: a transport
protocol for real time application, 2003.
[43] S Casner and H Schulzrinne. Rfc 3551: Rtp profile for audio and video conferences with minimal control. Technical report, Technical report, Columbia
University, Packet Design, 2003.
[44] D Hoffman, G Fernando, V Goyal, and M Civanlar. Rfc2250: Rtp payload
format for mpeg1. MPEG2 video, 1998.
[45] ISO Iso. IEC 11172-3 Information technology-coding of moving pictures and
associated audio for digital storage media at up to about 1.5 Mbit/s-Part3:
Audio. Motion Picture Experts Group, 1993.
[46] Andrew Tanenbaum. Computer networks. Prentice Hall Professional Technical Reference, 2002.
[47] Jon Postel. Rfc 793: Transmission control protocol, september 1981. Status:
Standard, 2003.
[48] R Braden. Rfc 1122: Requirements for internet hostscommunication layers. Status: Standard, 1989.
[49] R. Fielding, J. Gettys, J. Mogul, H. Frystyk, L. Masinter, P. Leach, T. BernersLee. Rfc 2616: Hypertext transfer protocolhttp/1.1. Network Working Group
and others, 1999.
[50] TJ
deJS.

Holowaychuck.

Connect

Middleware

For

No-

http://tjholowaychuk.com/post/664516126/

connect-middleware-for-nodejs.
226

[51] Cross-site request forgery (csrf).

https://www.owasp.org/index.php/

Cross-Site_Request_Forgery_(CSRF).
[52] Dave Raggett, Arnaud Le Hors, Ian Jacobs, et al. Html 4.01 specification.
W3C recommendation, 24, 1999.
[53] Matt Bridges.

application/x-www-form-urlencoded or multipart/form-

data?

http://stackoverflow.com/questions/4007969/

application-x-www-form-urlencoded-or-multipart-form-data.
[54] What

are

signed

cookies

in

connect/expressjs?

http://stackoverflow.com/questions/11897965/
what-are-signed-cookies-in-connect-expressjs.
[55] James

Allen.

Cookie

sessions.

https://github.com/jpallen/

connect-cookie-session.
[56] Name-based virtual host support. http://httpd.apache.org/docs/2.0/
en/vhosts/name-based.html.
[57] Ricky Ho. Mongodb architecture. http://horicky.blogspot.it/2012/04/
mongodb-architecture.html.
[58] Amy Brown and Greg Wilson. The architecture of open source applications.
Lulu. com, 2011.
[59] Tony Hannan. Why mongodb? [http://www.mongodb.org/display/DOCS/
Introduction.
[60] Introduction to mongodb.

http://www.mongodb.org/display/DOCS/

Philosophy.
[61] Guillermo Rauch. Socket.io: the crossbrowser websocket for real-time apps.
http://socket.io.
[62] Guillermo Rauch. Socket.io, frequently askes questions. http://socket.
io/#faq.
[63] I Fette and A Melnikov. Rfc 6455: The websocket protocol. Technical report,
Status: Internet Draft. Available at: http://tools. ietf. org/html/draft-ietfhybi-thewebsocketprotocol-17. Accessed 8-February-2012, 2011.
227

[64] Ian Hickson.

The websocket api.

W3C Working Draft WD-websockets-

20110929, September, 2011.


[65] Tian

Davis.

cript

Simple

and

long

jquery.

polling

example

with

javas-

http://techoctave.com/c7/posts/

60-simple-long-polling-example-with-javascript-and-jquery.
[66] Comet. http://en.wikipedia.org/wiki/Comet_%28programming%29.
[67] Viacheslav

Tykhanovskyi.

velopers.

Socket.io

for

backend

de-

http://showmetheco.de/articles/2011/8/

socket-io-for-backend-developers.html.
[68] Kyle Simpson. JSONP: Safer cross-domain AJAX. http://www.json-p.
org/.
[69] Jerod Venema. What is JSONP all about? http://stackoverflow.com/
questions/2067472/please-explain-jsonp.
[70] Jsonp, how it works.

http://en.wikipedia.org/wiki/JSONP#How_it_

works.
[71] Bob Ippolito. Remote JSON - JSONP. http://bob.ippoli.to/archives/
2005/12/05/remote-json-jsonp/.
[72] Authorizing.

https://github.com/LearnBoost/socket.io/wiki/

Authorizing.
[73] Exposed events.

https://github.com/LearnBoost/socket.io/wiki/

Exposed-events.
[74] Rooms. https://github.com/LearnBoost/socket.io/wiki/Rooms.
[75] Eugene
cing?

Beresovksy.

Socket.io

rooms

or

namespa-

http://stackoverflow.com/questions/10930286/

socket-io-rooms-or-namespacing.
[76] Peleus Uhley. Setting up a socket policy file server. http://www.adobe.
com/devnet/flashplayer/articles/socket_policy_files.html.
[77] Guillermo Rauch. Socket.IO protocol. https://github.com/LearnBoost/
socket.io-spec#handshake.

228

También podría gustarte