Documentos de Académico
Documentos de Profesional
Documentos de Cultura
JS A
TRAVS DE KOANS
See the
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
ndice general
ndice general
ndice de Tablas
ndice de Figuras
VII
1. Introduccin
2.1. Qu es Node? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . 15
. . . . . 25
. . . . . . . . . . . . . . . . . . . . . . . . . . 35
I
39
. . . . . . . . . . . . . . . . . . . . . . . . . . 52
65
. . . . . . . . . . . . . . . . . . . . . . . . . . 79
89
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
. . . . . . . . . . . . . . . . . . . . . . . . . . 112
121
. . . . . . . . . . . . . . . . . . 149
. . . . . . . . . . . . . . . . . . 154
157
. . . . . . . . . . . . . 158
. . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
. . . . . . . . . . . . . . . . . . . . . . . . . . 177
189
A. Listados
191
. . . . . . . . . . . . . . . . . . . . . . . . . . 191
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 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
. . . . . . . . . . . . . 79
. . . . . . . . . . . . . . . . . . . . . . 142
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.
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
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.
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
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?
2.2.
Es una plataforma
2.2.1.
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
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-
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
module.js https://github.com/joyent/node/blob/v0.8.20-release/lib/module.js#
L377
13
2.2.2.
El formato CommonJS
var PI = 3.14;
exports.area = function (r) {
return PI * r * r;
};
exports.circumference = function (r) {
return 2 * PI * r;
};
2.2.3.
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
2.2.4.
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
20
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.
2.3.1.
El lenguaje JavaScript
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
2.4.
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
2.5.
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
2.5.1.
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
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
[function(){}, function(){}],
"customEvent":
[function(){}, function(){}],
function(){}
2.5.4.
pendientes
para
la
siguiente
iteracin
llamando
2.6.
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
2.7.
Perfecto
para
aplicaciones
en
tiempo
real
data-intensive
2.7.1.
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
2.7.2.
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
38
Captulo 3
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
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
});
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){
});
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
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
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.
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.
http://www.videolan.org/vlc/
50
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
3.5.2.2.
Descripcin de la solucin
(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)
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 +
/ 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.
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.
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.
http://netcat.sourceforge.net/
63
3.8.
Conclusin
64
Captulo 4
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
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
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
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
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
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,
travs
del
evento
connection,
server.on(connection,
4.4.
4.4.1.
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:
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
")
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
4.5.
4.6.
});
4.7.
Conclusin
88
Captulo 5
Mdulo Http
5.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
5.1.1.
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.
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:
Contenido
Texto
Descripcin
text/plain
Slo texto
text/html
Imagenes
image/gif
Formato de intercambio de
grficos gif
image/jpeg
Audio
audio/mpeg3
Vdeo
video/mpeg
Datos
application/octet-stream
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
5.1.2.
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
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
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
5.2.1.
ServerRequest
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
});
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
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)
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
http.js: https://github.com/joyent/node/blob/v0.8.20-release/lib/http.js#L1294
107
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
5.2.4.
ClientResponse
5.3.
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.
Los requisitos bsicos de la aplicacin se describen a grandes rasgos a continuacin. Se debe tener en cuenta que el funcionamiento es realmente sencillo.
5.3.2.
Diseo propuesto
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.
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
5.4.
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
5.5.
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
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
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
json replacer
json spaces
view engine
views
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
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
preferidos
de
ste.
De
manera
anloga,
con
6.2.2.
Response
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],
6.3.
MongoDB
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
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
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
6.4.1.
6.4.1.1.
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
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.
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,
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
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
Por ltimo, se habilita la aplicacin para que acepte peticiones en el puerto (arbitrario) 8080:
app.listen(8080);
6.4.1.3.
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
6.4.1.4.
6.4.2.
6.4.2.1.
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.
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
{ type:String },
author
{ type:String },
date
});
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
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
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
6.4.2.3.
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
155
156
Captulo 7
Socket.IO
7.1.
Qu es Socket.IO?
7.1.1.
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.
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.
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
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
que,
durante
la
autenticacin,
se
puede
manipular
el
objeto
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
servidor
manejar
los
diferentes
endpoints
con
el
mtodo
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
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
xhr-polling,
xhr-multipart,
htmlfile,
websocket,
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
reconectarse,
este
evento
se
emite
despus
del
evento
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
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.
7.3.1.
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
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
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
7.3.4.
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
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
7.4.
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
7.5.
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
188
Captulo 8
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.
* 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:
0001110
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.
*/
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
198
")
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
//
//
// 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
A.3.
Mdulo http
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
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
*******************************/
/*
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
/******************************
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
});
});
/**********************
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
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-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.
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.
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.
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
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
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