Curso node.js
Upcoming SlideShare
Loading in...5
×
 

Curso node.js

on

  • 3,189 views

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

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

Statistics

Views

Total Views
3,189
Views on SlideShare
3,127
Embed Views
62

Actions

Likes
14
Downloads
154
Comments
3

2 Embeds 62

https://twitter.com 51
http://www.linkedin.com 11

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
  • Asombrado por el nivel del curso!
    Are you sure you want to
    Your message goes here
    Processing…
  • Hola Javier. Muchas gracias por el comentario. Nos alegra que te guste el material. Sólo es el tema 1, iremos liberando más material poco a poco. Aquí te esperamos para cuando decidas apuntarte al curso :) Un abrazo!
    Are you sure you want to
    Your message goes here
    Processing…
  • Wow, vaya pasada de Material ^^U

    Me fastidió mucho no poder asistir finalmente al weekend y no podré apuntarme a esta primera convocatoria del curso de nodejs, pero viendo la calidad del material puedo imaginar las buenas dotes docentes del profesor y por tanto si nada lo impide me apuntaré a la siguiente convocatoria. Además quiero hacer primero el de Javascript para profesionales que también tiene una pinta increible.

    Buen trabajo Redradix!
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

Curso node.js Curso node.js Presentation Transcript

  • Pero, ¿Qué es 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
  • ¿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)?
  • ¿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”.
  • ¿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?
  • ¿Qué es Node.js? Por ejemplo, si... • ...para ruby tenemos Rails... • ...para python tenemos Django... • ...para php tenemos Symphony...
  • ¿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?
  • ¿Qué es Node.js? ¡¡NO!!
  • ¿Qué es Node.js? ¿Qué es un “lenguaje de programación”?
  • ¿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
  • ¿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)
  • ¿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,...)
  • ¿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)
  • ¿Qué es Node.js? v8 (JavaScript) • 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) Node.js
  • ¿Qué es Node.js? Node.js Ruby Python Java Lenguaje JavaScript Ruby Python Java Motor v8 YARV cPython JavaVM Entorno Node.js Ruby Standard Library Python Standard Library Java SE Framework ??? Rails Django Spring
  • ¿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
  • ¿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
  • 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
  • Una filosofía
  • 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
  • ¿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
  • Un modelo de ejecución Para entender Node.js, tenemos que entender dos ideas fundamentales: • Concurrencia vs. paralelismo (asincronía) • Eventos
  • Un modelo de ejecución Concurrencia vs. Paralelismo • ¿Qué significa que dos cosas suceden “a la vez”?
  • Un modelo de ejecución ¿Qué significa que dos cosas suceden “a la vez”?
  • Un modelo de ejecución ¿Qué significa que dos cosas suceden “a la vez”?
  • 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
  • Un modelo de ejecución Paralelismo
  • Un modelo de ejecución Concurrencia
  • 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
  • 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?
  • 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.
  • 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??
  • Un modelo de ejecución Patrón Reactor Bucle Principal
  • Un modelo de ejecución Patrón Reactor Bucle Principal Mundo Exterior
  • Un modelo de ejecución Patrón Reactor Suceso Bucle Principal Mundo Exterior
  • Un modelo de ejecución Patrón Reactor Bucle Principal Suceso Mundo Exterior
  • Un modelo de ejecución Patrón Reactor Suceso 2 Bucle Principal Suceso Mundo Exterior
  • Un modelo de ejecución Patrón Reactor Bucle Principal Suceso Suceso 2 Mundo Exterior
  • Un modelo de ejecución Patrón Reactor Tick! Bucle Principal Suceso Suceso 2 Mundo Exterior
  • Un modelo de ejecución Patrón Reactor Tick! Bucle Principal Suceso Suceso 2 Mundo Exterior Evento: “Suceso”
  • Un modelo de ejecución Patrón Reactor Tick! Evento: “Suceso” Bucle Principal Suceso Suceso 2 Mundo Exterior Manejadores
  • Un modelo de ejecución Patrón Reactor Tick! Bucle Principal Suceso Suceso 2 Mundo Exterior Manejadores
  • Un modelo de ejecución Patrón Reactor Suceso 3 Tick! Bucle Principal Suceso Suceso 2 Mundo Exterior Manejadores
  • Un modelo de ejecución Patrón Reactor Tick! Bucle Principal Suceso Suceso 2 Mundo Exterior Suceso 3 Manejadores
  • Un modelo de ejecución Patrón Reactor Tick! Listo! Bucle Principal Suceso Suceso 2 Mundo Exterior Suceso 3 Manejadores
  • Un modelo de ejecución Patrón Reactor Tick! Bucle Principal Suceso Suceso 2 Mundo Exterior Suceso 3 Manejadores
  • Un modelo de ejecución Patrón Reactor Tick! Evento: “Suceso 2” ??? Bucle Principal Suceso Suceso 2 Mundo Exterior Suceso 3 Manejadores
  • Un modelo de ejecución Patrón Reactor Bucle Principal Suceso Suceso 2 Mundo Exterior Suceso 3 Manejadores
  • Un modelo de ejecución 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
  • ¿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
  • ¿Qué es Node.js? Muy enfocado hacia aplicaciones de red • ¿Por qué?
  • ¿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
  • Toma de contacto
  • 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
  • Ahora en serio: ¿Qué es Node.js? console.log("Hola, Mundo!");
  • Ahora en serio: ¿Qué es Node.js? $ node hola.js
  • Ahora en serio: ¿Qué es 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?
  • 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!
  • Ahora en serio: ¿Qué es Node.js? setTimeout(function() { console.log("Hola, Mundo del futuro!"); }, 1000);
  • Ahora en serio: ¿Qué es Node.js? setInterval(function() { console.log("Hola otra vez, Mundo del futuro!"); }, 1000);
  • Ahora en serio: ¿Qué es Node.js? setTimeout(function() { while (true); }, 100); setInterval(function() { console.log("Hola otra vez, Mundo del futuro!"); }, 1000);
  • 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);
  • 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);
  • Ahora en serio: ¿Qué es Node.js? Nos surgen problemas curiosos... • ¿Excepciones?
  • Ahora en serio: ¿Qué es Node.js? Nos surgen problemas curiosos... • ¿Excepciones? try { throw new Error("Peté!"); } catch(e) { console.log("Excepción!"); }
  • 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!"); }
  • 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
  • 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...");
  • 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...");
  • require/exports require(<paquete o ruta>) • Importar módulos (paquetes, otros ficheros) • Garantía: una única vez • Devuelve el módulo!
  • require/exports exports.propiedadPublica = <valor> • El otro lado del mecanismo • Se puede exportar cualquier valor
  • require/exports codigo.js var lib = require("./libreria"); console.log(lib.propiedad); libreria.js console.log("una vez"); exports.propiedad = "Pública";
  • 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"); });
  • 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);
  • 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
  • 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 • ...
  • 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
  • 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 - ...
  • JavaScript y el Universo Buffers var buf = new Buffer(100); buf.write("abcd", 0, 4, "ascii"); console.log(buf.toString("ascii"));
  • JavaScript y el Universo Buffers Tamaño del buffer var buf = new Buffer(100); buf.write("abcd", 0, 4, "ascii"); console.log(buf.toString("ascii"));
  • JavaScript y el Universo Posición Buffers Datos Longitud Codificación var buf = new Buffer(100); buf.write("abcd", 0, 4, "ascii"); console.log(buf.toString("ascii"));
  • JavaScript y el Universo Buffers var buf = new Buffer(100); buf.write("abcd", 0, 4, "ascii"); console.log(buf.toString("ascii")); Codificación
  • JavaScript y el Universo Buffers
  • JavaScript y el Universo function Bitmap(w, h) { this.width = w; this.height = h; this.header = "P6n" + w + " " + h + "n255n"; 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); } };
  • JavaScript y el Universo var bitmap = new Bitmap(1, 1); bitmap.putPixel(0, 0, "000000"); bitmap.render();
  • JavaScript y el Universo var bitmap = new Bitmap(10, 10); bitmap.fill("ffffff"); bitmap.putPixel(0, 0, "000000"); bitmap.render();
  • 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
  • JavaScript y el Universo Streams • Es raro crear streams directamente • Pero muchos recursos nos ofrecen este interfaz
  • 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
  • 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á!"); });
  • Una fácil Haz un programa que cuente las líneas de un fichero
  • 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
  • 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
  • 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!"); });
  • ¿Preguntas? Un buen momento para despejar dudas antes de seguir...
  • HTTP (por fin...)
  • 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
  • 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 HTTP var http = require("http"); var server = http.createServer(); server.on("request", function(req, res) { res.end("Hola, Mundo!"); }); server.listen(3000);
  • HTTP var http = require("http"); Eventos! Respuesta (stream) var server = http.createServer(); server.on("request", function(req, res) { res.end("Hola, Mundo!"); }); server.listen(3000); Request (objeto)
  • HTTP El servidor HTTP • Eventos: - connection request • Operaciones: - createServer([requestCallback]) listen(puerto, [hostname], [backlog], [callback]) close([callback])
  • 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
  • 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
  • Manos a la obra Escribe un servidor web que devuelva la hora
  • Un consejo: nodemon Para mejorar el flow de trabajo: • Editar, matar el proceso, volver a lanzarlo, probar... muy tedioso! • Instálate nodemon npm install -g nodemon • Reinicia el servidor cada vez que cambia el fichero $ nodemon <fichero.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);
  • 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);
  • HTTP
  • 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
  • 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); });
  • 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); });
  • 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?
  • 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)
  • 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
  • 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)
  • Servidor A/B Testing Tenéis maqueta y recursos en : /tema1/abtesting
  • Promesas
  • 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”
  • 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...
  • 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 } });
  • 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 } });
  • 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 } });
  • 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 Doom Callback Hell
  • 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 } });
  • CPS vs. Promesas
  • 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
  • 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 });
  • 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 });
  • 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 });
  • 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 });
  • 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 });
  • 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 });
  • 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 });
  • 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 });
  • 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 });
  • Promesas Una promesa = Un flujo de ejecución
  • Promesas ¡Pero aún no hemos ejecutado nada! Solamente hemos construído el flujo
  • 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
  • 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!"); })
  • 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!"); })
  • 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!"); })
  • 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!"); })
  • 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!"); })
  • 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!"); })
  • 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!"); })
  • 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!"); })
  • 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
  • Promesas var promesa = readFilePromise("./hola.txt"); promesa = promesa.then(function(data) { console.log("Contenido del fichero: ", data); }, function(err) { console.log("Ooops!", err); })
  • Promesas var promesa = readFilePromise("./hola.txt"); promesa = promesa.then(function(data) { console.log("Contenido del fichero: ", data); }, function(err) { console.log("Ooops!", err); })
  • Promesas var promesa = readFilePromise("./hola.txt"); promesa = promesa.then(function(data) { console.log("Contenido del fichero: ", data); }, function(err) { console.log("Ooops!", err); })
  • Promesas var promesa = readFilePromise("./hola.txt"); promesa = promesa.then(function(data) { console.log("Contenido del fichero: ", data); }, function(err) { console.log("Ooops!", err); })
  • Promesas var promesa = readFilePromise("./hola.txt"); promesa = promesa.then(function(data) { console.log("Contenido del fichero: ", data); }, function(err) { console.log("Ooops!", err); })
  • Promesas var promesa = readFilePromise("./hola.txt"); promesa = promesa.then(function(data) { console.log("Contenido del fichero: ", data); }, function(err) { console.log("Ooops!", err); })
  • Promesas var promesa = readFilePromise("./hola.txt"); promesa = promesa.then(function(data) { console.log("Contenido del fichero: ", data); }, function(err) { console.log("Ooops!", err); })
  • 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
  • Promesas var promesa = readFilePromise("./hola.txt"); var promesa2 = promesa.then(function(data) { return 1; }); var promesa3 = promesa.then(function(data) { return 2; }); data 1 data 2
  • Promesas 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!"); }); data 1 data 2
  • Promesas 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!"); }); data 1 data 2 2
  • Promesas 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..."); }); data 1 data 2 2
  • 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)
  • 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); });
  • 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); });
  • 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); }); ¿Qué se muestra por consola al ejecutar esto?
  • 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); }); ¿Por qué?
  • 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?
  • 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
  • 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
  • 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);
  • 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!"));
  • 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"; })
  • 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);
  • 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);
  • 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);
  • 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);
  • 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");
  • 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);
  • 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);
  • 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);
  • 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);
  • 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);
  • 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
  • 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);
  • 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);
  • 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
  • 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
  • Promesas function readFilePromise(filePath) { // ??? } readFilePromise("./hola.txt").then(function(contenido) { console.log("contenido:", contenido.toString()); }, function(err) { console.log("ERROR!", err); });
  • 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); });
  • Promesas var fs = require("fs"), Q = require("q"); Gestor del recurso function readFilePromise(filePath) { var defer = Q.defer(); fs.readFile(filePath, function(err, data) { err ? defer.reject(err) : defer.resolve(data); }) return defer.promise; } Consumidor del recurso readFilePromise("./hola.txt").then(function(contenido) { console.log("contenido:", contenido.toString()); }, function(err) { console.log("ERROR!", err); });
  • Promesas: paréntesis Intenta modificar el servidor de ficheros estáticos para que utilice promesas: • fileExistsPromise • readFilePromise
  • 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
  • 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
  • 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);
  • Q.all([promise, promise, ...])
  • 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 })
  • 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);
  • 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);
  • 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); });
  • 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 })
  • 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);
  • 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);
  • 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()
  • ¡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...)
  • Necesitas saber... Manejar rutas: require(“path”) • path.resolve(base, ruta): ruta relativa a ruta absoluta (partiendo de base) • path.relative(base, relativa (desde base) ruta): ruta absoluta a ruta
  • 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); })
  • 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); })
  • 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()
  • Primer paso: listado recursivo
  • 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
  • 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.
  • 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
  • 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) { })
  • 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); })
  • Express
  • ¿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, ...
  • ¿Qué es express? Express nos va ayudar con... • Rutas • Parámetros • Formularios y subida de ficheros • Cookies • Sesiones • Templates
  • ¿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
  • ¿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
  • ¿Qué es express? var express = require("express"); var app = express(); // configuración + rutas app.listen(3000);
  • ¿Qué es express? Equivalente a: var express = require("express"), http = require("http"); var app = express(); // configuración + rutas http.createServer(app).listen(3000);
  • ¿Qué es express? Equivalente a: var express = require("express"), http = require("http"); var app = express(); // configuración + rutas http.createServer(app).listen(3000);
  • ¿Qué es express? var app = require("express")(); app.get("/", function(req, res) { res.end("Hola desde express!"); }); app.listen(3000);
  • ¿Qué es express? Verbo Ruta Manejador var app = require("express")(); app.get("/", function(req, res) { res.end("Hola desde express!"); }); app.listen(3000);
  • ¿Qué es express? Objeto Stream var app = require("express")(); app.get("/", function(req, res) { res.end("Hola desde express!"); }); app.listen(3000);
  • ¿Qué es express? ¿Qué nos aporta express, exactamente? • Depende de los middlewares que usemos! • Algunas cosas vienen por defecto
  • Request req.params: parámetros de la ruta app.get("/user/:id", function(req, res) { req.params.id; });
  • Request req.query: la querystring, parseada app.get("/search", function(req, res) { // GET /search?text=nodejs+express req.query.text; });
  • 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
  • Response res.cookie(nombre, valor, [opciones]) • Modifica la cookie “nombre” con el valor “valor” res.cookie("visitas", "1", {domain: ".ejemplo.com"});
  • 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");
  • 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..."});
  • Response Muchos otros métodos auxiliares: • Cabeceras • Envío de ficheros • JSONP • Content-Type • ¡Lee la documentación! http://expressjs.com/api.html
  • 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'); })
  • Application ¿Qué significa “configurar la aplicación”? • Crear algunas propiedades globales • Especificar el stack de middleware
  • 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');
  • 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)
  • 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'));
  • 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'));
  • 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'));
  • Middleware Express trae unos cuantos preinstalados • http://www.senchalabs.org/connect/ Una lista de módulos de terceros • https://github.com/senchalabs/connect/wiki
  • express.favicon(ruta) Sirve el favicon de la aplicación • Debe ser el primero • Para evitar capas inncesarias • log • parseo • cookies • etc...
  • express.logger([opciones]) Registro de actividad • Muchas opciones... http://www.senchalabs.org/connect/logger.html • Se suele poner debajo de express.favicon()
  • 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); })
  • 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
  • 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)
  • 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);
  • 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
  • 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
  • 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'));
  • Templates Express tiene un mecanismo para rendear templates • Agnóstico • Modular • Simple • NO trae ningún motor de templates por defecto
  • Templates res.render(view, [locals], callback) • view: ruta del template • locals: valores a interpolar • callback: function(err, html) { ... }
  • Templates Tenemos muchos motores de templates para elegir! https://npmjs.org/browse/keyword/template • Haml • Hogan/Mustache • Twig/Swig • Ejs • Jinja • Jade ....
  • Ejs <h1><%= title %></h1> <ul> <% for(var i=0; i<supplies.length; i++) { %> <li> <a href='supplies/<%= supplies[i] %>'> <%= supplies[i] %> </a> </li> <% } %> </ul>
  • Jade
  • Templates Algunas propuestas originales/innovadoras: CoffeeKup: http://coffeekup.org/ Weld: https://github.com/tmpvar/weld Domo: http://domo-js.com/
  • 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)
  • 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)
  • 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)
  • 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)
  • 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)
  • Templates Donde /views/welcome.jade sería algo así: doctype 5 html(lang="es") body h1 Bienvenido, #{user}
  • 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)
  • 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
  • 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
  • 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); });
  • 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
  • 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(); }); })
  • 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!
  • 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...
  • 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()); })
  • 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()); })
  • 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()); })
  • 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)
  • 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)
  • 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
  • Más Middleware Dos maneras de activar middlewares • Globalmente (app.use), activos para toda la app • Locales para ciertas rutas
  • 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);
  • Más Middleware Afinar la funcionalidad de cada ruta • Pre-procesado de URL (parámetros, formato) • Seguridad • Caché • Métricas
  • 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()); })
  • 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
  • SimpleAuth Módulo para autenticar usuarios simpleAuth.js • Sencillo y flexible • Independiente de la BBDD • Utilizando las sesiones de express • Middleware de ruta
  • 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
  • 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"); });
  • 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?
  • SimpleAuth var auth = require("./simpleAuth") var users = [{email: "admin@asdf.com", 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); } } });
  • 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
  • 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
  • SimpleAuth auth.requiresSession 1. 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
  • SimpleAuth auth.destroySession 1. Borra la entrada adecuada en la sesión o cookie
  • SimpleAuth auth.setStrategy(strategy) 1. Configura la estretagia: - strategy.serializeUser - strategy.deserializeUser - strategy.checkCredentials - strategy.loginRoute
  • 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
  • Redis
  • ¿Qué es Redis? Una base de datos NoSQL... • Clave/valor • Extremadamente rápida • Persistencia y transacciones • Estructuras de datos - Listas - Conjuntos - Hashes • Pub/sub
  • ¿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
  • ¿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 :)
  • ¿Qué NO es Redis? Redis es una opción terrible para... • Relaciones de datos complejas • Búsquedas
  • ¿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
  • 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)
  • 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
  • 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);
  • Primeros pasos Ahora puedes mandar comandos a Redis • • client.nombreDelComando(arg, callback) • callback: function(err, value) { } client.nombreDelComando(arg1, arg2, callback)
  • 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); }); })
  • 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); })
  • 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); }); })
  • 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); }); })
  • 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();
  • SET / GET Guardar y recuperar un valor
  • Un truco: redis-cli monitor Para ver qué está pasando en Redis
  • 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();
  • 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();
  • 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();
  • 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();
  • 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();
  • 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
  • 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();
  • 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
  • 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();
  • 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()
  • 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
  • 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()
  • 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:admin@asdf.com”: <JSON del usuario> • Modifica la estrategia del autenticación
  • 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
  • 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()
  • 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 • SMEMBERS key member: ¿es miembro? key: todos los miembros
  • 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()
  • 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)); }); });
  • MongoDB
  • ¿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
  • ¿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
  • ¿Qué es MongoDB? Con algunos extras interesantes... • Índices geoespaciales • Búsquedas FTS • Almacenamiento eficiente de blobs y ficheros • Sharding automático • Replicación
  • ¿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)
  • ¿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
  • 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!
  • 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 • En c9.io mongodb •<comandos para instalar mongodb>
  • 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) });
  • Primeros pasos La BD está dividida en colecciones. Para abrir una colección: var collection = client.then(function(db) { return db.collection("coleccion"); });
  • Documento Un documento es un objeto BSON { "_id" : ObjectId("524872a99c50880000000001"), "email" : "test@asdf.com", "password" : "asdf1234", "name" : "Test User", "date" : 1380479657300, "token" : "hm6ly43v.0o1or" }
  • Documento Un documento es un objeto BSON { "_id" : ObjectId("524872a99c50880000000001"), "email" : "test@asdf.com", "password" : "asdf1234", "name" : "Test User", "date" : 1380479657300, "token" : "hm6ly43v.0o1or" }
  • 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" ] }
  • 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" ] }
  • 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" ] }
  • 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
  • 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
  • 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
  • Colecciones MongoDB trae un cliente de línea de comandos • mongo <host>/<dbname> • Ejecuta JavaScript • Muy práctico para explorar
  • Colecciones
  • Colecciones
  • 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(); });
  • Consulta Dos operaciones fundamentales: •findOne: devuelve un documento •find: devuelve varios documentos en un cursor
  • 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); });
  • 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)
  • 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)
  • 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/
  • 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
  • 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) { // ... })
  • 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" })
  • 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" } ])
  • 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()
  • 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
  • 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...
  • 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
  • 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
  • Ejercicio: Clón de Digg Datos del usuario { } "email" : "asdf@asdf.com", "name" : "Test User", "date" : 1380479657300, "_id" : ObjectId("524872a99c50880000000001"), "token" : "hm6ly43v.0o1or"
  • 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
  • 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")
  • Socket.io
  • ¿Qué son websockets? Protocolo de comunicación • Full-duplex • Una sola conexión permanente • Stream de mensajes • Contenido en tiempo real
  • ¿Qué son websockets? Es decir... • El cliente puede enviar y recibir datos en tiempo real • Orientado a “eventos” (mensajes) • Siempre conectado • Baja latencia
  • Websockets y Node.js Funcionan especialemente bien con Node.js • El servidor maneja muchas conexiones simultáneas • Buena integración con JSON • Eventos
  • ¿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”
  • 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/
  • Socket.io Socket.io tiene dos partes: • Servidor (Node.js): var express = require("express"), server = require("http").createServer(), io = require("socket.io").listen(server), app = express(); server.on("request", app).listen(3000); • Cliente: <script src="socket.io/socket.io.js"></script>
  • Socket.io Los sockets emiten eventos • Un evento = un “mensaje” • Se pueden pasar parámetros • socket.on(mensaje, callback) • socket.emit(mensaje, [param1, param2, ...])
  • 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);
  • 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);
  • 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);
  • 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>
  • 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>
  • 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>
  • 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)
  • 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)
  • 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
  • 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>}
  • 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);
  • 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>
  • 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!"); });
  • 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!"); });
  • 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
  • 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