Curso node.js

5,810 views

Published on

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

Published in: Technology
3 Comments
24 Likes
Statistics
Notes
  • Asombrado por el nivel del curso!
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • 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!
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • 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!
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
No Downloads
Views
Total views
5,810
On SlideShare
0
From Embeds
0
Number of Embeds
83
Actions
Shares
0
Downloads
363
Comments
3
Likes
24
Embeds 0
No embeds

No notes for slide

Curso node.js

  1. 1. Pero, ¿Qué es Node.js?
  2. 2. ¿Qué es Node.js? Lo que todos sabemos • Hay Javascript por alguna parte • Backend • ¿Algo que ver con NoSQL? • Sirve para hacer chats
  3. 3. ¿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)?
  4. 4. ¿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”.
  5. 5. ¿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?
  6. 6. ¿Qué es Node.js? Por ejemplo, si... • ...para ruby tenemos Rails... • ...para python tenemos Django... • ...para php tenemos Symphony...
  7. 7. ¿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?
  8. 8. ¿Qué es Node.js? ¡¡NO!!
  9. 9. ¿Qué es Node.js? ¿Qué es un “lenguaje de programación”?
  10. 10. ¿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
  11. 11. ¿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)
  12. 12. ¿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,...)
  13. 13. ¿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)
  14. 14. ¿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
  15. 15. ¿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
  16. 16. ¿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
  17. 17. ¿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
  18. 18. 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
  19. 19. Una filosofía
  20. 20. 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
  21. 21. ¿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
  22. 22. Un modelo de ejecución Para entender Node.js, tenemos que entender dos ideas fundamentales: • Concurrencia vs. paralelismo (asincronía) • Eventos
  23. 23. Un modelo de ejecución Concurrencia vs. Paralelismo • ¿Qué significa que dos cosas suceden “a la vez”?
  24. 24. Un modelo de ejecución ¿Qué significa que dos cosas suceden “a la vez”?
  25. 25. Un modelo de ejecución ¿Qué significa que dos cosas suceden “a la vez”?
  26. 26. 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
  27. 27. Un modelo de ejecución Paralelismo
  28. 28. Un modelo de ejecución Concurrencia
  29. 29. 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
  30. 30. 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?
  31. 31. 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.
  32. 32. 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??
  33. 33. Un modelo de ejecución Patrón Reactor Bucle Principal
  34. 34. Un modelo de ejecución Patrón Reactor Bucle Principal Mundo Exterior
  35. 35. Un modelo de ejecución Patrón Reactor Suceso Bucle Principal Mundo Exterior
  36. 36. Un modelo de ejecución Patrón Reactor Bucle Principal Suceso Mundo Exterior
  37. 37. Un modelo de ejecución Patrón Reactor Suceso 2 Bucle Principal Suceso Mundo Exterior
  38. 38. Un modelo de ejecución Patrón Reactor Bucle Principal Suceso Suceso 2 Mundo Exterior
  39. 39. Un modelo de ejecución Patrón Reactor Tick! Bucle Principal Suceso Suceso 2 Mundo Exterior
  40. 40. Un modelo de ejecución Patrón Reactor Tick! Bucle Principal Suceso Suceso 2 Mundo Exterior Evento: “Suceso”
  41. 41. Un modelo de ejecución Patrón Reactor Tick! Evento: “Suceso” Bucle Principal Suceso Suceso 2 Mundo Exterior Manejadores
  42. 42. Un modelo de ejecución Patrón Reactor Tick! Bucle Principal Suceso Suceso 2 Mundo Exterior Manejadores
  43. 43. Un modelo de ejecución Patrón Reactor Suceso 3 Tick! Bucle Principal Suceso Suceso 2 Mundo Exterior Manejadores
  44. 44. Un modelo de ejecución Patrón Reactor Tick! Bucle Principal Suceso Suceso 2 Mundo Exterior Suceso 3 Manejadores
  45. 45. Un modelo de ejecución Patrón Reactor Tick! Listo! Bucle Principal Suceso Suceso 2 Mundo Exterior Suceso 3 Manejadores
  46. 46. Un modelo de ejecución Patrón Reactor Tick! Bucle Principal Suceso Suceso 2 Mundo Exterior Suceso 3 Manejadores
  47. 47. Un modelo de ejecución Patrón Reactor Tick! Evento: “Suceso 2” ??? Bucle Principal Suceso Suceso 2 Mundo Exterior Suceso 3 Manejadores
  48. 48. Un modelo de ejecución Patrón Reactor Bucle Principal Suceso Suceso 2 Mundo Exterior Suceso 3 Manejadores
  49. 49. 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
  50. 50. ¿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
  51. 51. ¿Qué es Node.js? Muy enfocado hacia aplicaciones de red • ¿Por qué?
  52. 52. ¿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
  53. 53. Toma de contacto
  54. 54. 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
  55. 55. Ahora en serio: ¿Qué es Node.js? console.log("Hola, Mundo!");
  56. 56. Ahora en serio: ¿Qué es Node.js? $ node hola.js
  57. 57. Ahora en serio: ¿Qué es Node.js?
  58. 58. 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?
  59. 59. 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!
  60. 60. Ahora en serio: ¿Qué es Node.js? setTimeout(function() { console.log("Hola, Mundo del futuro!"); }, 1000);
  61. 61. Ahora en serio: ¿Qué es Node.js? setInterval(function() { console.log("Hola otra vez, Mundo del futuro!"); }, 1000);
  62. 62. Ahora en serio: ¿Qué es Node.js? setTimeout(function() { while (true); }, 100); setInterval(function() { console.log("Hola otra vez, Mundo del futuro!"); }, 1000);
  63. 63. 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);
  64. 64. 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);
  65. 65. Ahora en serio: ¿Qué es Node.js? Nos surgen problemas curiosos... • ¿Excepciones?
  66. 66. Ahora en serio: ¿Qué es Node.js? Nos surgen problemas curiosos... • ¿Excepciones? try { throw new Error("Peté!"); } catch(e) { console.log("Excepción!"); }
  67. 67. 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!"); }
  68. 68. 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
  69. 69. 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...");
  70. 70. 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...");
  71. 71. require/exports require(<paquete o ruta>) • Importar módulos (paquetes, otros ficheros) • Garantía: una única vez • Devuelve el módulo!
  72. 72. require/exports exports.propiedadPublica = <valor> • El otro lado del mecanismo • Se puede exportar cualquier valor
  73. 73. require/exports codigo.js var lib = require("./libreria"); console.log(lib.propiedad); libreria.js console.log("una vez"); exports.propiedad = "Pública";
  74. 74. 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"); });
  75. 75. 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);
  76. 76. 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
  77. 77. 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 • ...
  78. 78. 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
  79. 79. 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 - ...
  80. 80. JavaScript y el Universo Buffers var buf = new Buffer(100); buf.write("abcd", 0, 4, "ascii"); console.log(buf.toString("ascii"));
  81. 81. 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"));
  82. 82. 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"));
  83. 83. JavaScript y el Universo Buffers var buf = new Buffer(100); buf.write("abcd", 0, 4, "ascii"); console.log(buf.toString("ascii")); Codificación
  84. 84. JavaScript y el Universo Buffers
  85. 85. 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); } };
  86. 86. JavaScript y el Universo var bitmap = new Bitmap(1, 1); bitmap.putPixel(0, 0, "000000"); bitmap.render();
  87. 87. JavaScript y el Universo var bitmap = new Bitmap(10, 10); bitmap.fill("ffffff"); bitmap.putPixel(0, 0, "000000"); bitmap.render();
  88. 88. 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
  89. 89. JavaScript y el Universo Streams • Es raro crear streams directamente • Pero muchos recursos nos ofrecen este interfaz
  90. 90. 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
  91. 91. 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á!"); });
  92. 92. Una fácil Haz un programa que cuente las líneas de un fichero
  93. 93. 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
  94. 94. 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
  95. 95. 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!"); });
  96. 96. ¿Preguntas? Un buen momento para despejar dudas antes de seguir...
  97. 97. HTTP (por fin...)
  98. 98. 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
  99. 99. HTTP var http = require("http"); var server = http.createServer(); server.on("request", function(req, res) { res.end("Hola, Mundo!"); }); server.listen(3000);
  100. 100. 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);
  101. 101. 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)
  102. 102. HTTP El servidor HTTP • Eventos: - connection request • Operaciones: - createServer([requestCallback]) listen(puerto, [hostname], [backlog], [callback]) close([callback])
  103. 103. 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
  104. 104. 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
  105. 105. Manos a la obra Escribe un servidor web que devuelva la hora
  106. 106. 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>
  107. 107. 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);
  108. 108. 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);
  109. 109. HTTP
  110. 110. 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
  111. 111. 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); });
  112. 112. 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); });
  113. 113. 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?
  114. 114. 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)
  115. 115. 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
  116. 116. 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)
  117. 117. Servidor A/B Testing Tenéis maqueta y recursos en : /tema1/abtesting
  118. 118. Promesas
  119. 119. 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”
  120. 120. 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...
  121. 121. 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 } });
  122. 122. 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 } });
  123. 123. 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 } });
  124. 124. 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
  125. 125. 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 } });
  126. 126. CPS vs. Promesas
  127. 127. 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
  128. 128. 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 });
  129. 129. 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 });
  130. 130. 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 });
  131. 131. 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 });
  132. 132. 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 });
  133. 133. 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 });
  134. 134. 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 });
  135. 135. 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 });
  136. 136. 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 });
  137. 137. Promesas Una promesa = Un flujo de ejecución
  138. 138. Promesas ¡Pero aún no hemos ejecutado nada! Solamente hemos construído el flujo
  139. 139. 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
  140. 140. 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!"); })
  141. 141. 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!"); })
  142. 142. 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!"); })
  143. 143. 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!"); })
  144. 144. 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!"); })
  145. 145. 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!"); })
  146. 146. 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!"); })
  147. 147. 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!"); })
  148. 148. 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
  149. 149. Promesas var promesa = readFilePromise("./hola.txt"); promesa = promesa.then(function(data) { console.log("Contenido del fichero: ", data); }, function(err) { console.log("Ooops!", err); })
  150. 150. Promesas var promesa = readFilePromise("./hola.txt"); promesa = promesa.then(function(data) { console.log("Contenido del fichero: ", data); }, function(err) { console.log("Ooops!", err); })
  151. 151. Promesas var promesa = readFilePromise("./hola.txt"); promesa = promesa.then(function(data) { console.log("Contenido del fichero: ", data); }, function(err) { console.log("Ooops!", err); })
  152. 152. Promesas var promesa = readFilePromise("./hola.txt"); promesa = promesa.then(function(data) { console.log("Contenido del fichero: ", data); }, function(err) { console.log("Ooops!", err); })
  153. 153. Promesas var promesa = readFilePromise("./hola.txt"); promesa = promesa.then(function(data) { console.log("Contenido del fichero: ", data); }, function(err) { console.log("Ooops!", err); })
  154. 154. Promesas var promesa = readFilePromise("./hola.txt"); promesa = promesa.then(function(data) { console.log("Contenido del fichero: ", data); }, function(err) { console.log("Ooops!", err); })
  155. 155. Promesas var promesa = readFilePromise("./hola.txt"); promesa = promesa.then(function(data) { console.log("Contenido del fichero: ", data); }, function(err) { console.log("Ooops!", err); })
  156. 156. 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
  157. 157. 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
  158. 158. 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
  159. 159. 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
  160. 160. 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
  161. 161. 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)
  162. 162. 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); });
  163. 163. 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); });
  164. 164. 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?
  165. 165. 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é?
  166. 166. 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?
  167. 167. 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
  168. 168. 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
  169. 169. 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);
  170. 170. 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!"));
  171. 171. 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"; })
  172. 172. 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);
  173. 173. 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);
  174. 174. 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);
  175. 175. 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);
  176. 176. 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");
  177. 177. 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);
  178. 178. 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);
  179. 179. 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);
  180. 180. 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);
  181. 181. 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);
  182. 182. 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
  183. 183. 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);
  184. 184. 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);
  185. 185. 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
  186. 186. 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
  187. 187. Promesas function readFilePromise(filePath) { // ??? } readFilePromise("./hola.txt").then(function(contenido) { console.log("contenido:", contenido.toString()); }, function(err) { console.log("ERROR!", err); });
  188. 188. 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); });
  189. 189. 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); });
  190. 190. Promesas: paréntesis Intenta modificar el servidor de ficheros estáticos para que utilice promesas: • fileExistsPromise • readFilePromise
  191. 191. 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
  192. 192. 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
  193. 193. 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);
  194. 194. Q.all([promise, promise, ...])
  195. 195. 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 })
  196. 196. 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);
  197. 197. 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);
  198. 198. 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); });
  199. 199. 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 })
  200. 200. 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);
  201. 201. 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);
  202. 202. 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()
  203. 203. ¡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...)
  204. 204. 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
  205. 205. 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); })
  206. 206. 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); })
  207. 207. 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()
  208. 208. Primer paso: listado recursivo
  209. 209. 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
  210. 210. 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.
  211. 211. 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
  212. 212. 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) { })
  213. 213. 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); })
  214. 214. Express
  215. 215. ¿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, ...
  216. 216. ¿Qué es express? Express nos va ayudar con... • Rutas • Parámetros • Formularios y subida de ficheros • Cookies • Sesiones • Templates
  217. 217. ¿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
  218. 218. ¿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
  219. 219. ¿Qué es express? var express = require("express"); var app = express(); // configuración + rutas app.listen(3000);
  220. 220. ¿Qué es express? Equivalente a: var express = require("express"), http = require("http"); var app = express(); // configuración + rutas http.createServer(app).listen(3000);
  221. 221. ¿Qué es express? Equivalente a: var express = require("express"), http = require("http"); var app = express(); // configuración + rutas http.createServer(app).listen(3000);
  222. 222. ¿Qué es express? var app = require("express")(); app.get("/", function(req, res) { res.end("Hola desde express!"); }); app.listen(3000);
  223. 223. ¿Qué es express? Verbo Ruta Manejador var app = require("express")(); app.get("/", function(req, res) { res.end("Hola desde express!"); }); app.listen(3000);
  224. 224. ¿Qué es express? Objeto Stream var app = require("express")(); app.get("/", function(req, res) { res.end("Hola desde express!"); }); app.listen(3000);
  225. 225. ¿Qué es express? ¿Qué nos aporta express, exactamente? • Depende de los middlewares que usemos! • Algunas cosas vienen por defecto
  226. 226. Request req.params: parámetros de la ruta app.get("/user/:id", function(req, res) { req.params.id; });
  227. 227. Request req.query: la querystring, parseada app.get("/search", function(req, res) { // GET /search?text=nodejs+express req.query.text; });
  228. 228. 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
  229. 229. Response res.cookie(nombre, valor, [opciones]) • Modifica la cookie “nombre” con el valor “valor” res.cookie("visitas", "1", {domain: ".ejemplo.com"});
  230. 230. 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");
  231. 231. 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..."});
  232. 232. Response Muchos otros métodos auxiliares: • Cabeceras • Envío de ficheros • JSONP • Content-Type • ¡Lee la documentación! http://expressjs.com/api.html
  233. 233. 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'); })
  234. 234. Application ¿Qué significa “configurar la aplicación”? • Crear algunas propiedades globales • Especificar el stack de middleware
  235. 235. 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');
  236. 236. 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)
  237. 237. 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'));
  238. 238. 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'));
  239. 239. 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'));
  240. 240. Middleware Express trae unos cuantos preinstalados • http://www.senchalabs.org/connect/ Una lista de módulos de terceros • https://github.com/senchalabs/connect/wiki
  241. 241. express.favicon(ruta) Sirve el favicon de la aplicación • Debe ser el primero • Para evitar capas inncesarias • log • parseo • cookies • etc...
  242. 242. express.logger([opciones]) Registro de actividad • Muchas opciones... http://www.senchalabs.org/connect/logger.html • Se suele poner debajo de express.favicon()
  243. 243. 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); })
  244. 244. 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
  245. 245. 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)
  246. 246. 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);
  247. 247. 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
  248. 248. 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
  249. 249. 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'));
  250. 250. Templates Express tiene un mecanismo para rendear templates • Agnóstico • Modular • Simple • NO trae ningún motor de templates por defecto
  251. 251. Templates res.render(view, [locals], callback) • view: ruta del template • locals: valores a interpolar • callback: function(err, html) { ... }
  252. 252. Templates Tenemos muchos motores de templates para elegir! https://npmjs.org/browse/keyword/template • Haml • Hogan/Mustache • Twig/Swig • Ejs • Jinja • Jade ....
  253. 253. Ejs <h1><%= title %></h1> <ul> <% for(var i=0; i<supplies.length; i++) { %> <li> <a href='supplies/<%= supplies[i] %>'> <%= supplies[i] %> </a> </li> <% } %> </ul>
  254. 254. Jade
  255. 255. Templates Algunas propuestas originales/innovadoras: CoffeeKup: http://coffeekup.org/ Weld: https://github.com/tmpvar/weld Domo: http://domo-js.com/
  256. 256. 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)
  257. 257. 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)
  258. 258. 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)
  259. 259. 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)
  260. 260. 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)
  261. 261. Templates Donde /views/welcome.jade sería algo así: doctype 5 html(lang="es") body h1 Bienvenido, #{user}
  262. 262. 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)
  263. 263. 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
  264. 264. 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
  265. 265. 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); });
  266. 266. 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
  267. 267. 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(); }); })
  268. 268. 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!
  269. 269. 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...
  270. 270. 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()); })
  271. 271. 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()); })
  272. 272. 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()); })
  273. 273. 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)
  274. 274. 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)
  275. 275. 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
  276. 276. Más Middleware Dos maneras de activar middlewares • Globalmente (app.use), activos para toda la app • Locales para ciertas rutas
  277. 277. 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);
  278. 278. Más Middleware Afinar la funcionalidad de cada ruta • Pre-procesado de URL (parámetros, formato) • Seguridad • Caché • Métricas
  279. 279. 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()); })
  280. 280. 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
  281. 281. SimpleAuth Módulo para autenticar usuarios simpleAuth.js • Sencillo y flexible • Independiente de la BBDD • Utilizando las sesiones de express • Middleware de ruta
  282. 282. 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
  283. 283. 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"); });
  284. 284. 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?
  285. 285. 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); } } });
  286. 286. 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
  287. 287. 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
  288. 288. 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
  289. 289. SimpleAuth auth.destroySession 1. Borra la entrada adecuada en la sesión o cookie
  290. 290. SimpleAuth auth.setStrategy(strategy) 1. Configura la estretagia: - strategy.serializeUser - strategy.deserializeUser - strategy.checkCredentials - strategy.loginRoute
  291. 291. 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
  292. 292. Redis
  293. 293. ¿Qué es Redis? Una base de datos NoSQL... • Clave/valor • Extremadamente rápida • Persistencia y transacciones • Estructuras de datos - Listas - Conjuntos - Hashes • Pub/sub
  294. 294. ¿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
  295. 295. ¿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 :)
  296. 296. ¿Qué NO es Redis? Redis es una opción terrible para... • Relaciones de datos complejas • Búsquedas
  297. 297. ¿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
  298. 298. 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)
  299. 299. 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
  300. 300. 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);
  301. 301. Primeros pasos Ahora puedes mandar comandos a Redis • • client.nombreDelComando(arg, callback) • callback: function(err, value) { } client.nombreDelComando(arg1, arg2, callback)
  302. 302. 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); }); })
  303. 303. 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); })
  304. 304. 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); }); })
  305. 305. 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); }); })
  306. 306. 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();
  307. 307. SET / GET Guardar y recuperar un valor
  308. 308. Un truco: redis-cli monitor Para ver qué está pasando en Redis
  309. 309. 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();
  310. 310. 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();
  311. 311. 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();
  312. 312. 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();
  313. 313. 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();
  314. 314. 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
  315. 315. 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();
  316. 316. 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
  317. 317. 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();
  318. 318. 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()
  319. 319. 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
  320. 320. 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()
  321. 321. 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
  322. 322. 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
  323. 323. 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()
  324. 324. 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
  325. 325. 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()
  326. 326. 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)); }); });
  327. 327. MongoDB
  328. 328. ¿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
  329. 329. ¿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
  330. 330. ¿Qué es MongoDB? Con algunos extras interesantes... • Índices geoespaciales • Búsquedas FTS • Almacenamiento eficiente de blobs y ficheros • Sharding automático • Replicación
  331. 331. ¿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)
  332. 332. ¿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
  333. 333. 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!
  334. 334. 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>
  335. 335. 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) });
  336. 336. Primeros pasos La BD está dividida en colecciones. Para abrir una colección: var collection = client.then(function(db) { return db.collection("coleccion"); });
  337. 337. Documento Un documento es un objeto BSON { "_id" : ObjectId("524872a99c50880000000001"), "email" : "test@asdf.com", "password" : "asdf1234", "name" : "Test User", "date" : 1380479657300, "token" : "hm6ly43v.0o1or" }
  338. 338. Documento Un documento es un objeto BSON { "_id" : ObjectId("524872a99c50880000000001"), "email" : "test@asdf.com", "password" : "asdf1234", "name" : "Test User", "date" : 1380479657300, "token" : "hm6ly43v.0o1or" }
  339. 339. 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" ] }
  340. 340. 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" ] }
  341. 341. 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" ] }
  342. 342. 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
  343. 343. 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
  344. 344. 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
  345. 345. Colecciones MongoDB trae un cliente de línea de comandos • mongo <host>/<dbname> • Ejecuta JavaScript • Muy práctico para explorar
  346. 346. Colecciones
  347. 347. Colecciones
  348. 348. 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(); });
  349. 349. Consulta Dos operaciones fundamentales: •findOne: devuelve un documento •find: devuelve varios documentos en un cursor
  350. 350. 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); });

×