397
Pero, ¿Qué es Node.js?

Curso node.js

Embed Size (px)

DESCRIPTION

Curso de Node.js http://redradix.com/courses

Citation preview

Page 1: Curso node.js

Pero, ¿Qué es Node.js?

Page 2: Curso node.js

¿Qué es Node.js?

Lo que todos sabemos

• Hay Javascript por alguna parte

• Backend

• ¿Algo que ver con NoSQL?

• Sirve para hacer chats

Page 3: Curso node.js

¿Qué es Node.js?

Lo que no está tan claro

• ¿Es un framework para Javascript?

• ¿Es una librería?

• ¿Qué tiene que ver v8 con Node.js?

• ¿Para qué sirve (además de los chats)?

Page 4: Curso node.js

¿Qué es Node.js?

Node.js es:

• “Una plataforma de software usada para construir aplicaciones de red escalables (especialmente servidores). Node.js utiliza JavaScript como lenguaje, y alcanza alto rendimiento utilizando E/S no bloqueante y un bucle de eventos de una sola hebra”.

Page 5: Curso node.js

¿Qué es Node.js?

Node.js es:

• “Una plataforma de software usada para construir aplicaciones de red escalables (especialmente servidores). Node.js utiliza JavaScript como lenguaje, y alcanza alto rendimiento utilizando E/S no bloqueante y un bucle de eventos de una sola hebra”.

• Wat?

Page 6: Curso node.js

¿Qué es Node.js?

Por ejemplo, si...

• ...para ruby tenemos Rails...

• ...para python tenemos Django...

• ...para php tenemos Symphony...

Page 7: Curso node.js

¿Qué es Node.js?

Por ejemplo, si...

• ...para ruby tenemos Rails...

• ...para python tenemos Django...

• ...para php tenemos Symphony...

• ¿Podríamos decir que Node.js es el equivalente para JavaScript?

Page 8: Curso node.js

¿Qué es Node.js?

¡¡NO!!

Page 9: Curso node.js

¿Qué es Node.js?

¿Qué es un “lenguaje de programación”?

Page 10: Curso node.js

¿Qué es Node.js?

¿Qué es un “lenguaje de programación”?

• Una gramática que define la sintaxis del lenguaje

• Un intérprete/compilador que lo sabe interpretar y ejecutar

Page 11: Curso node.js

¿Qué es Node.js?

¿Qué es un “lenguaje de programación”?

• Una gramática que define la sintaxis del lenguaje

• Un intérprete/compilador que lo sabe interpretar y ejecutar

• Mecanismos para interactuar con el mundo exterior (llamadas al sistema)

Page 12: Curso node.js

¿Qué es Node.js?

¿Qué es un “lenguaje de programación”?

• Una gramática que define la sintaxis del lenguaje

• Un intérprete/compilador que lo sabe interpretar y ejecutar

• Mecanismos para interactuar con el mundo exterior (llamadas al sistema)

• Librería estándar (consola, ficheros, red, etc,...)

Page 13: Curso node.js

¿Qué es Node.js?

¿Qué es un “lenguaje de programación”?

• Una gramática que define la sintaxis del lenguaje

• Un intérprete/compilador que lo sabe interpretar y ejecutar

• Mecanismos para interactuar con el mundo exterior (llamadas al sistema)

• Librería estándar (consola, ficheros, red, etc,...)

• Utilidades (intérprete interactivo, depurador, paquetes)

Page 14: Curso node.js

¿Qué es Node.js?

• Una gramática que define la sintaxis del lenguaje

• Un intérprete/compilador que lo sabe interpretar y ejecutar

• Mecanismos para interactuar con el mundo exterior (llamadas al sistema)

• Librería estándar (consola, ficheros, red, etc,...)

• Utilidades (intérprete interactivo, depurador, paquetes)

v8 (JavaScript)

Node.js

Page 15: Curso node.js

¿Qué es Node.js?

Node.js Ruby Python Java

Lenguaje

Motor

Entorno

Framework

JavaScript Ruby Python Java

v8 YARV cPython JavaVM

Node.js Ruby Standard

Library

Python Standard

Library

Java SE

??? Rails Django Spring

Page 16: Curso node.js

¿Qué es Node.js?

Node.js es algo más:

• Una filosofía sobre cómo hacer las cosas

• Un modelo de ejecución singular

• Muy enfocado hacia aplicaciones de red

Page 17: Curso node.js

¿Qué es Node.js?

Node.js es algo más:

• Una filosofía sobre cómo hacer las cosas

• Un modelo de ejecución singular

• Muy enfocado hacia aplicaciones de red

Page 18: Curso node.js

Una filosofía

Node.js se crea con un objetivo en mente:

• Escribir aplicaciones muy eficientes (E/S) con el lenguaje dinámico más rápido (v8) para soportar miles de conexiones simultáneas

• Sin complicarse la vida innecesariamente

- Sin paralelismo

- Lenguaje sencillo y muy extendido

- API muy pequeña y muy consistente

- Apoyándose en Eventos y Callbacks

Page 19: Curso node.js
Page 20: Curso node.js

Una filosofía

Page 21: Curso node.js
Page 22: Curso node.js
Page 23: Curso node.js
Page 24: Curso node.js
Page 25: Curso node.js

Una filosofía

• No es la mejor opción para todos los casos

- Si puedes hacerlo con Rails/Django/Spring, hazlo

• Evita las “soluciones totales”

- Una necesidad, una herramienta

- Combina diferentes herramientas simples

• Entiende lo que estás haciendo

• Flexibilidad > magia

- Tu código es tu responsabilidad

- Cada aplicación es un mundo

Page 26: Curso node.js
Page 27: Curso node.js
Page 28: Curso node.js
Page 29: Curso node.js

¿Qué es Node.js?

Node.js es algo más:

• Una filosofía sobre cómo hacer las cosas

• Un modelo de ejecución singular

• Muy enfocado hacia aplicaciones de red

Page 30: Curso node.js

Un modelo de ejecución

Para entender Node.js, tenemos que entender dos ideas fundamentales:

• Concurrencia vs. paralelismo (asincronía)

• Eventos

Page 31: Curso node.js

Un modelo de ejecución

Concurrencia vs. Paralelismo

• ¿Qué significa que dos cosas suceden “a la vez”?

Page 32: Curso node.js

Un modelo de ejecución

¿Qué significa que dos cosas suceden “a la vez”?

Page 33: Curso node.js

Un modelo de ejecución

¿Qué significa que dos cosas suceden “a la vez”?

Page 34: Curso node.js

Un modelo de ejecución

Concurrencia vs. Paralelismo

• Paralelismo: varios actores realizando una acción cada uno simultáneamente

• Concurrencia: un solo actor, con varias tareas “activas” entre las que va alternando

Page 35: Curso node.js

Un modelo de ejecución

Paralelismo

Page 36: Curso node.js

Concurrencia

Un modelo de ejecución

Page 37: Curso node.js

Un modelo de ejecución

La potencia de Node.js es (curiosamente):

• Un modelo de ejecución concurrente

- Muchos clientes o tareas activas

• Pero NO paralelo

- Una única hebra

Page 38: Curso node.js

Un modelo de ejecución

• ¿Qué ventajas tiene evitar el paralelismo?

• ¿Qué desventajas?

• Y por tanto, ¿Cuándo es útil el modelo de Node.js?

Page 39: Curso node.js

Un modelo de ejecución

Patrón Reactor

• Un patrón de diseño para manejar eventos donde peticiones de servicio se transladan concurrentemente a un manejador central que se encarga de desmultiplexar las peticiones y despacharlas síncronamente mediante sus manejadores particulares asociados.

Page 40: Curso node.js

Un modelo de ejecución

Patrón Reactor

• Un patrón de diseño para manejar eventos donde peticiones de servicio se transladan concurrentemente a un manejador central que se encarga de desmultiplexar las peticiones y despacharlas síncronamente mediante sus manejadores particulares asociados.

• WAT??

Page 41: Curso node.js

Un modelo de ejecución

Patrón Reactor

Bucle Principal

Page 42: Curso node.js

Un modelo de ejecución

Patrón Reactor

Bucle Principal

Mundo Exterior

Page 43: Curso node.js

Un modelo de ejecución

Patrón Reactor

Bucle Principal

Mundo Exterior

Suceso

Page 44: Curso node.js

Patrón Reactor

Un modelo de ejecución

Bucle Principal

Mundo Exterior

Suceso

Page 45: Curso node.js

Patrón Reactor

Un modelo de ejecución

Bucle Principal

Mundo Exterior

Suceso

Suceso 2

Page 46: Curso node.js

Patrón Reactor

Un modelo de ejecución

Bucle Principal

Mundo Exterior

Suceso

Suceso 2

Page 47: Curso node.js

Patrón Reactor

Un modelo de ejecución

Bucle Principal

Mundo Exterior

Suceso

Suceso 2

Tick!

Page 48: Curso node.js

Patrón Reactor

Un modelo de ejecución

Bucle Principal

Mundo Exterior

Suceso

Suceso 2

Tick!Evento:

“Suceso”

Page 49: Curso node.js

Patrón Reactor

Un modelo de ejecución

Bucle Principal

Mundo Exterior

Suceso

Suceso 2

Tick!Evento:

“Suceso”

Manejadores

Page 50: Curso node.js

Patrón Reactor

Un modelo de ejecución

Bucle Principal

Mundo Exterior

Suceso

Suceso 2

Tick!

Manejadores

Page 51: Curso node.js

Patrón Reactor

Un modelo de ejecución

Bucle Principal

Mundo Exterior

Suceso

Suceso 2

Tick!

Manejadores

Suceso 3

Page 52: Curso node.js

Patrón Reactor

Un modelo de ejecución

Bucle Principal

Mundo Exterior

Suceso

Suceso 2

Tick!

ManejadoresSuceso 3

Page 53: Curso node.js

Patrón Reactor

Un modelo de ejecución

Bucle Principal

Mundo Exterior

Suceso

Suceso 2

Tick!

ManejadoresSuceso 3

Listo!

Page 54: Curso node.js

Patrón Reactor

Un modelo de ejecución

Bucle Principal

Mundo Exterior

Suceso

Suceso 2

Tick!

ManejadoresSuceso 3

Page 55: Curso node.js

Patrón Reactor

Un modelo de ejecución

Bucle Principal

Mundo Exterior

Suceso

Suceso 2

Tick!

ManejadoresSuceso 3

Evento: “Suceso 2”

???

Page 56: Curso node.js

Patrón Reactor

Un modelo de ejecución

Bucle Principal

Mundo Exterior

Suceso

Suceso 2

ManejadoresSuceso 3

Page 57: Curso node.js

Patrón Reactor

• Programación contra eventos

• Una vez puestos los manejadores, se pueden ejecutar en cualquier orden

- El orden lo determina el orden en que aparezcan sucesos

• La ejecución de los manejadores bloquea la hebra

• Nunca hay dos manejadores ejecutándose al mismo tiempo

• Muy eficiente... cuando E/S >> ejecuión del manejador

Un modelo de ejecución

Page 58: Curso node.js

¿Qué es Node.js?

Node.js es algo más:

• Una filosofía sobre cómo hacer las cosas

• Un modelo de ejecución singular

• Muy enfocado hacia aplicaciones de red

Page 59: Curso node.js

¿Qué es Node.js?

Muy enfocado hacia aplicaciones de red

• ¿Por qué?

Page 60: Curso node.js

¿Qué es Node.js?

Muy enfocado hacia aplicaciones de red

• Mucha E/S

- Por tanto, mucho tiempo con la CPU inactiva

• Para aprovechar ese tiempo, necesitas otros clientes que lo puedan aprovechar

• Similar a un camarero en un bar

- Es un “Patron Reactor” del mundo real

- Para aprovecharlo, tiene que haber varios clientes!

- Un cliente no termina más deprisa por dedicarle un camarero sólo a él

Page 61: Curso node.js

Toma de contacto

Page 62: Curso node.js

Ahora en serio: ¿Qué es Node.js?

Necesitas:

• Un ordenador

• Un editor de texto

• Saber abrir una consola/terminal

• Tener node instalado (mejor si es v. 0.10)

- Asegúrate de tener también npm

- Como alternativa: http://c9.io

• Saber manejarte con JavaScript

Page 63: Curso node.js

Ahora en serio: ¿Qué es Node.js?

console.log("Hola, Mundo!");

Page 64: Curso node.js

Ahora en serio: ¿Qué es Node.js?

$ node hola.js

Page 65: Curso node.js

Ahora en serio: ¿Qué es Node.js?

Page 66: Curso node.js

Ahora en serio: ¿Qué es Node.js?

Pero...

• ¿Y ese rollo del patrón Reactor?

• ¿Por qué termina el programa en vez de quedarse escuchando sucesos en el bucle principal?

Page 67: Curso node.js

Ahora en serio: ¿Qué es Node.js?

Lo que pasa en realidad:

• Node.js ejecuta todo tu código del tirón

• Coloca los manejadores que hayas definido

• Si no hay ningún manejador que se pueda ejecutar en el futuro, el programa termina!

Page 68: Curso node.js

Ahora en serio: ¿Qué es Node.js?

setTimeout(function() { console.log("Hola, Mundo del futuro!");}, 1000);

Page 69: Curso node.js

setInterval(function() { console.log("Hola otra vez, Mundo del futuro!");}, 1000);

Ahora en serio: ¿Qué es Node.js?

Page 70: Curso node.js

Ahora en serio: ¿Qué es Node.js?

setTimeout(function() { while (true);}, 100);

setInterval(function() { console.log("Hola otra vez, Mundo del futuro!");}, 1000);

Page 71: Curso node.js

Ahora en serio: ¿Qué es Node.js?

var start = Date.now();

setTimeout(function() { for (var i=Number.MAX_VALUE; i--;) { Math.pow(12345, 123455); }}, 100);

setInterval(function() { var now = Date.now(); console.log("Han pasado", now - start, "ms"); start = now; console.log("Hola otra vez, Mundo del futuro!");}, 1000);

Page 72: Curso node.js

Ahora en serio: ¿Qué es Node.js?

var start = Date.now();

setTimeout(function() { var timesLeft = Number.MAX_VALUE, r; (function unPoquitoMas() { if (timesLeft-- > 0) { r = Math.pow(12345, 123455); } setTimeout(unPoquitoMas, 0); }());}, 100);

setInterval(function() { var now = Date.now(); console.log("Han pasado", now - start, "ms"); start = now; console.log("Hola otra vez, Mundo del futuro!");}, 1000);

Page 73: Curso node.js

Ahora en serio: ¿Qué es Node.js?

Nos surgen problemas curiosos...

• ¿Excepciones?

Page 74: Curso node.js

Ahora en serio: ¿Qué es Node.js?

Nos surgen problemas curiosos...

• ¿Excepciones?

try { throw new Error("Peté!");} catch(e) { console.log("Excepción!");}

Page 75: Curso node.js

Ahora en serio: ¿Qué es Node.js?

Nos surgen problemas curiosos...

• ¿Excepciones?

try { setTimeout(function() { throw new Error("Peté!"); }, 0);} catch(e) { console.log("Excepción!");}

Page 76: Curso node.js

EventEmitter

Nuestro código va a estar dirigido por eventos

• Node.js tiene su propio “estándar”

• Trae una implementación del patrón Observador (o Pub/Sub): EventEmitter

• Todas sus librerías (y casi todos los paquetes) siguen este modelo

Page 77: Curso node.js

EventEmitter

var EventEmitter = require("events").EventEmitter;

var pub = new EventEmitter();

pub.on("ev", function(m) { console.log("[ev]", m);});

pub.once("ev", function(m) { console.log("(ha sido la primera vez)");});

pub.emit("ev", "Soy un Emisor de Eventos!");pub.emit("ev", "Me vas a ver muy a menudo...");

Page 78: Curso node.js

EventEmitter

var EventEmitter = require("events").EventEmitter;

var pub = new EventEmitter();

pub.on("ev", function(m) { console.log("[ev]", m);});

pub.once("ev", function(m) { console.log("(ha sido la primera vez)");});

pub.emit("ev", "Soy un Emisor de Eventos!");pub.emit("ev", "Me vas a ver muy a menudo...");

Page 79: Curso node.js

require/exports

require(<paquete o ruta>)

• Importar módulos (paquetes, otros ficheros)

• Garantía: una única vez

• Devuelve el módulo!

Page 80: Curso node.js

require/exports

exports.propiedadPublica = <valor>

• El otro lado del mecanismo

• Se puede exportar cualquier valor

Page 81: Curso node.js

require/exports

codigo.js

libreria.js

var lib = require("./libreria");console.log(lib.propiedad);

console.log("una vez");exports.propiedad = "Pública";

Page 82: Curso node.js

Para que te confíes...

Haz un módulo “reloj”

• Que exporte una clase Reloj

• Emita eventos “segundo”, “minuto” y “hora”

var Reloj = require("./reloj").Reloj;var reloj = new Reloj();

reloj.on("segundo", function(fecha) { console.log("Un segundo! son las:", fecha); reloj.removeAllListeners("segundo");});

Page 83: Curso node.js

Para que te confíes...

Un truco:

• require(“util”).inherits• inherits(constructor, superConstructor)

var inherits = require("util").inherits;

function MiClase() { // ...}

function MiSubClase() { // ...}inherits(MiSubClase, MiClase);

Page 84: Curso node.js

JavaScript y el Universo

El JavaScript del navegador vive en un mundo ideal

• No hay SO con el que lidiar

• No hay datos binarios

• Todo es accesible con objetos y valores primitivos

• Apenas hay E/S, siempre con valores simples (strings)

• Un único usuario

Page 85: Curso node.js

JavaScript y el Universo

En Node.js, las cosas son de otra manera...

• Llamadas al sistema

• Mucha E/S

• Datos binarios (ficheros, sockets, etc)

• Descriptores de ficheros

• Puertos

• ...

Page 86: Curso node.js

JavaScript y el Universo

Tenemos que añadir nuevos conceptos a nuestro JS

• Streams (lectura, escritura o duplex)

• Buffers (representación de datos binarios)

• Procesos

• Rutas

• http://nodejs.org/api/index.html

Page 87: Curso node.js

JavaScript y el Universo

Buffers

• Una tira de bytes (datos binarios)

• Similar a un array de enteros

• Tamaño fijo

• Manipular datos directamente

- Sockets

- Implementar protocolos complejos

- Manipulación de ficheros/imágenes

- Criptografía

- ...

Page 88: Curso node.js

JavaScript y el Universo

Buffers

var buf = new Buffer(100);buf.write("abcd", 0, 4, "ascii");

console.log(buf.toString("ascii"));

Page 89: Curso node.js

JavaScript y el Universo

Buffers

var buf = new Buffer(100);buf.write("abcd", 0, 4, "ascii");

console.log(buf.toString("ascii"));

Tamaño del buffer

Page 90: Curso node.js

JavaScript y el Universo

Buffers

var buf = new Buffer(100);buf.write("abcd", 0, 4, "ascii");

console.log(buf.toString("ascii"));

Datos

Posición

Longitud

Codificación

Page 91: Curso node.js

JavaScript y el Universo

Buffers

var buf = new Buffer(100);buf.write("abcd", 0, 4, "ascii");

console.log(buf.toString("ascii"));

Codificación

Page 92: Curso node.js

JavaScript y el Universo

Buffers

Page 93: Curso node.js

JavaScript y el Universo

function Bitmap(w, h) { this.width = w; this.height = h; this.header = "P6\n" + w + " " + h + "\n255\n"; this.buffer = new Buffer(w*h*3+this.header.length); this.buffer.write(this.header, 0, this.header.length, "ascii");}Bitmap.prototype = { putPixel: function(x, y, color) { var pos = this.header.length + (y*this.width*3) + x*3; this.buffer.write(color, pos, 3, "hex"); }, fill: function(color) { this.buffer.fill(255, this.header.length); }, render: function() { process.stdout.write(this.buffer); }};

Page 94: Curso node.js

JavaScript y el Universo

var bitmap = new Bitmap(1, 1);bitmap.putPixel(0, 0, "000000");bitmap.render();

Page 95: Curso node.js

JavaScript y el Universo

var bitmap = new Bitmap(10, 10);bitmap.fill("ffffff");bitmap.putPixel(0, 0, "000000");bitmap.render();

Page 96: Curso node.js

JavaScript y el Universo

Streams

• “Chorros” de información

- Lectura / Escritura / Duplex

• Detrás de muchos mecanismos de Node.js

- stdin/stdout

- request HTTP

- sockets

- etc...

• Instancias de EventEmitter

• Acceso asíncrono

Page 97: Curso node.js

JavaScript y el Universo

Streams

• Es raro crear streams directamente

• Pero muchos recursos nos ofrecen este interfaz

Page 98: Curso node.js

JavaScript y el Universo

Streams de lectura

• Entrada de datos

• Eventos:

- readable: hay datos para leer

- data: se ha leído un trozo y está disponible

- end: se agotó el stream

- close: se cerró el stream

- error: algo malo sucedió leyendo los datos

Page 99: Curso node.js

JavaScript y el Universo

Streams de lectura

var fs = require("fs");

var readStream = fs.createReadStream("/etc/passwd", { flags: "r", encoding: "ascii", autoClose: true});

readStream.on("data", function(chunk) { console.log("He leído:", chunk.length);});

readStream.on("end", function() { console.log("ya está!");});

Page 100: Curso node.js

Una fácil

Haz un programa que cuente las líneas de un fichero

Page 101: Curso node.js

Una fácil

Haz un programa que cuente las líneas de un fichero

• Tienes los argumentos con los que se ha llamado al programa en process.argv

Page 102: Curso node.js

JavaScript y el Universo

Streams de escritura

• Salida de datos

• Operaciones:- write(chunk, [encoding], [callback])- end([chunk], [encoding], [callback])

• Eventos:

- drain: el buffer del stream está vacío (puedes escribir más)

- finish: se ha terminado de escribir toda la info y se ha cerrado el stream

Page 103: Curso node.js

JavaScript y el Universo

Streams de escritura

var fs = require("fs");

var writeStream = fs.createWriteStream(process.argv[2], { flags: "w", encoding: "utf-8"});

for (var i=100; i--;) { writeStream.write(i + " líneas más para terminar...\n");}writeStream.end("FIN");

writeStream.on("finish", function() { console.log("Listo!");});

Page 104: Curso node.js

¿Preguntas?

Un buen momento para despejar dudas antes de seguir...

Page 105: Curso node.js

HTTP(por fin...)

Page 106: Curso node.js

HTTP

Node.js trae un servidor web estupendo

• Asíncrono

- No bloquea la hebra

- Cada cliente conectado consume muy poquitos recursos

- Genial para miles de conexiones simultáneas

• Relativamente rápido

• Interfaz sencilla

• HTTP puro y duro, sin adornos

• Basado en streams y eventos

Page 107: Curso node.js

HTTP

var http = require("http");

var server = http.createServer();

server.on("request", function(req, res) { res.end("Hola, Mundo!");});

server.listen(3000);

Page 108: Curso node.js

HTTP

var http = require("http");

var server = http.createServer();

server.on("request", function(req, res) { res.end("Hola, Mundo!");});

server.listen(3000);

El módulo

Page 109: Curso node.js

HTTP

var http = require("http");

var server = http.createServer();

server.on("request", function(req, res) { res.end("Hola, Mundo!");});

server.listen(3000);

Respuesta(stream)

Request(objeto)

Eventos!

Page 110: Curso node.js

HTTP

El servidor HTTP

• Eventos:

- connection- request

• Operaciones:- createServer([requestCallback])- listen(puerto, [hostname], [backlog], [callback])- close([callback])

Page 111: Curso node.js

HTTP

http.IncomingMessage (parametro “req”)

• Representa la petición HTTP del cliente

• Propiedaes:

- req.headers: cabeceras de la petición

- req.method: verbo HTTP

- req.url: url de la petición

- req.conneciton.remoteAddress: ip del cliente

Page 112: Curso node.js

HTTP

http.ServerResponse (parametro “res”)

• Representa la respuesta del servidor

• Stream de escritura

• Operaciones adicionales:

- res.writeHead(statusCode, [headers]): código HTTP y cabeceras de la respuesta

- res.statusCode: [propiedad] Otra forma de establecer el código HTTP de la respuesta

- res.setHeader(name, value): Otra forma de establecer las cabeceras, de una en una

Page 113: Curso node.js

Manos a la obra

Escribe un servidor web que devuelva la hora

Page 114: Curso node.js

Un consejo: nodemon

Para mejorar el flow de trabajo:

• Editar, matar el proceso, volver a lanzarlo, probar... muy tedioso!

• Instálate nodemonnpm install -g nodemon

• Reinicia el servidor cada vez que cambia el fichero

$ nodemon <fichero.js>

Page 115: Curso node.js

HTTP

Node.js trae un módulo para parsear URLs

var http = require("http"), url = require("url"), inspect = require("util").inspect;

var server = http.createServer();

server.on("request", function(req, res) { var urlData = url.parse(req.url, true); res.end(inspect(urlData, {colors: false}));});

server.listen(3000);

Page 116: Curso node.js

HTTP

Node.js trae un módulo para parsear URLs

var http = require("http"), url = require("url"), inspect = require("util").inspect;

var server = http.createServer();

server.on("request", function(req, res) { var urlData = url.parse(req.url, true); res.end(inspect(urlData, {colors: false}));});

server.listen(3000);

Page 117: Curso node.js

HTTP

Page 118: Curso node.js

Un poco más difícil

Escribe un servidor de ficheros

• Lee el pathname de la URL

• Busca un fichero con esa ruta dentro de ./public

• Si existe, lo sirve

• Si no existe, devuelve 404

Page 119: Curso node.js

Un poco más difícil: notas

fs.exists(filePath, callback)

• Llama al callback con true si el fichero filePath existe

• O con false si no existe

var fs = require("fs");

fs.exists("./hola.txt", function(exists) { console.log(exists);});

Page 120: Curso node.js

Un poco más difícil: notas

fs.readFile(filePath, callback)

• Otra manera de leer ficheros

• Intenta leer filePath e invoca a callback con dos parámetros:

- err: null si todo ha ido bien o, si hubo error, el error

- data: todo el contenido del fichero (si fue posible leerlo)

var fs = require("fs");

fs.readFile("./hola.txt", function(err, data) { if (err) { /* error! */ } console.log(data);});

Page 121: Curso node.js

Un poco más difícil: epílogo

¿Cómo podríamos añadir un caché para no leer los ficheros del disco duro más de una vez?

¿Cómo podríamos hacer que los ficheros cacheados se liberaran después de x minutos?

¿Cómo podríamos escribir un registro de acceso?

Page 122: Curso node.js

Un poco más difícil: variaciones

Haz un contador de aperturas de emails

• Sirviendo un .gif de 1x1 y contando cuantas veces lo sirves

• Mejor aún, cuenta solo a cuántas IPs lo sirves

Haz un servicio de avatares que cambie según:

• La hora del día

• La frecuencia con que es pedido (popularidad)

Page 123: Curso node.js

Un poco más difícil: variaciones

Haz un “servidor hellban”

Si la IP está en la lista negra, todas las peticiones tienen un delay aleatorio que se va incrementando con cada petición consecutiva

Page 124: Curso node.js

Servidor A/B Testing

Cada vez que un cliente pide un recurso:

• Se comprueba si ya tiene caso asignado (por IP) o se le asigna uno

• Se construye la ruta del recurso según el caso asignado y se sirve

• Si pide “success.png”, se marca su caso como exitoso y se le sirve la imágen

• /stats devuelve un JSON con info sobre los casos (visitas totales y visitas exitosas por cada caso)

Page 125: Curso node.js

Servidor A/B Testing

Tenéis maqueta y recursos en :

/tema1/abtesting

Page 126: Curso node.js

Promesas

Page 127: Curso node.js

Node.js y CPS

Node.js maneja la asincronía utilizando callbacks

• Continuation Passing Style

• Continuaciones explícitas como funciones

• “Cuando termines, ejecuta esta otra función”

Page 128: Curso node.js

Node.js y CPS

Los callbacks tienen muchas ventajas

• Muy fáciles de entender e implementar

• Familiares para el programador JavaScript

• Extremadamente flexibles (clausuras, funciones de primer orden, etc, ...)

• Un mecanismo universal de asincronía/continuaciones

Pero...

Page 129: Curso node.js

Node.js y CPS

var fs = require("fs");

fs.exists("./hola.txt", function(exists) { if (exists) { fs.readFile("./hola.txt", function(err, data) { if (err) { // MANEJO DE ERROR } else { fs.writeFile("./copia.txt", data, function(err) { if (err) { // MANEJO DE ERROR } else { console.log("OK!"); } }) } }) } else { // MANEJO DE ERROR }});

Page 130: Curso node.js

Node.js y CPS

var fs = require("fs");

fs.exists("./hola.txt", function(exists) { if (exists) { fs.readFile("./hola.txt", function(err, data) { if (err) { // MANEJO DE ERROR } else { fs.writeFile("./copia.txt", data, function(err) { if (err) { // MANEJO DE ERROR } else { console.log("OK!"); } }) } }) } else { // MANEJO DE ERROR }});

Page 131: Curso node.js

Node.js y CPS

var fs = require("fs");

fs.exists("./hola.txt", function(exists) { if (exists) { fs.readFile("./hola.txt", function(err, data) { if (err) { // MANEJO DE ERROR } else { fs.writeFile("./copia.txt", data, function(err) { if (err) { // MANEJO DE ERROR } else {

console.log("OK!");}

}) } }) } else { // MANEJO DE ERROR }});

Page 132: Curso node.js

Node.js y CPS

var fs = require("fs");

fs.exists("./hola.txt", function(exists) { if (exists) { fs.readFile("./hola.txt", function(err, data) { if (err) { // MANEJO DE ERROR } else { fs.writeFile("./copia.txt", data, function(err) { if (err) { // MANEJO DE ERROR } else {

console.log("OK!");}

}) } }) } else { // MANEJO DE ERROR }});

Pyramid of DoomCallback Hell

Page 133: Curso node.js

Node.js y CPS

var fs = require("fs");

fs.exists("./hola.txt", function(exists) { if (exists) { fs.readFile("./hola.txt", function(err, data) { if (err) { // MANEJO DE ERROR } else { fs.writeFile("./copia.txt", data, function(err) { if (err) { // MANEJO DE ERROR } else { console.log("OK!"); } }) } }) } else { // MANEJO DE ERROR }});

Page 134: Curso node.js

CPS vs. Promesas

Page 135: Curso node.js

Promesas

Una manera alternativa de modelar asincronía

• Construcción explícita del flujo de ejecución

• Separación en bloques consecutivos

• Manejo de errores más controlado

• Combinación de diferentes flujos asíncronos

Page 136: Curso node.js

Promesas

Una promesa = Un flujo de ejecución

promesa.then(function() { // bloque return readFilePromise("./hola.txt");}).then(function(data) { // bloque return writeFilePromise("./copia.txt");}).then(function() { console.log("listo!");}).fail(function(err) { // MANEJO DEL ERROR});

Page 137: Curso node.js

Promesas

Una promesa = Un flujo de ejecución

promesa.then(function() { // bloque return readFilePromise("./hola.txt");}).then(function(data) { // bloque return writeFilePromise("./copia.txt");}).then(function() { console.log("listo!");}).fail(function(err) { // MANEJO DEL ERROR});

Page 138: Curso node.js

Promesas

Una promesa = Un flujo de ejecución

promesa.then(function() { // bloque return readFilePromise("./hola.txt");}).then(function(data) { // bloque return writeFilePromise("./copia.txt");}).then(function() { console.log("listo!");}).fail(function(err) { // MANEJO DEL ERROR});

Page 139: Curso node.js

Promesas

Una promesa = Un flujo de ejecución

promesa.then(function() { // bloque return readFilePromise("./hola.txt");}).then(function(data) { // bloque return writeFilePromise("./copia.txt");}).then(function() { console.log("listo!");}).fail(function(err) { // MANEJO DEL ERROR});

Page 140: Curso node.js

Promesas

Una promesa = Un flujo de ejecución

promesa.then(function() { // bloque return readFilePromise("./hola.txt");}).then(function(data) { // bloque return writeFilePromise("./copia.txt");}).then(function() { console.log("listo!");}).fail(function(err) { // MANEJO DEL ERROR});

Page 141: Curso node.js

Promesas

Una promesa = Un flujo de ejecución

promesa.then(function() { // bloque return readFilePromise("./hola.txt");}).then(function(data) { // bloque return writeFilePromise("./copia.txt");}).then(function() { console.log("listo!");}).fail(function(err) { // MANEJO DEL ERROR});

Page 142: Curso node.js

Promesas

Una promesa = Un flujo de ejecución

promesa.then(function() { // bloque return readFilePromise("./hola.txt");}).then(function(data) { // bloque return writeFilePromise("./copia.txt");}).then(function() { console.log("listo!");}).fail(function(err) { // MANEJO DEL ERROR});

Page 143: Curso node.js

Promesas

Una promesa = Un flujo de ejecución

promesa.then(function() { // bloque return readFilePromise("./hola.txt");}).then(function(data) { // bloque return writeFilePromise("./copia.txt");}).then(function() { console.log("listo!");}).fail(function(err) { // MANEJO DEL ERROR});

Page 144: Curso node.js

Promesas

Una promesa = Un flujo de ejecución

promesa.then(function() { // bloque return readFilePromise("./hola.txt");}).then(function(data) { // bloque return writeFilePromise("./copia.txt");}).then(function() { console.log("listo!");}).fail(function(err) { // MANEJO DEL ERROR});

Page 145: Curso node.js

Promesas

Una promesa = Un flujo de ejecución

Page 146: Curso node.js

Promesas

¡Pero aún no hemos ejecutado nada! Solamente hemos construído el flujo

Page 147: Curso node.js

Promesas

¿Ventajas?

• Código mucho más ordenado y más legible

• Mejor control de errores

• Podemos manipular el flujo

- Añadir nuevas etapas

- Devolverlo en funciones

- Pasarlo como parámetro

• Podemos combinar varios flujos

Page 148: Curso node.js

Promesas

function copyFile(from, to) { return readFilePromise(from); .then(function(data) { // bloque return writeFilePromise(to); });}

copyFile("./hola.txt", "./copia.txt").then(function() { return copyFile("./otraCosa.txt", "./copia2.txt");}).then(function() { console.log("listo!");}).fail(function(err) { console.log("Oops!");})

Page 149: Curso node.js

Promesas

function copyFile(from, to) { return readFilePromise(from); .then(function(data) { // bloque return writeFilePromise(to); });}

copyFile("./hola.txt", "./copia.txt").then(function() { return copyFile("./otraCosa.txt", "./copia2.txt");}).then(function() { console.log("listo!");}).fail(function(err) { console.log("Oops!");})

Page 150: Curso node.js

Promesas

function copyFile(from, to) { return readFilePromise(from); .then(function(data) { // bloque return writeFilePromise(to); });}

copyFile("./hola.txt", "./copia.txt").then(function() { return copyFile("./otraCosa.txt", "./copia2.txt");}).then(function() { console.log("listo!");}).fail(function(err) { console.log("Oops!");})

Page 151: Curso node.js

Promesas

function copyFile(from, to) { return readFilePromise(from); .then(function(data) { // bloque return writeFilePromise(to); });}

copyFile("./hola.txt", "./copia.txt").then(function() { return copyFile("./otraCosa.txt", "./copia2.txt");}).then(function() { console.log("listo!");}).fail(function(err) { console.log("Oops!");})

Page 152: Curso node.js

Promesas

function copyFile(from, to) { return readFilePromise(from); .then(function(data) { // bloque return writeFilePromise(to); });}

copyFile("./hola.txt", "./copia.txt").then(function() { return copyFile("./otraCosa.txt", "./copia2.txt");}).then(function() { console.log("listo!");}).fail(function(err) { console.log("Oops!");})

Page 153: Curso node.js

Promesas

function copyFile(from, to) { return readFilePromise(from); .then(function(data) { // bloque return writeFilePromise(to); });}

copyFile("./hola.txt", "./copia.txt").then(function() { return copyFile("./otraCosa.txt", "./copia2.txt");}).then(function() { console.log("listo!");}).fail(function(err) { console.log("Oops!");})

Page 154: Curso node.js

Promesas

function copyFile(from, to) { return readFilePromise(from); .then(function(data) { // bloque return writeFilePromise(to); });}

copyFile("./hola.txt", "./copia.txt").then(function() { return copyFile("./otraCosa.txt", "./copia2.txt");}).then(function() { console.log("listo!");}).fail(function(err) { console.log("Oops!");})

Page 155: Curso node.js

Promesas

function copyFile(from, to) { return readFilePromise(from); .then(function(data) { // bloque return writeFilePromise(to); });}

copyFile("./hola.txt", "./copia.txt").then(function() { return copyFile("./otraCosa.txt", "./copia2.txt");}).then(function() { console.log("listo!");}).fail(function(err) { console.log("Oops!");})

Page 156: Curso node.js

Promesas

.then(success, [error])

• Concatena bloques

• El nuevo bloque (success)...

- Sólo se ejecuta si el anterior se ha ejecutado sin errores

- Recibe como parámetro el resultado del bloque anterior

- Devuelve el valor que se le pasará el siguiente bloque➡ Si es un dato inmediato, se pasa tal cual

➡ Si es una promesa, se resuelve antes de llamar al siguiente bloque

• El segundo parámetro pone un manejador de error

- Equivalente a llamar a .fail(error)

• .then(...) siempre devuelve una nueva promesa

Page 157: Curso node.js

Promesas

var promesa = readFilePromise("./hola.txt");

promesa = promesa.then(function(data) { console.log("Contenido del fichero: ", data);}, function(err) { console.log("Ooops!", err);})

Page 158: Curso node.js

Promesas

var promesa = readFilePromise("./hola.txt");

promesa = promesa.then(function(data) { console.log("Contenido del fichero: ", data);}, function(err) { console.log("Ooops!", err);})

Page 159: Curso node.js

Promesas

var promesa = readFilePromise("./hola.txt");

promesa = promesa.then(function(data) { console.log("Contenido del fichero: ", data);}, function(err) { console.log("Ooops!", err);})

Page 160: Curso node.js

Promesas

var promesa = readFilePromise("./hola.txt");

promesa = promesa.then(function(data) { console.log("Contenido del fichero: ", data);}, function(err) { console.log("Ooops!", err);})

Page 161: Curso node.js

Promesas

var promesa = readFilePromise("./hola.txt");

promesa = promesa.then(function(data) { console.log("Contenido del fichero: ", data);}, function(err) { console.log("Ooops!", err);})

Page 162: Curso node.js

Promesas

var promesa = readFilePromise("./hola.txt");

promesa = promesa.then(function(data) { console.log("Contenido del fichero: ", data);}, function(err) { console.log("Ooops!", err);})

Page 163: Curso node.js

Promesas

var promesa = readFilePromise("./hola.txt");

promesa = promesa.then(function(data) { console.log("Contenido del fichero: ", data);}, function(err) { console.log("Ooops!", err);})

Page 164: Curso node.js

Promesas

var promesa = readFilePromise("./hola.txt");

promesa.then(function(data) { return 1;}).then(function(uno) { return 2;}, function(err) { console.log("Oh, oh...");}).then(function(dos) { return 3;}).fail(function(err) { console.log("Oops!");});

data

1

2

3

Page 165: Curso node.js

Promesas

var promesa = readFilePromise("./hola.txt");

var promesa2 = promesa.then(function(data) { return 1;});

var promesa3 = promesa.then(function(data) { return 2;});

data data

1 2

Page 166: Curso node.js

Promesas

data data

1 2

var promesa = readFilePromise("./hola.txt");

var promesa2 = promesa.then(function(data) { return 1;});

var promesa3 = promesa.then(function(data) { return 2;});

promesa3.then(function(dos) { console.log("Ping!");});

Page 167: Curso node.js

Promesas

data data

12

var promesa = readFilePromise("./hola.txt");

var promesa2 = promesa.then(function(data) { return 1;});

var promesa3 = promesa.then(function(data) { return 2;});

promesa3.then(function(dos) { console.log("Ping!");});promesa3.then(function(dos) { console.log("Pong!");});

2

Page 168: Curso node.js

Promesas

data data

12

var promesa = readFilePromise("./hola.txt");

var promesa2 = promesa.then(function(data) { return 1;});

var promesa3 = promesa.then(function(data) { return 2;});

promesa3.then(function(dos) { console.log("Ping!");});promesa3.then(function(dos) { console.log("Pong!");}, function(err) { console.log("Oh, oh...");});

2

Page 169: Curso node.js

Promesas: walled garden

Vamos a empezar a trastear con promesas...

• Pero, de momento, con una librería de mentira

• Para asentar conceptos

• (y perder el miedo)

Page 170: Curso node.js

Promesas: walled garden

var fakePromise = require("./fakePromise");

var promise = fakePromise.gimmePromise();

promise.then(function() { return "hola";}).then(function(msg) { console.log(msg); return "mundo";}).then(function(msg) { console.log(msg);});

Page 171: Curso node.js

Promesas: walled garden

var fakePromise = require("./fakePromise");

var promise = fakePromise.gimmePromise();

promise.then(function() { return "hola";}).then(function(msg) { console.log(msg); return "mundo";}).then(function(msg) { console.log(msg);});

Page 172: Curso node.js

Promesas: walled garden

¿Qué se muestra por consola al ejecutar esto?

var fakePromise = require("./fakePromise");

var promise = fakePromise.gimmePromise();

promise.then(function() { return "hola";}).then(function(msg) { console.log(msg); return "mundo";}).then(function(msg) { console.log(msg);});

Page 173: Curso node.js

Promesas: walled garden

¿Por qué?

var fakePromise = require("./fakePromise");

var promise = fakePromise.gimmePromise();

promise.then(function() { return "hola";}).then(function(msg) { console.log(msg); return "mundo";}).then(function(msg) { console.log(msg);});

Page 174: Curso node.js

Promesas: walled garden

Una promesa = un flujo de ejecución

• Configuramos un árbol de flujos e ejecución

• Añadimos bloques a promesas

• Pero... ¿Cuándo se empieza a ejecutar?

Page 175: Curso node.js

Promesas: walled garden

Una promesa = un flujo de ejecución

• Configuramos un árbol de flujos e ejecución

• Añadimos bloques a promesas

• Pero... ¿Cuándo se empieza a ejecutar?

• Cuando se resuelva la primera promesa del árbol

Page 176: Curso node.js

Promesas: walled garden

Las promesas se resuelven o se rechazan

Si se resuelven:

• Se resuelven a un valor (si es un bloque, su valor de retorno)

• Representan el estado “OK, puede seguir el siguiente”

Si se rechazan:

• Representan un error

• La ejecución cae hasta el siguiente manejador de errores

• Se saltan todos los estados desde el error hasta el manejador

Page 177: Curso node.js

Promesas: walled garden

var fakePromise = require("./fakePromise");

var promise = fakePromise.gimmePromise();

promise.then(function() { return "hola";}).then(function(msg) { console.log(msg); return "mundo";}).then(function(msg) { console.log(msg);});

promise.resolve(42);

Page 178: Curso node.js

Promesas: walled garden

var fakePromise = require("./fakePromise");

var promise = fakePromise.gimmePromise();

promise.then(function() { return "hola";}).then(function(msg) { console.log(msg); return "mundo";}).then(function(msg) { console.log(msg);}).fail(function(err) { console.log(err); return "MAL!"})

promise.reject(new Error("Oops!"));

Page 179: Curso node.js

Promesas: walled garden

Otra manera de rechazar una promesa es lanzar una excepción desde el interior de un bloque

promise.then(function() { return "hola";}).then(function(msg) { console.log(msg); throw new Error("Oh, oh..."); return "mundo";})

Page 180: Curso node.js

Promesas: walled garden

var fakePromise = require("./fakePromise");

var promise = fakePromise.gimmePromise();

promise.then(function() { return "hola";}).then(function(msg) { console.log(msg); throw new Error("Oh, oh..."); return "mundo";}).then(function(msg) { console.log(msg);}).fail(function(err) { console.log(err); return "MAL!"})

promise.resolve(42);

Page 181: Curso node.js

Promesas: walled garden

var fakePromise = require("./fakePromise");

var promise = fakePromise.gimmePromise();

promise.then(function() { return "hola";}).then(function(msg) { console.log(msg); throw new Error("Oh, oh..."); return "mundo";}).then(function(msg) { console.log(msg);}).fail(function(err) { console.log(err); return "MAL!"})

promise.resolve(42);

Page 182: Curso node.js

Promesas: walled garden

var fakePromise = require("./fakePromise");

var promise = fakePromise.gimmePromise();

promise.then(function() { return "hola";}).then(function(msg) { console.log(msg); throw new Error("Oh, oh..."); return "mundo";}).then(function(msg) { console.log(msg);}).fail(function(err) { console.log(err); return "MAL!"})

promise.resolve(42);

Page 183: Curso node.js

Promesas: walled garden

var fakePromise = require("./fakePromise");

var promise = fakePromise.gimmePromise();

promise.then(function() { return "hola";}).then(function(msg) { console.log(msg); throw new Error("Oh, oh..."); return "mundo";}).then(function(msg) { console.log(msg);}).fail(function(err) { console.log(err); return "MAL!"})

promise.resolve(42);

Page 184: Curso node.js

Promesas: walled garden

var fakePromise = require("./fakePromise");var promise = fakePromise.gimmePromise();

promise.then(function(msg) { console.log(msg); throw new Error("Oh, oh..."); return "mundo";}).then(function(msg) { console.log(msg); return "OK!";}).fail(function(err) { console.log(err); return "MAL!";}).then(function(msg) { console.log(msg);})

promise.resolve("hola");

Page 185: Curso node.js

Promesas: walled garden

Propagación de promesas

var fakePromise = require("./fakePromise");

var promise = fakePromise.gimmePromise(), promise2 = fakePromise.gimmePromise();

promise.then(function(val) { console.log("promise:", val); promise2.then(function(val) { console.log("promise2:", val); });});

promise.resolve(42);setTimeout(promise2.resolve.bind(promise2, 12), 2000);

Page 186: Curso node.js

Promesas: walled garden

Propagación de promesas

var fakePromise = require("./fakePromise");

var promise = fakePromise.gimmePromise(), promise2 = fakePromise.gimmePromise();

promise.then(function(val) { console.log("promise:", val); promise2.then(function(val) { console.log("promise2:", val); });});

promise.resolve(42);setTimeout(promise2.resolve.bind(promise2, 12), 2000);

Page 187: Curso node.js

Promesas: walled garden

Propagación de promesas

var fakePromise = require("./fakePromise");

var promise = fakePromise.gimmePromise(), promise2 = fakePromise.gimmePromise();

promise.then(function(val) { console.log("promise:", val); return promise2}).then(function(val) { console.log("promise2:", val);});

promise.resolve(42);setTimeout(promise2.resolve.bind(promise2, 12), 2000);

Page 188: Curso node.js

Promesas: walled garden

Propagación de promesas

var fakePromise = require("./fakePromise");

var promise = fakePromise.gimmePromise(), promise2 = fakePromise.gimmePromise();

promise.then(function(val) { console.log("promise:", val); return promise2}).then(function(val) { console.log("promise2:", val);});

promise.resolve(42);setTimeout(promise2.resolve.bind(promise2, 12), 2000);

Page 189: Curso node.js

Promesas: walled garden

¿Y al revés?

var fakePromise = require("./fakePromise");

var promise = fakePromise.gimmePromise(), promise2 = fakePromise.gimmePromise();

promise.then(function(val) { console.log("promise:", val); return promise2}).then(function(val) { console.log("promise2:", val);});

setTimeout(promise.resolve.bind(promise, 42), 2000);promise2.resolve(12);

Page 190: Curso node.js

Diferidos

En el mundo real, las cosas son un poco distintas

Las “promesas” están divididas en dos objetos:

• La promesa en sí

- Interfaz limitada a la construcción de flujos

- .then, .fail y algún método más

• Su diferido

- Interfaz limitada a controlar el estado

- .reject y .resolve

Page 191: Curso node.js

Diferidos

var Q = require("q");

var defer = Q.defer(), promise = defer.promise;

// flujo

promise.then(function(val) { console.log("val:", val);}, function(err) { console.log("Error!");});

// estado

defer.resolve(42);

Page 192: Curso node.js

Diferidos

var Q = require("q");

var defer = Q.defer(), promise = defer.promise;

// flujo

promise.then(function(val) { console.log("val:", val);}, function(err) { console.log("Error!");});

// estado

defer.resolve(42);

Page 193: Curso node.js

Diferidos

Cumplen dos roles diferentes:

• Diferido lo controla el gestor del recurso/proceso que se está modelando

• Promesa es el interfaz para que el consumidor del recurso pueda construir el flujo que necesita

Page 194: Curso node.js

Promesas

Un ejemplo realista: readFilePromise(file)• Implementa la función

• Basándote en fs.readFile()

• Devuelve una promesa

• Se resuelve con el contenido del fichero

• O se rechaza con el error

Page 195: Curso node.js

Promesas

function readFilePromise(filePath) { // ???}

readFilePromise("./hola.txt").then(function(contenido) { console.log("contenido:", contenido.toString());}, function(err) { console.log("ERROR!", err);});

Page 196: Curso node.js

Promesas

var fs = require("fs"), Q = require("q");

function readFilePromise(filePath) { var defer = Q.defer(); fs.readFile(filePath, function(err, data) { err ? defer.reject(err) : defer.resolve(data); }) return defer.promise;}

readFilePromise("./hola.txt").then(function(contenido) { console.log("contenido:", contenido.toString());}, function(err) { console.log("ERROR!", err);});

Page 197: Curso node.js

Promesas

var fs = require("fs"), Q = require("q");

function readFilePromise(filePath) { var defer = Q.defer(); fs.readFile(filePath, function(err, data) { err ? defer.reject(err) : defer.resolve(data); }) return defer.promise;}

readFilePromise("./hola.txt").then(function(contenido) { console.log("contenido:", contenido.toString());}, function(err) { console.log("ERROR!", err);});

Gestor del recurso

Consumidor del recurso

Page 198: Curso node.js

Promesas: paréntesis

Intenta modificar el servidor de ficheros estáticos para que utilice promesas:

• fileExistsPromise

• readFilePromise

Page 199: Curso node.js

Promesas: combinaciones

Q es una librería muy potente

• Trae un montón de funcionalidad

• Muy recomendable leer la documentación

• Nosotros vamos a ver dos cosas esenciales:

- Combinación de promesas en paralelo

- Adaptadores de llamadas con formato Node.js

Page 200: Curso node.js

Q.all([promise, promise, ...])

Crea una promesa que representa al conjunto

• La nueva promesa se resuelve cuando todas las del conjunto se hayan resuelto

• Su valor es un array con los valores de las promesas

• Si una del conjunto es rechazada, la nueva promesa también es rechazada

Page 201: Curso node.js

Q.all([promise, promise, ...])

var Q = require("q");

var def1 = Q.defer(), prom1 = def1.promise, def2 = Q.defer(), prom2 = def2.promise;

Q.all([prom1, prom2]).then(function(v) { console.log("Todas resueltas!"); console.log(v); // [42, 13]})

def1.resolve(42);setTimeout(def2.resolve.bind(def2, 13), 1000);

Page 202: Curso node.js

Q.all([promise, promise, ...])

Page 203: Curso node.js

Q.spread([v1, v2], callback)

“Reparte” valores de un array en parámetros

var Q = require("q");

Q.spread([1,2,3,4], function(a, b, c, d) { console.log(a, b, c, d); // 1 2 3 4})

Page 204: Curso node.js

Q.spread([v1, v2], callback)

Invoca automáticamente a Q.all(...)!

var Q = require("q");

var def1 = Q.defer(), def2 = Q.defer(), def3 = Q.defer(), pro1 = def1.promise, pro2 = def2.promise, pro3 = def3.promise;

pro1.then(function(v1) { console.log(v1); return [pro2, pro3];}).spread(function(v2, v3) { console.log(v2, v3);});

def1.resolve(42);setTimeout(def2.resolve.bind(def2, 13), 1000);setTimeout(def3.resolve.bind(def3, 71), 200);

Page 205: Curso node.js

Q.spread([v1, v2], callback)

Invoca automáticamente a Q.all(...)!

var Q = require("q");

var def1 = Q.defer(), def2 = Q.defer(), def3 = Q.defer(), pro1 = def1.promise, pro2 = def2.promise, pro3 = def3.promise;

pro1.then(function(v1) { console.log(v1); return [pro2, pro3];}).spread(function(v2, v3) { console.log(v2, v3);});

def1.resolve(42);setTimeout(def2.resolve.bind(def2, 13), 1000);setTimeout(def3.resolve.bind(def3, 71), 200);

Page 206: Curso node.js

Q.ninvoke(ctx, method, arg, [arg])

Adaptador para convertir llamadas Node.js en promesas

var Q = require("q"), fs = require("fs");

Q.ninvoke(fs, "readFile", "./hola.txt").then(function(data) { console.log("Contenido: ", data.toString());}).fail(function(err) { console.log("Oops!", err);});

Page 207: Curso node.js

Q(promiseOrValue)

Homogeneizar valores:

• Si es una promesa, se queda tal cual

• Si es un valor, se convierte en una promesa que se resuelve a ese valor

Q(42).then(function(v) { console.log(v); // 42})

Q(promise).then(function(v) { console.log(v); // resolución de promise})

Page 208: Curso node.js

Promesas: gotcha

¿Qué pasa aquí?

var d = Q.defer(), promise = d.promise;

promise.then(function(v) { console.log(v); throw new Error("Vaya por Dios!");}).then(function() { console.log("Hola?");});

d.resolve(42);

Page 209: Curso node.js

promise.done()

Finaliza el flujo, levantando los errores que no se hayan manejado

var d = Q.defer(), promise = d.promise;

promise.then(function(v) { console.log(v); throw new Error("Vaya por Dios!");}).then(function() { console.log("Hola?");}).done();

d.resolve(42);

Page 210: Curso node.js

promise.done()

La regla es:

• Si vas a devolver la promesa, déjala abierta

• Si eres el consumidor final de la promesa, asegúrate de cerrarla con .done()

Page 211: Curso node.js

¡A teclear!

Vamos el primer ejercicio complicadillo: un servidor de ficheros versionado

• La herramienta que hemos estado utilizando para compartir código

• Con promesas

• (Si alguien se atreve, que lo intente hacer sin promesas...)

Page 212: Curso node.js

Necesitas saber...

Manejar rutas: require(“path”)• path.resolve(base, ruta): ruta relativa a ruta

absoluta (partiendo de base)

• path.relative(base, ruta): ruta absoluta a ruta relativa (desde base)

Page 213: Curso node.js

Necesitas saber...

fs.readdir(ruta): Listar ficheros de un directorio

• Devuelve un array de strings con los nombres de los ficheros

• No es recursivo

• No hay manera de saber si una entrada es un fichero o un directorio

var fs = require("fs"), Q = require("q");

Q.ninvoke(fs, "readdir", ".").then(function(list) { console.log(list);})

Page 214: Curso node.js

Necesitas saber...

fs.stat(ruta): Info sobre un fichero/directorio

• stats.isDirectory(): true si es un directorio

• stats.mtime: Date de la última modificación

var fs = require("fs"), Q = require("q");

Q.ninvoke(fs, "stat", ".").then(function(stats) { console.log("es dir?", stats.isDirectory()); console.log("última modificación:", stats.mtime);})

Page 215: Curso node.js

Primer paso: listado recursivo

Escribe una función listAllFiles(ruta) que:

• Devuelva una promesa

• La promesa se resuelva con un listado recursivo de todos los ficheros que hay dentro del directorio

• Para cada fichero, genere un objeto del tipo{path: “/ruta/absoluta.txt”, stats: statsDelFichero}

listAllFiles(".").then(function(list) { console.log(list);}).done()

Page 216: Curso node.js

Primer paso: listado recursivo

Page 217: Curso node.js

Segundo paso: listener

Función somethingChanged(ruta):

• Devuelve true si algún fichero ha sido modificado desde la última vez que se invocó

• Impleméntalo utilizando stats.mtime como referencia

Utilizando esa función, escribe un “demonio” que monitorize un directorio y escriba un mensaje por consola cada vez que hay cambios

Page 218: Curso node.js

Tercer paso: volcado a memoria

Función readAllFiles(ruta):

• Completa el resultado de listAllFiles() añadiendo una tercera propiedad “contents” con los contenidos del fichero

Haz que el demonio lea todos los ficheros si detecta algún cambio y guarda el resultado en posiciones consecutivas de un array. Este va a ser nuestro control de versiones.

Page 219: Curso node.js

Cuarto paso: sirve los datos

El interfaz web tiene las siguientes rutas:

• /: listado de versiones

• /list?version=<n>: listado de ficheros de la versión n

• /ruta/al/fichero?version=<n>: busca el fichero con la ruta correspondiente en la versión n y lo sirve

• la versión “latest” siempre apunta a la versión más reciente

Page 220: Curso node.js

Virguerías opcionanes

Escribe un módulo simpleRoute de modo que podamos definir rutas así:

var routes = require("./simpleRoute");

routes.get("/", function(req, res) {})

routes.get("/list", function(req, res) {})

routes.default(function(req, res) {})

Page 221: Curso node.js

Virguerías opcionales

Además, simpleRoute modifica el parámetro req.url y lo sustituye por la url parseada

var routes = require("./simpleRoute");

routes.get("/list", function(req, res) { console.log(req.url.pathname); console.log(req.url.query.version);})

Page 222: Curso node.js

Express

Page 223: Curso node.js

¿Qué es express?

Un framework web para Node.js

• Estrictamente web (microframework)

• Sencillo y flexible

• Muy popular

• Se adapta muy bien a la filosofía de Node

• Similar a Sinatra, Sylex, Flask, Spark, ...

Page 224: Curso node.js

¿Qué es express?

Express nos va ayudar con...

• Rutas

• Parámetros

• Formularios y subida de ficheros

• Cookies

• Sesiones

• Templates

Page 225: Curso node.js

¿Qué es express?

Express NO nos va ayudar con...

• Base de datos / ORM

• Autenticación de usuarios

• Seguridad

• Migraciones

• Deployment

• Organización del código

Page 226: Curso node.js

¿Qué es express?

Más concretamente, express...

• Construye sobre http• Procesando la petición por un stack de middleware que

se encarga de decorar las peticiones

- Asocia rutas a manejadores

- Decorar los objetos req y res (parseo de parámetros, multipart, etc,...)

- Rendear templates

• Nosotros escogemos qué middlewares queremos usar, y en qué orden

Page 227: Curso node.js

¿Qué es express?

var express = require("express");

var app = express();

// configuración + rutas

app.listen(3000);

Page 228: Curso node.js

¿Qué es express?

Equivalente a:

var express = require("express"), http = require("http");

var app = express();

// configuración + rutas

http.createServer(app).listen(3000);

Page 229: Curso node.js

¿Qué es express?

Equivalente a:

var express = require("express"), http = require("http");

var app = express();

// configuración + rutas

http.createServer(app).listen(3000);

Page 230: Curso node.js

¿Qué es express?

var app = require("express")();

app.get("/", function(req, res) { res.end("Hola desde express!");});

app.listen(3000);

Page 231: Curso node.js

¿Qué es express?

var app = require("express")();

app.get("/", function(req, res) { res.end("Hola desde express!");});

app.listen(3000);

Verbo RutaManejador

Page 232: Curso node.js

¿Qué es express?

var app = require("express")();

app.get("/", function(req, res) { res.end("Hola desde express!");});

app.listen(3000);

Objeto Stream

Page 233: Curso node.js

¿Qué es express?

¿Qué nos aporta express, exactamente?

• Depende de los middlewares que usemos!

• Algunas cosas vienen por defecto

Page 234: Curso node.js

Request

req.params: parámetros de la ruta

app.get("/user/:id", function(req, res) { req.params.id;});

Page 235: Curso node.js

Request

req.query: la querystring, parseada

app.get("/search", function(req, res) { // GET /search?text=nodejs+express req.query.text;});

Page 236: Curso node.js

Request

• req.ip: IP del cliente conectado

• req.host: Hostname del servidor

• req.xhr: ¿Es ajax?

• req.acceptedLanguages: Array de locales

• req.host: Hostname del servidor

• Mas info en http://express.js.com/api.html

Page 237: Curso node.js

Response

res.cookie(nombre, valor, [opciones])• Modifica la cookie “nombre” con el valor “valor”

res.cookie("visitas", "1", {domain: ".ejemplo.com"});

Page 238: Curso node.js

Response

res.redirect([status], url)• Redirige a url

• El código de estado es opcional (302 por defecto)

res.redirect(301, "http://www.google.com");

Page 239: Curso node.js

Response

res.send([status], body)• Envía una respuesta (escribe en el buffer)

• Lo más adecuado para respuestas sencillas

- no streaming

• Automatiza ciertas cabeceras

- Content-Type, Content-Length

• Convierte objetos a JSON

res.send(500, {msg: "Oh, oh..."});

Page 240: Curso node.js

Response

Muchos otros métodos auxiliares:

• Cabeceras

• Envío de ficheros

• JSONP

• Content-Type

• ¡Lee la documentación!

http://expressjs.com/api.html

Page 241: Curso node.js

Application

app.configure([entorno], callback)• Configurar la aplicación

• Opcionalmente: configuración para un entorno

- “development”

- “testing”

- “production”

- etc

app.configure('development', function(){ app.set('db uri', 'localhost/dev');})

Page 242: Curso node.js

Application

¿Qué significa “configurar la aplicación”?

• Crear algunas propiedades globales

• Especificar el stack de middleware

Page 243: Curso node.js

Application

app.set(prop, value) / app.get(prop)

• Escribe/consulta valores globales en app

• Básicamente inútil...

• Excepto para cambiar alguna configuración más avanzada

app.set('title', 'Redradix');app.get('title');

Page 244: Curso node.js

Middleware

Middleware son módulos “plug and play” que se pueden apilar arbitrariamente en cualquier orden y proveen cierta funcionalidad

• Filtros: procesan tráfico entrate/saliente, pero no responden a ninguna request. (ejemplo: bodyParser)

• Proveedores: ofrecen respuestas automáticas a algún tipo de petición (ejemplo: static provider)

Page 245: Curso node.js

Middleware

app.configure(function() { app.use(express.favicon()); app.use(express.logger('dev')); app.use(express.bodyParser()); app.use(express.methodOverride());

app.use(express.cookieParser('your secret here')); app.use(express.session());

app.use(app.router); app.use(express.static(__dirname + '/public'));});

Page 246: Curso node.js

Middleware

app.configure(function() { app.use(express.favicon()); app.use(express.logger('dev')); app.use(express.bodyParser()); app.use(express.methodOverride());

app.use(express.cookieParser('your secret here')); app.use(express.session());

app.use(app.router); app.use(express.static(__dirname + '/public'));});

Page 247: Curso node.js

Middleware

app.configure(function() { app.use(express.favicon()); app.use(express.logger('dev')); app.use(express.bodyParser()); app.use(express.methodOverride());

app.use(express.cookieParser('your secret here')); app.use(express.session());

app.use(app.router); app.use(express.static(__dirname + '/public'));});

Page 248: Curso node.js

Middleware

Express trae unos cuantos preinstalados

• http://www.senchalabs.org/connect/

Una lista de módulos de terceros

• https://github.com/senchalabs/connect/wiki

Page 249: Curso node.js

express.favicon(ruta)

Sirve el favicon de la aplicación

• Debe ser el primero

• Para evitar capas inncesarias

• log

• parseo

• cookies

• etc...

Page 250: Curso node.js

express.logger([opciones])

Registro de actividad

• Muchas opciones...

http://www.senchalabs.org/connect/logger.html

• Se suele poner debajo de express.favicon()

Page 251: Curso node.js

express.cookieParser([secret])

Parsea las cookies de la petición

• Opcional: firmar cookies con secret

• Crea los objetos req.cookies y req.signedCookies

app.configure(function(){ app.use(express.cookieParser('secreto'));})

app.get("/", function(req, res) { console.log(req.cookies); console.log(req.signedCookies); res.send(200);})

Page 252: Curso node.js

express.bodyParser()

Parsea el cuerpo de las peticiones POST

• Decodifica

- application/json

- application/x-www-form-urlencoded

- multipart/form-data

• Crea el objeto req.body con los parámetros POST

• Crea el objeto req.files con los ficheros que se han subido desde un formulario

Page 253: Curso node.js

express.cookieSession([opciones])

Inicializa y parsea los datos de sesión del usuario

• Crea el objeto req.session

• Utilizando cookies como almacenamiento

• Opciones:

- secret: firma de segurdad para la cookie

- maxAge: duración, en ms (default: sin caducidad)

- path: ruta para la que es válida la cookie (default: /)

- httpOnly: protegida del cliente (default: true)

Page 254: Curso node.js

express.cookieSession([opciones])

var express = require("express"), app = express();

app.configure(function(){ app.use(express.cookieParser('secreto')); app.use(express.cookieSession());})

app.get("/", function(req, res) { req.session.visitas || (req.session.visitas = 0); var n = req.session.visitas++; res.send("Me has visitado: " + n + " veces!");})

app.listen(3000);

Page 255: Curso node.js

express.static(dir)

Sirve los ficheros estáticos dentro de dir• ¡Muy útil! Se pone cerca del final

• Cachea los ficheros

• La variable global __dirname contiene el directorio donde reside el script en ejecución

Page 256: Curso node.js

app.router

El enrutado de la aplicación

• Sirve para específicar exáctamente en qué momento quieres que se procesen las rutas de tu app

Page 257: Curso node.js

Middleware

app.configure(function() { app.use(express.favicon()); app.use(express.logger('dev')); app.use(express.bodyParser()); app.use(express.methodOverride());

app.use(express.cookieParser('your secret here')); app.use(express.cookieSession());

app.use(app.router); app.use(express.static(__dirname + '/public'));});

Page 258: Curso node.js

Templates

Express tiene un mecanismo para rendear templates

• Agnóstico

• Modular

• Simple

• NO trae ningún motor de templates por defecto

Page 259: Curso node.js

Templates

res.render(view, [locals], callback)• view: ruta del template

• locals: valores a interpolar

• callback: function(err, html) { ... }

Page 260: Curso node.js

Templates

Tenemos muchos motores de templates para elegir!

https://npmjs.org/browse/keyword/template

• Haml

• Hogan/Mustache

• Twig/Swig

• Ejs

• Jinja

• Jade

....

Page 261: Curso node.js

Ejs

<h1><%= title %></h1><ul> <% for(var i=0; i<supplies.length; i++) { %> <li> <a href='supplies/<%= supplies[i] %>'> <%= supplies[i] %> </a> </li> <% } %></ul>

Page 262: Curso node.js

Jade

Page 263: Curso node.js

Templates

Algunas propuestas originales/innovadoras:

CoffeeKup: http://coffeekup.org/

Weld: https://github.com/tmpvar/weld

Domo: http://domo-js.com/

Page 264: Curso node.js

Templates

Para utilizarlos desde express:

var express = require("express"), app = express();

app.configure(function() { app.engine("jade", require("jade").__express); app.set("views", "./views"); app.set("view engine", "jade");})

app.get("/", function(req, res) { res.render("welcome", {user: "Pepito"});})

app.listen(3000)

Page 265: Curso node.js

Templates

Para utilizarlos desde express:

var express = require("express"), app = express();

app.configure(function() { app.engine("jade", require("jade").__express); app.set("views", "./views"); app.set("view engine", "jade");})

app.get("/", function(req, res) { res.render("welcome", {user: "Pepito"});})

app.listen(3000)

Page 266: Curso node.js

Templates

Para utilizarlos desde express:

var express = require("express"), app = express();

app.configure(function() { app.engine("jade", require("jade").__express); app.set("views", "./views"); app.set("view engine", "jade");})

app.get("/", function(req, res) { res.render("welcome", {user: "Pepito"});})

app.listen(3000)

Page 267: Curso node.js

Templates

Para utilizarlos desde express:

var express = require("express"), app = express();

app.configure(function() { app.engine("jade", require("jade").__express); app.set("views", "./views"); app.set("view engine", "jade");})

app.get("/", function(req, res) { res.render("welcome", {user: "Pepito"});})

app.listen(3000)

Page 268: Curso node.js

Templates

Para utilizarlos desde express:

var express = require("express"), app = express();

app.configure(function() { app.engine("jade", require("jade").__express); app.set("views", "./views"); app.set("view engine", "jade");})

app.get("/", function(req, res) { res.render("welcome", {user: "Pepito"});})

app.listen(3000)

Page 269: Curso node.js

Templates

Donde /views/welcome.jade sería algo así:

doctype 5html(lang="es") body h1 Bienvenido, #{user}

Page 270: Curso node.js

Templates

Express es bastante listo (buenos defaults)

var express = require("express"), app = express();

app.get("/", function(req, res) { res.render("welcome.jade", {user: "Pepito"});})

app.listen(3000)

Page 271: Curso node.js

Blog en 15 minutos

Vamos a poner en práctica lo que hemos visto

• Vamos a hacer un “blog”, simplificado

• Sin BBDD

• Tiene 7 rutas (4 pantallas)

- GET /posts

- GET /posts/new

- POST /posts

- GET /posts/:id

- GET /posts/edit

- PUT /posts/:id

- DEL /posts/:id

Page 272: Curso node.js

Blog en 15 minutos

¿Sin base de datos?

• Guarda los datos en memoria, en un array

• Cuidado si usas nodemon, porque al guardar se resetea el servidor y se pierde el contenido del array

Page 273: Curso node.js

Un truco: app.param(...)

Mapear parámetros de url

app.param("postid", function(req, res, next, postId) { req.post = posts.find(postId); if (err || !req.post) { next(err || new Error("Post no encontrado ("+postId+")")); } else { next(); }});

app.get("/posts/:postid", function(req, res) { console.log(req.post);});

Page 274: Curso node.js

Más Middleware

Escribir middleware para express es muy sencillo:

• Una función que recibe tres parámetros:

- req

- res

- next

• Al terminar su tarea, tiene que invocar a next()- Sin parámetro: se invoca al siguiente middleware del stack

- Con parámetro: se cambia la ruta a lo que se pase como parámetro

Page 275: Curso node.js

Más Middleware

Por ejemplo, un logger simple:

app.configure(function(){ app.use(function(req, res, next) { console.log(" * %s: %s %s", req.connection.remoteAddress, req.method, req.url); next(); });})

Page 276: Curso node.js

Más Middleware

Haz un logger que muestre:

• Hora de la petición

• IP

• Método

• Ruta

• Tiempo de respuesta de la petición

Pero solo si el tiempo de respuesta está muy por encima de la media!

Page 277: Curso node.js

Más Middleware

Haz un módulo de “mensajes flash”

• Mensajes que solo están disponibles para la siguiente request

• Muy útiles para informar de ciertos sucesos

- “Post creado con éxito”

- “Email enviado”

- Errores

- etc...

Page 278: Curso node.js

Más Middleware

app.post("/posts", function(req, res) { var post = createPost(req.body); if (post) { req.flash.message("Post creado con éxito!"); } else { req.flash.error("No se ha podido crear..."); } res.redirect("/posts/" + post.id);})

app.get("/posts/:postid", function(req, res) { console.log(req.flash.message()); console.log(req.flash.error());})

Page 279: Curso node.js

Más Middleware

app.post("/posts", function(req, res) { var post = createPost(req.body); if (post) { req.flash.message("Post creado con éxito!"); } else { req.flash.error("No se ha podido crear..."); } res.redirect("/posts/" + post.id);})

app.get("/posts/:postid", function(req, res) { console.log(req.flash.message()); console.log(req.flash.error());})

Page 280: Curso node.js

Más Middleware

app.post("/posts", function(req, res) { var post = createPost(req.body); if (post) { req.flash.message("Post creado con éxito!"); } else { req.flash.error("No se ha podido crear..."); } res.redirect("/posts/" + post.id);})

app.get("/posts/:postid", function(req, res) { console.log(req.flash.message()); console.log(req.flash.error());})

Page 281: Curso node.js

Más Middleware: Errores

Un caso especial de middleware: una función que reciba un parámetro más

var express = require("express"), app = express();

app.get("/", function(req, res) { throw new Error("??")})

app.use(function(err, req, res, next) { console.log("* SOCORRO! ALGO VA MAL! ", err); res.send(500)})

app.listen(3000)

Page 282: Curso node.js

Más Middleware: Errores

Más correcto así:

var express = require("express"), app = express();

app.get("/", function(req, res, next) { next(new Error("esta app no hace nada"));})

app.use(function(err, req, res, next) { console.log("* SOCORRO! ALGO VA MAL! ", err); res.send(500)})

app.listen(3000)

Page 283: Curso node.js

Más Middleware: Errores

Escribe un manejador de errores para el blog

• Página 404 (página no encontrada o ruta no existe)

• Página 500 (mostrar en caso de error/excepción)

• Registra la fecha, la ruta y el mensaje de error en errors.log

Page 284: Curso node.js

Más Middleware

Dos maneras de activar middlewares

• Globalmente (app.use), activos para toda la app

• Locales para ciertas rutas

Page 285: Curso node.js

Más Middleware

var express = require("express"), app = express()

function logThis(req, res, next) { console.log(" -> ", req.url); next();}

app.get("/", logThis, function(req, res) { res.send("Ok!");})

app.listen(3000);

Page 286: Curso node.js

Más Middleware

Afinar la funcionalidad de cada ruta

• Pre-procesado de URL (parámetros, formato)

• Seguridad

• Caché

• Métricas

Page 287: Curso node.js

Más Middleware

Escribe un módulo para cachear la respuesta de una ruta durante X ms

Duración en ms

app.get("/", new StaticCache(5*1000), function(req, res) { res.send("Envío mi respuesta..." + new Date());})

Page 288: Curso node.js

Más Middleware

Una variación:

• Cachea a un fichero

• Sustituye res.write por el stream del fichero

• Cuando se dispare “finish”, sirve lo que has almacenado

• Puedes añadirlo al ejercicio del Blog para cachear las páginas de detalle de un post

Page 289: Curso node.js

SimpleAuth

Módulo para autenticar usuarios simpleAuth.js• Sencillo y flexible

• Independiente de la BBDD

• Utilizando las sesiones de express

• Middleware de ruta

Page 290: Curso node.js

SimpleAuth

El módulo provee 3 middlewares:

• createSession: comprueba los credenciales de usuario y crea una nueva sesión si son correctos

• requiresSession: protege una ruta y solo deja pasar a usuarios autenticados

• destroySession: desloguea a un usuario

Page 291: Curso node.js

SimpleAuth

var auth = require("./simpleAuth");

app.post("/login", auth.createSession({ redirect: "/secret" }))

app.get("/secret", auth.requiresSession, function(req, res) { res.end("Hola, " + req.user.email);})

app.get("/logout", auth.destroySession, function(req, res) { res.end("Te has deslogueado");});

Page 292: Curso node.js

SimpleAuth

El usuario ha de definir una estrategia:

serializeUser: ¿Cómo serializar un usuario?

deserializeUser: ¿Cómo des-serializar un usuario?

checkCredentials: ¿Son correctos los credenciales?

Page 293: Curso node.js

SimpleAuth

var auth = require("./simpleAuth")

var users = [{email: "[email protected]", pass: "asdf", id: 0}];

auth.setStrategy({ serializeUser: function(user) { return user.id; }, deserializeUser: function(userId, cb) { cb(users[userId]); }, checkCredentials: function(email, pass, cb) { var admin = users[0]; if (email === admin.email && pass === admin.pass) { cb(null, admin); } else { cb(null, false); } }});

Page 294: Curso node.js

SimpleAuth

auth.createSession(config)• config.username: nombre del campo del formulario

• config.password: nombre del campo del formulario

• config.redirect: URL en caso de éxito

• config.failRedirect: URL en caso de error

- default: /login

• Devuelve: una función para usar como middleware

Page 295: Curso node.js

SimpleAuth

auth.createSession(config)• Genera una función que...

1. Extrae username y password según config de req.body

2. Llama a strategy.checkCredentials con username y password

3. Si son credenciales correctos:3.1. Crea una entrada en la sessión o cookie con el resultado de llamar a strategy.serializeUser(usuario), donde usuario es lo que devuelve strategy.checkCredentials

3.2. Redirige a la URL de éxito

4. Si no son correctos:4.1. Redirige a la URL de error

Page 296: Curso node.js

SimpleAuth

auth.requiresSession1. Se asegura de que exista la entrada adecuada en la

sesión o cookie

2. Llama a strategy.deserializeUser con el valor

3. Guarda el usuario des-serializado en req.user

4. En caso de error, borra la sesión o cookie

Page 297: Curso node.js

SimpleAuth

auth.destroySession1. Borra la entrada adecuada en la sesión o cookie

Page 298: Curso node.js

SimpleAuth

auth.setStrategy(strategy)1. Configura la estretagia:

- strategy.serializeUser

- strategy.deserializeUser

- strategy.checkCredentials

- strategy.loginRoute

Page 299: Curso node.js

SimpleAuth

Protege la edición del blog con simpleAuth• Utilizando un array de usuarios escrito directamente en

el código

• Página de login + link logout + solo los usuarios logueados pueden crear o editar posts

• Si te sobra tiempo, haz que los usuarios se puedan registrar

Page 300: Curso node.js

Redis

Page 301: Curso node.js

¿Qué es Redis?

Una base de datos NoSQL...

• Clave/valor

• Extremadamente rápida

• Persistencia y transacciones

• Estructuras de datos

- Listas

- Conjuntos

- Hashes

• Pub/sub

Page 302: Curso node.js

¿Qué es Redis?

Un servidor de estructuras de datos

• Sin tablas, ni queries ni JOINs

• Una manera de pensar muy diferente

• Muchas queries muy rápidas

• Muy fácil de aprender, muy fácil de usar mal

Page 303: Curso node.js

¿Qué es Redis?

Redis es una opción estupenda para...

• Datos de acceso inmediato

- Sesiones

- Cachés

• Escrituras muy rápidas

- Logs

- Conteos y estadísticas

• Canal de comunicación pub/sub

- Comunicación entre procesos (workers, big data)

- Chats :)

Page 304: Curso node.js

¿Qué NO es Redis?

Redis es una opción terrible para...

• Relaciones de datos complejas

• Búsquedas

Page 305: Curso node.js

¿Para qué sirve?

Mi consejo:

• NO bases tu app en redis

• A no ser que sea muy simple o sepas muy bien lo que haces

• Utiliza Redis como complemento a tu BBDD

- Cachés

- Estadísticas

- Workers

- Sesiones

- Registros

- Colas

Page 306: Curso node.js

La idea general

Redis es un gran Hash de claves

• No hay concepto de tabla/colección

• El valor fundamental es la cadena (string)

• Pero una clave puede contener un valor más complejo

- Hashes (string -> string)

- Listas (de strings)

- Sets (de strings)

- Sets ordenados (de strings)

Page 307: Curso node.js

Requisitos

Necesitas:

• Tener instalado y arrancado redis

• Tener a mano la documentación➡ http://redis.io/commands

• npm install hiredis redis• Como alternativa:

➡ c9.io

- nada-nix install redis- npm install hiredis redis- redis-server --port 16379 --bind $IP

Page 308: Curso node.js

Primeros pasos

Primero, necesitas conectarte al servidor

var redis = require("redis");

var client = redis.createClient();// c9.io:// var client = redis.createClient(16379, process.env.IP);

Page 309: Curso node.js

Primeros pasos

Ahora puedes mandar comandos a Redis

• client.nombreDelComando(arg, callback)• client.nombreDelComando(arg1, arg2, callback)• callback: function(err, value) { }

Page 310: Curso node.js

SET / GET

Guardar y recuperar un valor

var redis = require("redis"), client = redis.createClient();

client.set("miClave", "miValor", function(err, val) { console.log(arguments); client.get("miClave", function(err, value) { console.log("valor: ", value); });})

Page 311: Curso node.js

SET / GET

Guardar y recuperar un valor

var redis = require("redis"), client = redis.createClient();

client.set("miClave", "miValor", function(err, val) { console.log(arguments); client.get("miClave", redis.print);})

Page 312: Curso node.js

SET / GET

Guardar y recuperar un valor

var redis = require("redis"), client = redis.createClient();

client.set("miClave", "miValor", function(err, val) { console.log(arguments); client.get("miClave", function(err, value) { console.log("valor: ", value); });})

Page 313: Curso node.js

SET / GET

Guardar y recuperar un valor

var redis = require("redis"), client = redis.createClient();

client.set("miClave", "miValor", function(err, val) { console.log(arguments); client.get("miClave", function(err, value) { console.log("valor: ", value); });})

Page 314: Curso node.js

SET / GET

Guardar y recuperar un valor

var redis = require("redis"), Q = require("q"), client = redis.createClient();

Q.ninvoke(client, "set", "miClave", "miValor").then(function() { return Q.ninvoke(client, "get", "miClave");}).then(function(value) { console.log("Valor: ", value);}).done();

Page 315: Curso node.js

SET / GET

Guardar y recuperar un valor

Page 316: Curso node.js

Un truco: redis-cli monitor

Para ver qué está pasando en Redis

Page 317: Curso node.js

Redis = Strings!!

Cuidado con los valores. Han de ser siempre strings.

var redis = require("redis"), Q = require("q"), client = redis.createClient();

Q.ninvoke(client, "set", "miClave", {un: "objeto"}).then(function() { return Q.ninvoke(client, "get", "miClave");}).then(function(value) { console.log("Valor: ", value); // Valor: [object Object]}).done();

Page 318: Curso node.js

Redis = Strings!!

var obj = {propiedad: "valor"};

Q.ninvoke(client, "set", "miClave", JSON.stringify(obj)).then(function() { return Q.ninvoke(client, "get", "miClave");}).then(function(value) { console.log("Valor: ", value); // Valor: {"propiedad": "valor"}}).done();

Page 319: Curso node.js

Redis = Strings!!

var serializable = { toString: function() { return JSON.stringify(this); }};

var obj = Object.create(serializable, { propiedad: { enumerable: true, value: "valor" }});

Q.ninvoke(client, "set", "miClave", obj).then(function() { return Q.ninvoke(client, "get", "miClave");}).then(function(value) { console.log("Valor: ", value); // Valor: {"propiedad": "valor"}}).done();

Page 320: Curso node.js

Redis = Strings!!

var serializable = { toString: function() { return JSON.stringify(this); }};

var obj = Object.create(serializable);obj.propiedad = "valor";

Q.ninvoke(client, "set", "miClave", obj).then(function() { return Q.ninvoke(client, "get", "miClave");}).then(function(value) { console.log("Valor: ", value); // Valor: {"propiedad": "valor"}}).done();

Page 321: Curso node.js

Redis = Strings!!

Una solución más radical:

Object.prototype.toString = function() { return JSON.stringify(this);};

var obj = {propiedad: "valor"};

Q.ninvoke(client, "set", "miClave", obj).then(function() { return Q.ninvoke(client, "get", "miClave");}).then(function(value) { console.log("Valor: ", value); // Valor: {"propiedad": "valor"}}).done();

Page 322: Curso node.js

DEL / EXISTS / TYPE / RENAME

Operaciones con claves

• DEL: borrar una clave

• EXSITS: comprobar si una clave existe

• TYPE: el tipo de valor almacenado en una clave

• RENAME: cambiar el nombre de la calve

Page 323: Curso node.js

DEL / EXISTS / TYPE / RENAME

var redis = require("redis"), Q = require("q"), client = redis.createClient();

Q.ninvoke(client, "exists", "MiClave").then(function(exists) { console.log( exists? "Existe!" : "No existe..." ); return Q.ninvoke(client, "set", "MiClave", "MiValor");}).then(function() { return Q.ninvoke(client, "rename", "MiClave", "MyKey");}).then(function() { return Q.ninvoke(client, "type", "MyKey");}).then(function(type) { console.log("MyKey es de tipo", type);}).done();

Page 324: Curso node.js

Operaciones con cadenas

• APPEND: añade el valor a la cadena

• DECR/INCR: Decrementa/incrementa el valor en 1

• DECRBY/INCRBY: Dec/inc el valor en N

• GETSET: Modifica el valor y devuelve el viejo

• STRLEN: Longitud del valor

Page 325: Curso node.js

Operaciones con cadenas

var op = Q.ninvoke.bind(Q, client);

op("set", "miClave", 1).then(function() { return op("incrby", "miClave", 10);}).then(function() { return op("decr", "miClave");}).then(function() { return op("getset", "miClave", "fin");}).then(function(valor) { console.log("VALOR: ", valor); return op("strlen", "miClave")}).then(function(len) { console.log("Len: ", len);}).done();

Page 326: Curso node.js

Operaciones múltiples

• MGET: Trae el valor de varias claves

• MSET: Modifica el valor de varias claves

var op = Q.ninvoke.bind(Q, client);

op("mset", "miClave", 1, "otraClave", 2).then(function() { return op("mget", "miClave", "otraClave");}).then(function(values) { console.log(values[0], ",", values[1]);}).done()

Page 327: Curso node.js

Listas

• LPUSH/RPUSH key value [value ...]• LPOP/RPOP key• LINDEX key index• LSET key index value• LLEN key• LRANGE key start stop: trae el rango de elementos

• LTRIM key start stop: limita al rango start-stop

• RPOPLPUSH source dest: RPOP sour + LPUSH dest

Page 328: Curso node.js

Listas

var op = Q.ninvoke.bind(Q, client), makeOp = function() { var args = arguments; return function() { return op.apply(null, args); } };

op("del", "miClave").then(makeOp("rpush", "miClave", 1, 2, 3, 4)).then(makeOp("lrange", "miClave", 0, 2)).then(function(values) { console.log(values); return op("ltrim", "miClave", 0, 1);}).then(makeOp("llen", "miClave")).then(function(len) { console.log(len);}).done()

Page 329: Curso node.js

A teclear un poco!

Modifica el ejercicio del blog del tema anterior...

• Para que utilice Redis como BD

• Guardar los posts como objetos JSON

• Guarda los usuarios en claves tipo:

- “user:[email protected]”: <JSON del usuario>

• Modifica la estrategia del autenticación

Page 330: Curso node.js

Hashes

• HSET key field value: Modifica el valor de campo field del hash en value

• HGET key field: Consulta el valor de campo field del hash en value

• HEXISTS key field: Existe el campo field?

• HKEYS/HVALS key: Todos los campos/valores

• HGETALL: Trae el hash entero

• HINCRBY key field n: Incrementa el campo en n

• HMGET/HMSET: Operaciones múltiples

Page 331: Curso node.js

Hashes

op("del", "miClave").then(function() { return op("hmset", "miClave", "a", 1, "b", 2, "c", 3);}).then(function() { return op("hincrby", "miClave", "c", 100);}).then(function() { return op("hgetall", "miClave");}).then(function(hash) { console.log(hash); // { a: '1', b: '2', c: '103' }}).done()

Page 332: Curso node.js

Conjuntos

• SADD key member [member ...]: añadir miembros

• SREM key member [member ...]: quitar miembros

• SCARD key: cardinal (número de elementos)

• SDIFF key [key ...]• SINTER key [key ...]• SUNION key [key ...]• SISMEMBER key member: ¿es miembro?

• SMEMBERS key: todos los miembros

Page 333: Curso node.js

Conjuntos

var op = Q.ninvoke.bind(Q, client), makeOp = function() { var args = arguments; return function() { return op.apply(null, args); } };

op("sadd", "miConjunto", 1, 1, 2, 3, 5, 8, 13).then(makeOp("sadd", "miConjunto2", 1, 3, 5, 7, 9, 11, 13)).then(makeOp("sinter", "miConjunto", "miConjunto2")).then(function(values) { console.log("Intersección:", values); return op("sdiff", "miConjunto", "miConjunto2");}).then(function(values) { console.log("Diferencia:", values);}).done()

Page 334: Curso node.js

Ejercicio: acotador de URLs

Escribe un acortador de URLs con Redis

• Registro y login de usuarios utilizando simpleAuth

• Redirección automática de urls

• Estadísticas de visita

- Cada IP cuenta una sola vez

- ¿Estadísticas por fecha? ¿Tendencias?

- ¿Geotracking de visitas (freegeoip.net)?

http.get("http://freegeoip.net/json/83.44.23.171", function(res) { res.on("data", function(data) { console.log(JSON.parse(data)); });});

Page 335: Curso node.js

MongoDB

Page 336: Curso node.js

¿Qué es MongoDB?

Una base de datos NoSQL...

• Sin esquema

• Alto rendimiento

• Almacena documentos BSON

• Enfocada en escalabilidad horizontal

• Lenguaje de consultas potente

• Sin transacciones

• Agregado de datos

Page 337: Curso node.js

¿Qué es MongoDB?

Una base de datos de documentos

• No impone forma a los datos

• No necesita migraciones/reestructuración de la BBDD

• Permite estructuras muy complejas

• Herramientas potentes de agregado con JavaScript

- Map-Reduce

- Aggregation Pipeline

Page 338: Curso node.js

¿Qué es MongoDB?

Con algunos extras interesantes...

• Índices geoespaciales

• Búsquedas FTS

• Almacenamiento eficiente de blobs y ficheros

• Sharding automático

• Replicación

Page 339: Curso node.js

¿Qué es MongoDB?

Es una buena alternativa para... ¡muchas cosas!

• Prototipos y aplicaciones simples

• Hacer la transición de front a back

• Aplicaciones con mucha carga de escritura

• Agregado de datos a un nivel medio/alto

• Aplicaciones con datos muy heterogéneos

• Enormes colecciones de datos (sharding)

• Almacenar ficheros (sharding)

Page 340: Curso node.js

¿Qué NO es MongoDB?

No te dejes seducir demasiado:

• Mongo no puede hacer JOINs!

• El lenguaje de consulta menos potente que SQL

• No tiene transacciones!

• La velocidad baja al subir la seguridad (escritura)

• Ten cuidado:

- Es muy fácil empezar con MongoDB

- Si tu app crece mucho... vas a necesitar JOINs

Page 341: Curso node.js

La idea general

Un almacen de documentos

• Básicamente, objetos JSON (BSON)

• Dividido en colecciones

• Consultas basadas en la estructura del documento

• ¡Se integra genial con JavaScript!

Page 342: Curso node.js

Requisitos

Necesitas

• Instalar y arrancar mongod

‣ mongod --dbpath <path> --nojournal• Tener la documentación a mano

➡ http://docs.mongodb.org/manual/

➡ http://mongodb.github.io/node-mongodb-native/

• npm install mongodb• En c9.io

•<comandos para instalar mongodb>

Page 343: Curso node.js

Primeros pasos

Para conectarte al servidor

var MongoClient = require("mongodb").MongoClient , ObjectID = require("mongodb").ObjectID;

var client = Q.ninvoke(MongoClient, "connect", "mongodb://127.0.0.1:27017/dbname");

client.fail(function(e) { console.log("ERROR conectando a Mongo: ", e)});

Page 344: Curso node.js

Primeros pasos

La BD está dividida en colecciones. Para abrir una colección:

var collection = client.then(function(db) { return db.collection("coleccion");});

Page 345: Curso node.js

Documento

Un documento es un objeto BSON

{ "_id" : ObjectId("524872a99c50880000000001"), "email" : "[email protected]", "password" : "asdf1234", "name" : "Test User", "date" : 1380479657300, "token" : "hm6ly43v.0o1or"}

Page 346: Curso node.js

Documento

Un documento es un objeto BSON

{ "_id" : ObjectId("524872a99c50880000000001"), "email" : "[email protected]", "password" : "asdf1234", "name" : "Test User", "date" : 1380479657300, "token" : "hm6ly43v.0o1or"}

Page 347: Curso node.js

Documento

Un documento puede contener arrays y otros documentos

{ "_id" : ObjectId("5249a2e9b90687d56453b2f3"), "text" : "Soy un comentario", "user" : { "_id" : ObjectId("524872a99c50880000000001"), "nombre" : "Test User", "avatar" : "/img/as09a8sd09.jpg" }, "tags" : [ "test", "prueba" ]}

Page 348: Curso node.js

Documento

Un documento puede contener arrays y otros documentos

{ "_id" : ObjectId("5249a2e9b90687d56453b2f3"), "text" : "Soy un comentario", "user" : { "_id" : ObjectId("524872a99c50880000000001"), "nombre" : "Test User", "avatar" : "/img/as09a8sd09.jpg" }, "tags" : [ "test", "prueba" ]}

Page 349: Curso node.js

Documento

Un documento puede contener arrays y otros documentos

{ "_id" : ObjectId("5249a2e9b90687d56453b2f3"), "text" : "Soy un comentario", "user" : { "_id" : ObjectId("524872a99c50880000000001"), "nombre" : "Test User", "avatar" : "/img/as09a8sd09.jpg" }, "tags" : [ "test", "prueba" ]}

Page 350: Curso node.js

Documentos

MongoDB no puede hacer JOINs

• Sin embargo, se pueden empotrar documentos y arrays

• Profundidad y complejidad arbitraria

• El límite: documento < 16MB

• Se suelen favorecer los diseños desnormalizados

✓ Mucho más cómodos

✓ Más eficientes en Mongo (con cuidado)

๏ Redundancia...

๏ Posible inconsistencia

๏ Actualizar los datos a mano cuando cambian

Page 351: Curso node.js

Colecciones

Una colección es una agrupación de documentos

• Puede alojar cualquier documento (no impone estructura)

• Puede alojar documentos con diferentes formas

• Operaciones de consulta

• Es donde se ponen los índices

Page 352: Curso node.js

Colecciones

Operaciones sobre una colección:

• collection.save: guardar/actualizar un documento

• collection.insert: inserta un documento

• collection.findOne: recuperar un documento

• collection.find: recuperar varios documentos

• collection.remove: borrar uno o varios documentos

• collection.drop: elimina la colección

• collection.rename: cambia de nombre la colección

• collection.count: número de documentos

Page 353: Curso node.js

Colecciones

MongoDB trae un cliente de línea de comandos

• mongo <host>/<dbname>• Ejecuta JavaScript

• Muy práctico para explorar

Page 354: Curso node.js

Colecciones

Page 355: Curso node.js

Colecciones

Page 356: Curso node.js

Colecciones

Desde Node.js

var MongoClient = require("mongodb").MongoClient , ObjectID = require("mongodb").ObjectID , Q = require("q");

Q.ninvoke(MongoClient, "connect", "mongodb://127.0.0.1:27017/dbname").then(function(db) { var micoleccion = db.collection("micoleccion"), op = Q.ninvoke.bind(Q, micoleccion);

op("insert", {uno: 1, dos: 2}) .then(function() { return op("insert", {tres: 3, cuatro: [4]}); }) .then(function() { return op("findOne", {uno: 1}); }) .then(function(doc) { console.log(doc); }) .done();});

Page 357: Curso node.js

Consulta

Dos operaciones fundamentales:

•findOne: devuelve un documento

•find: devuelve varios documentos en un cursor

Page 358: Curso node.js

Consulta

Ambos reciben como parámetro las conditiones que tiene que cumplir el documento:

var micoleccion = db.collection("micoleccion"), op = Q.ninvoke.bind(Q, micoleccion);

Q.ninvoke(micoleccion, "findOne", {uno: 1}) .then(function(doc) { console.log(doc); });

Q.ninvoke(micoleccion, "findOne", {dos: {$gt: 0}}) .then(function(doc) { console.log(doc); });

Page 359: Curso node.js

Consulta

Los operadores de búsqueda:

• $gt / $gte: mayor/mayor o igual

• $lt / $lte: menor/menor o igual

• $ne: diferente

• $in / $nin: en/no en array de valores

micol.findOne({ valor: {$in: [ 5, 15 ] }}, cb)

Page 360: Curso node.js

Consulta

Los operadores lógicos:

• $or: se cumple alguna cláusula

• $and: se cumplen todas las cláusulas

• $nor: el resultado opuesto

• $not: no se cumplen todas las cláusulas

micol.findOne({$or: [{valor: 5},{precio: {$gt: 15 }}

]}, callback)

Page 361: Curso node.js

Consulta

Más operadores interesantes:

• Operadores de evaluación

- Regex

- Código JavaScript arbitrario

• Operaciones geoespaciales➡ http://docs.mongodb.org/manual/reference/operator/nav-query/

Page 362: Curso node.js

Cursores

El operador find(...) devuelve un cursor

• Representa un conjunto de resultados

• cursor.count(callback): cantidad de documentos

• cursor.limit(n): limitar a n documentos

• cursor.skip(n): saltarse los n primeros documentos

• cursor.nextObject(callback): siguiente documento

• cursor.each(callback): para cada doc, en orden

• cursor.toArray(callback): convierte el cursor en array

Page 363: Curso node.js

Cursores

cursor.sort(opciones, [callback])• Ordenar los resultados

• Opciones del tipo:

‣ [[“campo”, 1], [“otroCampo”, -1]]

‣ 1 para ascendente, -1 para descendente

coleccion.find() .sort([['a', -1]]) .nextObject(function(err, item) { // ... })

Page 364: Curso node.js

Modificación

El operador más sencillo para modificar: save• Si el documento es nuevo (no tiene _id), lo inserta

• Si el documento ya existe, lo modifica

db.micol.save({ nombre: "Test User" })

Page 365: Curso node.js

Modificación

insert(<documento o array>)• Inserta uno o varios documentos en la colección

db.micol.insert([ { nombre: "Test User" }, { nombre: "Test User 2" }])

Page 366: Curso node.js

Escritura

remove(<patrón>)• Elimina los documentos que satisfagan la búsqueda

op("insert", {a: 1}) .then(function() { return op("remove", {a: 1}); }) .then(function(doc) { return op("count"); }) .then(function(n) { console.log(n); }) .done()

Page 367: Curso node.js

Ejercicio: Clón de Digg

Vamos a hacer un clon de Digg/HN/Reddit/...

• El usuario se puede registrar/loguear

• Puede añadir links (url+título+descripción)

• Puede votar +1/-1 los links (solo una vez)

• Puede comentar los links posteados

• Puede votar +1/-1 los comentarios

• Recientes/Populares

Page 368: Curso node.js

Ejercicio: Clón de Digg

La mecánica es ligeramente distinta al ej. anterior

• El cliente es una Single Page App en Backbone que sólo hace peticiones a una API JSON (autenticadas por token)

• No hay vistas, solo respondemos con datos JSON

• Algunas rutas tienen parámetros extra...

Page 369: Curso node.js

Ejercicio: Clón de Digg

Peculiaridades:

• /me: información sobre el usuario logueado

• /posts?s=<seccion>&page=<page>- seccion == “hottest”: ordenados por voto

- else: ordenados por fecha desc.

• /posts/:postsid/vote/up• /posts/:postsid/vote/down• /comments/:commentsid/vote/up• /comments/:commentsid/vote/down

Page 370: Curso node.js

Ejercicio: Clón de Digg

Para loguearse:

• El usuario manda user+pass a POST /session

• El servidor:

1. Genera un token para el usuario

2. Lo guarda en user.token

3. Devuelve el JSON del usuario al cliente

Page 371: Curso node.js

Ejercicio: Clón de Digg

Datos del usuario

{ "email" : "[email protected]", "name" : "Test User", "date" : 1380479657300, "_id" : ObjectId("524872a99c50880000000001"), "token" : "hm6ly43v.0o1or"}

Page 372: Curso node.js

Ejercicio: Clón de Digg

Datos del post

{ "_id" : ObjectId("524889c998ea730000000001"), "date" : 1380485577004, "description" : "asdf", "link" : "http://www.google.com", "ncomments" : 9, "title" : "titulo", "user" : { "name" : "Test User", "_id" : ObjectId("524872a99c50880000000001") }, "votes" : 0}

Page 373: Curso node.js

Ejercicio: Clón de Digg

Datos del comentario

{ "text" : "Comentario!", "post_id" : ObjectId("524889c998ea730000000001"), "user" : { "name" : "Test User", "_id" : ObjectId("524872a99c50880000000001") }, "votes" : 0, "date" : 1380485577013, "_id" : ObjectId("524889c998ea730000000002")}

Page 374: Curso node.js

Socket.io

Page 375: Curso node.js

¿Qué son websockets?

Protocolo de comunicación

• Full-duplex

• Una sola conexión permanente

• Stream de mensajes

• Contenido en tiempo real

Page 376: Curso node.js

¿Qué son websockets?

Es decir...

• El cliente puede enviar y recibir datos en tiempo real

• Orientado a “eventos” (mensajes)

• Siempre conectado

• Baja latencia

Page 377: Curso node.js

Websockets y Node.js

Funcionan especialemente bien con Node.js

• El servidor maneja muchas conexiones simultáneas

• Buena integración con JSON

• Eventos

Page 378: Curso node.js

¿Para qué sirven?

Fundamentalmente, para:

• Actividades colaborativas

• Juegos multijugador

• Acelerar ciertas operaciones

• Enviar datos

• Cargar recursos

• En resumen: tiempo real en vez de “a petición”

Page 379: Curso node.js

Socket.io

Vamos a usar Socket.io

• Una librería para manipular websockets

• Muy popular

• Fallback para navegadores obsoletos

• Muy fácil de usar

➡ http://socket.io/

Page 380: Curso node.js

Socket.io

Socket.io tiene dos partes:

• Servidor (Node.js):

• Cliente:

var express = require("express"), server = require("http").createServer(), io = require("socket.io").listen(server), app = express();

server.on("request", app).listen(3000);

<script src="socket.io/socket.io.js"></script>

Page 381: Curso node.js

Socket.io

Los sockets emiten eventos

• Un evento = un “mensaje”

• Se pueden pasar parámetros

• socket.on(mensaje, callback)• socket.emit(mensaje, [param1, param2, ...])

Page 382: Curso node.js

Socket.io

server.js

var express = require("express"), app = express(), server = require("http").createServer(app), io = require("socket.io").listen(server);

app.use(express.static(__dirname + "/public"));

io.sockets.on("connection", function(socket) { socket.emit("ping"); socket.on("pong", function() { console.log("PONG!"); });});

server.listen(3000);

Page 383: Curso node.js

Socket.io

server.js

var express = require("express"), app = express(), server = require("http").createServer(app), io = require("socket.io").listen(server);

app.use(express.static(__dirname + "/public"));

io.sockets.on("connection", function(socket) { socket.emit("ping"); socket.on("pong", function() { console.log("PONG!"); });});

server.listen(3000);

Page 384: Curso node.js

Socket.io

server.js

var express = require("express"), app = express(), server = require("http").createServer(app), io = require("socket.io").listen(server);

app.use(express.static(__dirname + "/public"));

io.sockets.on("connection", function(socket) { socket.emit("ping"); socket.on("pong", function() { console.log("PONG!"); });});

server.listen(3000);

Page 385: Curso node.js

Socket.io

index.html

<html> <head> <script src="/socket.io/socket.io.js"></script> <script type="text/javascript"> var socket = io.connect("http://localhost:3000");

socket.on("ping", function() { console.log("PING!"); socket.emit("pong"); }); </script> </head> <body></body></html>

Page 386: Curso node.js

Socket.io

index.html

<html> <head> <script src="/socket.io/socket.io.js"></script> <script type="text/javascript"> var socket = io.connect("http://localhost:3000");

socket.on("ping", function() { console.log("PING!"); socket.emit("pong"); }); </script> </head> <body></body></html>

Page 387: Curso node.js

Socket.io

index.html

<html> <head> <script src="/socket.io/socket.io.js"></script> <script type="text/javascript"> var socket = io.connect("http://localhost:3000");

socket.on("ping", function() { console.log("PING!"); socket.emit("pong"); }); </script> </head> <body></body></html>

Page 388: Curso node.js

Socket.io

Eventos reservados (servidor):

• io.sockets.on(“connection”, cb)• socket.on(“message”, cb)• socket.on(“disconnect”, cb)

Cliente:• socket.on(“connect”, cb)• socket.on(“disconnect”, cb)• socket.on(“error”, cb)• socket.on(“message”, cb)

Page 389: Curso node.js

Socket.io

Métodos (servidor)

• socket.broadcast.emit(msg)- les llega a todos menos el emisor

• socket.disconnect()• socket.emit(msg) / socket.on(msg)

Métodos (client)

• var socket = io.connect(host)• socket.disconnect()• socket.emit(msg) / socket.on(msg)

Page 390: Curso node.js

Un Chat! (simple)

Vamos a hacer un chat sencillo:

• Los usuarios se loguean eligiendo un nick

• Todo el mundo escribe en la misma sala común

• No tenemos indicador de presencia

Page 391: Curso node.js

Un Chat! (simple)

En el cliente:

• Chat.registerHandler(cb): callback cuando el usuario escribe

• Chat.postMsg(user, msg): Muestra un mensaje de otro

• Chat.showMyMsg(user, msg): Muestra un mensaje propio

Donde:

• user: {avatar: <string>, name: <string>}• msg: {text: <string>, time: <date>}

Page 392: Curso node.js

Canales

Con Socket.io podemos crear canales o namespaces para agrupar los receptores

var express = require("express"), app = express(), server = require("http").createServer(app), io = require("socket.io").listen(server);

app.use(express.static(__dirname + "/public"));

io.of("/canal").on("connection", function(socket) { socket.emit("ping");});

server.listen(3000);

Page 393: Curso node.js

Canales

En el cliente:

<script src="/socket.io/socket.io.js"></script> <script type="text/javascript"> var socket = io.connect("http://localhost:3000/canal");

socket.on("ping", function() { console.log("PING!"); });

</script>

Page 394: Curso node.js

Canales

Podemos tener varios canales simultáneos (multiplexando el mismo websocket)

io.of("/canal").on("connection", function(socket) { socket.emit("ping");});

io.of("/otro").on("connection", function(socket) { socket.emit("bang!");});

Page 395: Curso node.js

Canales

En el cliente:

var canal = io.connect("http://localhost:3000/canal"), otro = io.connect("http://localhost:3000/otro");

canal.on("ping", function() { console.log("PING!"); });

otro.on("bang!", function() { console.log("Estoy herido!"); });

Page 396: Curso node.js

Ahora, multisala

Utilizando namespaces, los usuarios pueden:

• Loguearse/registrarse (simpleauth)

• Crear salas

• Unirse y salirse de las salas creadas

• Escribir en la sala en la que estén

Page 397: Curso node.js

Ahora, multisala

Consejos:

• Guarda los sockets de cada usuario en un objeto

• Utiliza la sesión (o req.user) para saber en qué sala está un usuario (clave del objeto de sockets + canal)

• Crea mensajes para:

- Un usuario ha entrado en la sala

- Un usuario ha salido de la sala

- Alguien postea un mensaje