0% encontró este documento útil (0 votos)
31 vistas

Libro para Principiantes Node Js

Cargado por

Manuel
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
31 vistas

Libro para Principiantes Node Js

Cargado por

Manuel
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF o lee en línea desde Scribd
Está en la página 1/ 98
El Libro para Principiantes en Node,js Un tutorial de Node js por: Manuel Kiessling & Herman A. Junge Sobre el Tutorial E] objetivo de este documento es ayudarte a empezar con el desarrollo de aplicaciones para Node.js, ensefidndote todo lo que necesites saber acerca de JavaScript "avanzado" sobre la marcha. Este tutorial va mucho mas alld del tipico manual "Hola Mundo". Status Estas leyendo la versién final de este libro, es decir, las actualizaciones solo seran hechas para corregir errores 0 para reflejar cambiar en nuevas versiones de Node.js. Las muestras de cédigo de este libro estan probadas para funcionar con la versién 0.6.11 de Node.js. Audiencia Objetivo Este documento probablemente sera mejor entendido por los lectores que tengan un trasfondo similar al mio: Programadores experimentados en al menos un lenguaje orientado al objeto, como Ruby, Python, PHP o Java; poca experiencia con JavaScript, y ninguna experiencia en Node.js. El que este documento esté orientado a desarrolladores que ya tienen experiencia con otros lenguajes de programacién significa que no vamos a cubrir temas realmente basicos como tipos de datos, variables, estructuras de control y similares. Debes saber acerca de estos tépicos para entender este documento. Sin embargo, dado que las funciones y objetos en JavaScript son diferentes de sus contrapartes en la mayoria de los lenguajes, estos seran explicados con mas detalle. Estructura de este documento Al Término de este documento, habras creado una aplicacién Web completa, que permita a los usuarios de ésta el ver paginas web y subir archivos. La cual, por supuesto, no va ser nada como la "aplicacién que va a cambiar el mundo", no obstante eso, nosotros haremos algo mas y no vamos sdlo a codificar una aplicacién lo "suficientemente simple" para hacer estos casos de uso posible, sino que crearemos un framework sencillo, pero completo, a fin de poder separar los distintos aspectos de nuestra aplicacién. Veras lo que esto significa en poco tiempo. Empezaremos por mirar cémo el desarrollo en JavaScript en Node.js es diferente del desarrollo en JavaScript en un browser. Luego, nos mantendremos con la vieja tradicion de escribir una aplicacién "Hola Mundo", la cual es la aplicacién mas basica de Node.js que "hace" algo. Enseguida, discutiremos que tipo de "aplicacién del mundo real" queremos construir, disectaremos las diferentes partes que necesitan ser implementadas para ensamblar esta aplicacion, y empezaremos trabajando en cada una de estas partes paso a paso. Segiin lo prometido, aprenderemos sobre la marcha acerca de algunos de los muchos conceptos avanzados de JavaScript, como hacer uso de ellos, y ver el por qué tiene sentido el hacer uso de estos conceptos en vez de los que ya conocemos por otros lenguajes de programacién. Tabla de Contenidos JavaScript y Node.js JavaScript y Ta Antes que hablemos de toda la parte técnica, tomémonos un minuto y hablemos acerca de ti y tu relacién con JavaScript. Este capitulo esta aqui para permitirte estimar si tiene sentido el que sigas o no leyendo este documento. Si eres como yo, empezaste con el "desarrollo" HTML hace bastante tiempo, escribiendo documentos HTML. Te encontraste en el camino con esta cosa simpatica llamada JavaScript, pero solo la usabas en una forma muy basica, agregando interactividad a tus paginas de cuando en cuando. Lo que realmente quisiste era "la cosa real", querias saber como construir sitios web complejos - Aprendiste un lenguaje de programacién como PHP, Ruby, Java, y empezaste a escribir cédigo "backend". No obstante, mantuviste un ojo en JavaScript, y te diste cuenta que con la introduccién de jQuery, Prototype y otros, las cosas se fueron poniendo mas avanzadas en las Tierras de JavaScript, y que este lenguaje era realmente mas que hacer un window.open(. Sin embargo, esto era todo cosa del frontend, y aunque era agradable contar con jQuery a tu disposicién en cualquier momento que te sintieras con animo de sazonar una pagina web, al final del dia, lo que eras a lo mas, era un usuario de JavaScript, pero no, un desarrollador de JavaScript. Y entonces llegé Node.js. JavaScript en el servidor, Qué hay con eso? Decidiste que era ya tiempo de revisar el nuevo JavaScript. Pero espera: Escribir aplicaciones Node.js es una cosa; Entender el por qué ellas necesitan ser escritas en la manera que lo son significa entender JavaScript! Y esta vez es en serio. Y aqui esta el problema: Ya que JavaScript realmente vive dos, o tal vez tres vidas (El pequefio ayudante DHTML de mediados de los 90's, las cosas mas serias tales como jQuery y similares, y ahora, el lado del servidor), no es tan facil encontrar informacion que te ayude a aprender JavaScript de Ja “manera correcta", de forma de poder escribir aplicaciones de Node.js en una apariencia que te haga sentir que no sdlo estés usando JavaScript, sino que también estén desarrollando con él. Porque ahi esta el asunto: Ya eres un desarrollador experimentado, y no quieres aprender una nueva técnica simplemente metiendo cddigo aqui y alla mal- aprovechandolo; Quieres estar seguro que te estas enfocando en un angulo correcto. Hay, por supuesto, excelente documentacion afuera. Pero la documentacién por si sola no es suficiente. Lo que se necesita es una guia. Mi objetivo es proveerte esta guia. Una Advertencia Hay algunas personas realmente excelente en JavaScript. No soy una de ellas. Yo soy realmente el tipo del que te he hablado en los parrafos previos. Sé un par de cosas acerca de desarrollar aplicaciones backend, pero atin soy nuevo al JavaScript "real" y ain mas nuevo a Node.js. He aprendido solo recientemente alguno de los aspectos avanzados de JavaScript. No soy experimentado. Por lo que este no es un libro "desde novicio hasta experto". Este es mas bien un libro "desde novicio a novicio avanzado". Si no fallo, entonces este sera el tipo de documento que deseo hubiese tenido cuando empecé con Node.js. JavaScript del Lado del Servidor Tae nrimarne annamanionne da TevaGarint ufufan an lac Las primeras encarnaciones de JavaScript vivian en los browsers. Pero esto es sdlo el contexto. Define lo que puedes hacer con el lenguaje, pero no dice mucho acerca de lo que el lenguaje mismo puede hacer. JavaScript es un lenguaje "completo": Lo puedes usar en muchos contextos y alcanzar con éste, todo lo que puedes alcanzar con cualquier otro lenguaje "completo". Node.js realmente es sélo otro contexto: te permite correr codigo JavaScript en el backend, fuera del browser. Para ejecutar el codigo JavaScript que tu pretendes correr en el backend, este necesita ser interpretado y, bueno, ejecutado, Esto es lo que Node.js realiza, haciendo uso de la Maquina Virtual V8 de Google, el mismo entorno de ejecucién para JavaScript que Google Chrome utiliza. Ademas, Node.js viene con muchos méddulos ttiles, de manera que no tienes que escribir todo de cero, como por ejemplo, algo que ponga un string a la consola. Entonces, Node.js es en realidad dos cosas: un entorno de ejecucion y una libreria. Para hacer uso de éstas (la libreria y el entorno), necesitas instalar Node.js. En lugar de repetir el proceso aqui, te ruego visitar las_instrucciones oficiales de instalacion. Por favor vuelve una vez que tengas tu version de Node.js corriendo. "Hola Mundo" Ok. Saltemos enionces al agua fria y escribamos nuesira primera aplicacién Node.js: "Hola Mundo". Abre tu editor favorito y crea un archivo llamado holamundo,js. Nosotros queremos escribir "Hola Mundo" a STDOUT, y aqui esta el cddigo necesario para hacer esto: console-log("Hola Mundo"); Graba el archivo, y ejectitalo a través de Node.js: node holamando.32 Este deberia retornar Hola Mundo en tu monitor. Ok, esto es aburrido, de acuerdo? Asi que escribamos alguna cosa real. Una Aplicacién Web Completa con Node.js Los casos de Uso Mantengaémoslo simple, pero realista: ¢ E] Usuario deberia ser capaz de ocupar nuestra aplicacién con un browser. El Usuario deberia ver una pagina de bienvenida cuando solicita http://dominio/inicio, la cual despliega un formulario de subida. e Eligiendo un archivo de imagen para subir y enviando el formulario, la imagen deberia ser subida a http://dominio /subir, donde es desplegada una vez que la subida este finalizada. Muy bien. Ahora, tu puedes ser capaz de alcanzar este objetivo googleando y programando lo que sea, pero eso no es Jo que queremos hacer aqui. Mas que eso, no queremos escribir simplemente el cédigo mas basico posible para alcanzar este objetivo, no importa lo elegante y correcto que pueda ser este cédigo. Nosotros agregaremos intencionalmente mas abstraccién de la necesaria de manera de poder tener una idea de lo que es construir aplicaciones mas complejas de Node.js. La Pila de Aplicaciones Hagamos un desglose a nuestra aplicacién. ¢Qué partes necesitan ser implementadas para poder satisfacer nuestros casos de uso? © Queremos servir paginas web, de manera que necesitamos un Servidor HTTP. e Nuestro servidor necesitara responder directamente peticiones (requests), dependiendo de qué URL sea pedida en este requerimiento, es que necesitaremos algiin tipo de enrutador (router) de manera de mapear los peticiones a los handlers (manejadores) de éstos. Para satisfacer a los peticiones que llegaron al servidor y han sido ruteados usando el enrutador, necesitaremos de hecho handlers (manejadores) de peticiones e E] Enrutador probablemente deberia tratar cualquier informacién POST que llegue y darsela a los handlers de peticiones en una forma conveniente, luego necesitaremos manipulaci6n de data de petici6én © Nosotros no solo queremos manejar peticiones de URLs, sino que también queremos desplegar contenido cuando estas URLs sean pedidas, lo que significa que necesitamos algan tipo de légica en las vistas a ser utilizada por los handlers de peticiones, de manera de poder enviar contenido al browser del Usuario. Por tiltimo, pero no menos importante, el Usuario sera capaz de subir imagenes, asi que necesitaremos algun tipo de manipulacién de subidas quien se hard cargo de los detalles. Pensemos un momento acerca de como construiriamos esta pila de aplicaciones con PHP. No es exactamente un secreto que la configuracién tipica seria un Apache HTTP server con mod_php35 instalado. Lo que, a su vez, significa que el tema "Necesitamos ser capaces de servir paginas web y recibir peticiones HTTP" ni siquiera sucede dentro de PHP mismo. Bueno, con Node.js, las cosas son un poco distintas. Porque con Node.js, no solo implementamos nuestra aplicaci6n, nosotros también implementamos todo el servidor HTTP completo. De hecho, nuestra aplicacién web y su servidor web son basicamente lo mismo. Esto puede sonar como mucho trabajo, pero veremos en un momento que con Node.js, no lo es. Empecemos por el principio e implementemos la primera parte de nuestra pila, el servidor HTTP.. Construyendo la Pila de Aplicaciones Un Servidor HTTP Basico Cuando llegué al punto donde queria empezar con mi primera aplicacién Node.js "real", me pregunté no solo como la iba a programar, sino que también, como organizar mi cédigo. éNecesitaré tenerlo todo en un archivo? Muchos tutoriales en la Web que te ensefian como escribir un servidor HTTP basico en Node.js tienen toda la logica en un solo lugar. éQué pasa si yo quiero asegurarme que mi cédigo se mantenga leible a medida que le vaya agregando mas cosas? Resulta, que es relativamente facil de mantener los distintos aspectos de tu cddigo separados, poniéndolos en médulos. Esto te permite tener un archivo main limpio, en el cual ejecutas Node.js, y médulos limpios que pueden ser utilizados por el archivo main entre muchos otros. Asi que vamos a crear un archivo main el cual usaremos para iniciar nuestra aplicacién, y un archivo de médulo dénde residira el codigo de nuestro servidor HTTP. Mi impresion es que es mas o menos un estandar nombrar a tu archivo principal como index.js. Tiene sentido también que pongamos nuestro médulo de servidor en un archivo llamado server js. Empecemos con el médulo del servidor. Crea el archivo server,js en el directorio raiz de tu proyecto, y llénalo con el cédigo siguiente: var http ~ require ("hetpt): http.createServer (function (request, response) { response.writeliead (200, ["Content-Type": "text/html"}): response.write("Hola Mundo"); response end () 7 }) -listen(assg) : Eso es! Acabas de escribir un servidor HTTP activo. Probémoslo ejecuténdolo y testedndolo. Primero ejecuta tu script con Node.js: Ahora, abre tu browser y apuntalo a http://localhost:8888/. Esto deberia desplegar una pdgina web que diga "Hola Mundo". Interesante, éno? ¢Qué tal si hablamos de que esta pasando aqui y dejamos la pregunta de 'cOmo organizar nuestro proyecto’ para después? Prometo que volveremos a esto. Analizando nuestro servidor HTTP Bueno, entonces, analicemos que esta pasando aqui. La primera linea require, requiere al médulo http que viene incluido con Node.js y lo hace accesible a través de la variable http. Luego llamamos a una de las funciones que el médulo http ofrece: createServer. Esta funcién retorna un objeto, y este objeto tiene un método llamado listen (escucha), y toma un valor numérico que indica el numero de puerto en que nuestro servidor HTTP va a escuchar. Por favor ignora por un segundo a la definicién de funcién que sigue a la llave de apertura de http.createServer. Nosotros podriamos haber escrito el cédigo que inicia a nuestro servidor y lo hace escuchar al puerto 8888 de la siguiente manera: var http ~ require ("http"); var server = http.createServer (); server. listen (3888) 7 Esto hubiese iniciado al servidor HTTP en el puerto 8888 y no hubiese hecho nada mas (ni siquiera respondido alguna peticion entrante). La parte realmente interesante (y rara, si tu trasfondo es en un lenguaje mas conservador, como PHP) es que la definicién de funcién esta ahi mismo donde uno esperaria el primer parametro de la llamada a createServer(). Resulta que, este definicién de funcién ES el primer (y tinico) parametro que le vamos a dar a la llamada a createServer(. Ya que en JavaScript, las funciones pueden ser pasadas de un lado a otro como cualquier otro valor. Pasando Funciones de un Lado a Otrs Puedes, por ejemplo, hacer algo como esto: fonction decir (palabra) ( console. log (palabra) : ) function eJecutar (algunaFuncion, valor) ( ‘algunafuncion (valor) : ) ejecutar (decir, “Hola"); Lee esto cuidadosamente! Lo que estamos haciendo aqui es, nosotros pasamos la funcién decir) como el primer parametro de la funcion ejecutar. No el valor de retorno de decir, sino que decir() misma! Entonces, decir se convierte en la_ variable local algunaFuncion dentro de ejecutar, y ejecutar puede llamar a Ja funcién en esta variable usando algunaFuncion() (agregando llaves). Por supuesto, dado que decir toma un parametro, ejecutar puede pasar tal parametro cuando llama a algunaFuncion. Nosotros podemos, tal como lo hicimos, pasar una funcién por su nombre como parametro a otra funcién. Pero no estamos obligados a tener que definir la funcién primero y luego pasarla. Podemos también definir y pasar la funcién como un parametro a otra funcién todo al mismo tiempo: function ejecutar(algunaFuncion, valor) { algunaFuncion (valor) : , ejecutar (function (palabra) { console.log(palabra) }, Hola"); (N.del T.: function es una palabra clave de JavaScript). Nosotros definimos la funcién que queremos pasar a ejecutar justo ahi en el lugar donde ejecutar espera su primer parametro. De esta manera, no necesitamos darle a la funcién un nombre, por lo que esta funcién es llamada funcién anénima. Esta es una primera ojeada a lo que me gusta llamar JavaScript "avanzado". Pero tomémoslo paso a paso. Por ahora, aceptemos que en JavaScript, nosotros podemos pasar una funcién como un parametro cuando llamamos a otra funcién. Podemos hacer esto asignando nuestra funcién a una variable, la cual luego pasamos, o definiendo la funcién a pasar en el mismo lugar. De Qué manera el pasar funciones hace que nuestro servidor HTTP funcione Con este conocimiento, Volvamos a nuestro servidor HTTP minimalista: var http = require ("http"); http-createServer (function (request, response) { response. writeHead (200, ("Content-Type": "text/ntml")) + response.write ("Hola Mundo"); response.end() 1) -Listen (8888) ; Aestas alturas, deberia quedar claro lo que estamos haciendo aca: Estamos pasdndole a la funcién createServer una funci6n anénima. Podemos llegar a lo mismo refactorizando nuestro cédigo asi: var http = require ("http"); function onRequest (request, response) { reoponse.writeHead (200, response. writ reaponse.end() , ontent—Type: "text /html")) jola Mundo": http-createserver (onRequest) -1isten (8888) Quizas ahora es un buen momento para preguntar: ¢Por Qué estamos haciendo esto de esta manera? Callbacks Manejadas por Eventos La respuesta a) No es algo facil de explicar (al menos para mi), y b) Yace en la naturaleza misma de como Node.js trabaja: Esta orientado al evento, esa es la razon de por qué es tan rapido. Podrias tomarte un tiempo para leer este excelente post (en inglés) de Felix Geisendérdfer: Understanding node.js para alguna explicacién de trasfondo. Al final todo se reduce al hecho que Node.js trabaja orientado al evento. Ah, y si, yo tampoco sé exactamente qué significa eso. Pero voy a hacer un intento de explicar, el por qué esto tiene sentido para nosotros, que queremos escribir aplicaciones web en Node.js. Cuando nosotros llamamos al método http.createServer, por supuesto que no sdlo queremos que el servidor se quede escuchando en algin puerto, sino que también queremos hacer algo cuando hay una peticion HTTP a este servidor. El problema es, que esto sucede de manera asincrona: Puede suceder en cualquier momento, pero solo tenemos un unico proceso en el cual nuestro servidor corre. Cuando escribimos aplicaciones PHP, esto no nos molesta en absoluto: cada vez que hay una peticién HTTP, el servidor web (por lo general Apache) genera un nuevo proceso solo para esta peticién, y empieza el script PHP indicado desde cero, el cual es ejecutado de principio a fin. Asi que respecto al control de flujo, estamos en el medio de nuestro programa en Node.js, cuando una nueva peticién llega al puerto 8888: éComo manipulamos esto sin volvernos locos? Bueno, esta es la parte donde el disefio orientado al evento de Node.js / JavaScript de verdad ayuda, aunque tengamos que aprender nuevos conceptos para poder dominarlo. Veamos como estos conceptos son aplicados en nuestro cédigo de servidor. Nosotros creamos el servidor, y pasamos una funci6n al método que lo crea. Cada vez que nuestro servidor recibe una peticién, la funcién que le pasamos ser llamada. No sabemos qué es lo que va a suceder, pero ahora tenemos un lugar donde vamos a poder manipular la peticin entrante. Es la funcién que pasamos, sin importar si la definimos o si la pasamos de manera anonima. Este concepto es llamado un callback (N. del T.: del inglés: call = llamar; y back = de vuelta). Nosotros pasamos una funcién a algain método, y el método ocupa esta funcién para llamar (call) de vuelta (back) si un evento relacionado con este método ocurre. Al menos para mi, esto tomd algin tiempo para ser entendido. Lee el articulo del blog de Felix de nuevo si todavia no te sientes seguro. Juguemos un poco con este nuevo concepto. ¢Podemos probar que nuestro cédigo contintia después de haber creado el servidor, incluso si no ha sucedido ninguna peticion HTTP y la funcién callback que pasamos no ha sido llamada? Probemos: var http ~ require ("hetpt): function onRequest (request, response) { console.log ("Peticion Recibida."); response.writetiead (200, ("Content-Type": “text/html")); response.write("Hola Mundo") ; response.end(); , http.createServer (onRequest) -1isten (8888); console-1og ("Servidor Iniciado."); Noten que utilizo console.log para entregar un texto cada vez que la funcién onRequest (nuestro callback) es gatillada, y otro texto después de iniciar nuestro servidor HTTP. Cuando iniciamos esta aplicacién (con node server.js, como siempre). Esta inmediatamente escribira en pantalla "Servidor Iniciado" en la linea de comandos. Cada vez que hagamos una peticion a nuestro servidor (abriendo http://localhost:8888/ en nuestro browser), el mensaje "Peticion Recibida." va a ser impreso en la linea de comandos. Esto es JavaScript del lado del servidor asincrono y orientado al evento con callbacks en accién :-) (Toma en cuenta que nuestro servidor probablemente escribir "Peticién Recibida." a STDOUT dos veces al abrir la pagina en un browser. Esto es porque la mayoria de los browsers van a tratar de cargar el favicon mediante la peticion http://localhost:8888/favicon.ico cada vez que abras http://localhost:8888/). Como nuestro Servidor manipula las peticiones OK, analicemos rapidamente el resto del cddigo de nuestro servidor, esto es, el cuerpo de nuestra funcién de callback onRequest(). Cuando la callback es disparada y nuestra funcién onRequest() es gatillada, dos pardmetros son pasados a ella: request y response. Estos son objetos, y puedes usar sus métodos para manejar los detalles de la peticién HTTP ocurrida y responder a la peticion (en otras palabras enviar algo de vuelta al browser que hizo la peticion a tu servidor). Y eso es lo que nuestro cédigo hace: cada vez que una peticion es recibida, usa la funcién response.writeHead() para enviar un estatus HTTP 200 y un content-type (parametro que define que tipo de contenido es) en el encabezado de la respuesta HTTP, y la funcion response.write() para enviar el texto "Hola Mundo" en el cuerpo de la respuesta. Por ultimo, nosotros llamamos response.end() para finalizar nuestra respuesta. Hasta el momento, no nos hemos interesado por los detalles de la peticion, y ese es el por qué no hemos ocupado el objeto request completamente. Encontrando un lugar para nuestro modulo de servidor OK, prometi que volveriamos a al Cémo organizar nuestra aplicacién. Tenemos el cédigo de nuestro servidor HTTP muy basico en el archivo server.js, y mencioné que es comun tener un archivo principal llamado index,js, el cual es usado para arrancar y partir nuestra aplicacién haciendo uso de los otros médulos de la aplicacién (como el médulo de servidor HTTP que vive en server’js). Hablemos de como podemos hacer que nuestro server.js sea un verdadero médulo Node.js y que pueda ser usado por nuestro pronto-a-ser-escrito archivo principal index,js. Como habran notado, ya hemos usado médulos en nuestro cédigo, como éste: var http ~ require ("ntep"): nttp-createserver(...)7 En algtin lugar dentro de Node.js vive un médulo llamado "http", y podemos hacer uso de éste en nuestro propio cédigo requiriéndolo y asignando el resultado del requerimiento a una variable local. Esto transforma a nuestra variable local en un objeto que acarrea todos los métodos ptblicos que el médulo http provee. Es practica comtn elegir el nombre del médulo como nombre para nuestra variable local, pero somos libres de escoger cualquiera que nos guste: var foo = require ("http")? Peteneseeseeree a Bien. Ya tenemos claro como hacer uso de los médulos internos de Node.js. ¢Como hacemos para crear nuestros propios médulos, y Como los utilizamos? Descubramoslo transformando nuestro script server,js en un modulo real. Sucede que, no tenemos que transformarlo tanto. Hacer que algin cédigo sea un Médulo, significa que necesitamos exportar las partes de su funcionalidad que queremos proveer a otros scripts que requieran nuestro médulo. Por ahora, la funcionalidad que nuestro servidor HTTP necesita exportar es simple: Permitir a los scripts que utilicen este médulo arrancar el servidor. Para hacer esto posible, dotaremos al cédigo de nuestro servidor de una funcién llamada inicio, y exportaremos esta funcién: var http = require("http); function iniciar() { function onRequest (request, response) { console. 1log("Feticion Recibida.")? response .writeHead (200, {"Content-Type": "text/htmI™)) response.write("Hola Mando") ; responae end); , http.createServer (onRequest) . Listen (8888) : console.log ("Servidor Iniciado."); exporte-iniciar ~ inicier: De este modo, Podemos crear nuestro propio archivo principal indexjs, y arrancar nuestro servidor HTTP alli, aunque el cédigo para el servidor este en nuestro archivo server js. Crea un archivo index.js con el siguiente contenido: var verver = require("./server"): server-iniciar(); Como puedes ver, nosotros utilizamos nuestro médulo de servidor tal como cualquier otro médulo interno: requiriendo el archivo donde esta contenido y asignandolo a una variable, con las funciones que tenga 'exportadas' disponibles para nosotros. Eso es. Podemos ahora arrancar nuestra aplicacién por medio de nuestro script principal, y va a hacer exactamente lo mismo: node index.j= Bien, ahora podemos poner las diferentes partes de nuestra aplicacién en archivos diferentes y enlazarlas juntas a través de la creacién de estos médulos. Tenemos solo la primera parte de nuestra aplicacién en su lugar: Podemos recibir peticiones HTTP. Pero necesitamos hacer algo con ellas - necesitamos reaccionar de manera diferente, dependiendo de que URL el browser requiera de nuestro servidor. Para una aplicacién muy simple, podrias hacer esto directamente dentro de una funcién de callback OnRequest(Q). Pero, como dije, agreguemos un poco mas de abstraccién, de manera de hacer nuestra aplicacién mas interesante. Hacer diferentes peticiones HTTP ir a partes diferentes de nuestro cédigo se llama "ruteo" (N. del T.: routing, en inglés) - bueno, entonces, creemos un mddulo llamado router. éQué se necesita para "rutear" peticiones? Necesitamos ser capaces de entregar la URL requerida y los posibles parametros GET o POST adicionales a nuestro router, y basado en estos, el router debe ser capaz de decidir qué cédigo ejecutar (este "cddigo a ejecutar" es la tercera parte de nuestra aplicacion: una coleccién de manipuladores de peticiones que haran el verdadero trabajo cuando una peticion es recibida). Asi que, Necesitamos mirar en las peticiones HTTP y extraer la URL requerida, asi como los paraémetros GET/POST de ellos. Se puede discutir acerca de si este procedimiento debe ser parte del router o del servidor (0 si lo hacemos un médulo por si mismo), pero hagamos el acuerdo de hacerlo parte de nuestro servidor HTTP por ahora. Toda la informacién que necesitamos esta disponible en el objeto request, el que es pasado como primer parametro a nuestra funcién callback onRequestQ. Pero para interpretar esta informaci6n, necesitamos algunos médulos adicionales Node.js, llamados url y querystring. El médulo url provee métodos que nos permite extraer las diferentes partes de una URL (como por ejemplo la ruta requerida y el string de consulta), y querystring puede, en cambio, ser usado para parsear el string de consulta para los parametros requeridos: wepacee (ating) oueey weLeperoe(string) pathname | 1 http: //localhost:8888/iniciar?fi fl querystring (string) ("foo") | querystring (string) ["hel1o"] Podemos, por supuesto, también utilizar querystring para parsear el cuerpo de una peticion POST en busca de parametros, como veremos mas tarde. Agreguemos ahora a nuestra funcién onRequest( la légica requerida para encontrar que ruta URL el browser solicité: var http ~ require ("ht! var url = require (*url| fonction iniciar() { function onkequest (request, response) { var pathname = url.parse (request url) -pathnane; console.log("Peticién para " + pathname +" response .weiteliead (200, ("Content—Type": response.write("Hola Mundo") ; response.end(); ) http. createserver (onRequest) -Listen (8883) console-log ("Servidor Iniciado.); , exporte-iniciar = inieiar? Muy Bien. Nuestra aplicacién puede ahora distinguir peticiones basadas en la ruta URL requerida - esto nos permite mapear peticiones hacia nuestro manipuladores de peticiones, basandonos en la ruta URL usando nuestro (pronto a ser escrito) router. Luego, podemos construir nuestra aplicacién en una forma REST (N. del T.: RESTful way en Inglés), ya que ahora podemos implementar una interfaz que sigue los principios que guian a la Identificacién de Recursos (ve por favor el articulo de Wikipedia acerca de la Transferencia del Estado Representacional para informacién de trasfondo. En el contexto de nuestra aplicacién, esto significa simplemente que seremos capaces de tener peticiones para las URLs /iniciar y /subir manejadas por partes diferentes de nuestro cédigo. Veremos pronto como todo esto encaja. OK, es hora de escribir nuestro router. Vamos a crear un nuevo archivo llamado router.js, con el siguiente contenido: function route (pathname) { console.1og("A punto de rutear una peticion para " + pathname) ; ) exporta.route - router Por supuesto, este cddigo no esta haciendo nada, pero eso esta bien por ahora. Empecemos a ver como vamos a encajar este router con nuestro servidor antes de poner mas légica en el router. Nuestro servidor HTTP necesita saber y hacer uso de nuestro router. Podemos escribir directamente esta dependencia a nuestro servidor, pero como hemos aprendido de la manera dificil en nuestras experiencias, vamos a acoplar de manera débil (loose coupling en Inglés) al router y su servidor via inyeccién por dependencia. Para una referencia de fondo, leer el Articulo de Martin Fowler (en Inglés). Primero extendamos nuestra funcién iniciar() de manera de permitirnos pasar la funcién de ruteo a ser usada como parametro: var http = require ("ne var url ~ require ("url"); fonction iniciar (route) ( function onRequest (request, response) [ var pathname = url.parse (request url) -pathnane; consols.log(*Peticion para " + pathname +" recibida."); route (pathname) ; response.uriteHead(200, ("Content-Type" response.write("Hola Mundo") ; response.end() ) weext/ntmi"}): http. createserver (onRequest) -Listen (8888) + console-log ("Servidor Iniciado."); ' exports-iniciar = iniciary Y extendamos nuestro index.js adecuadamente, esto es, inyectando la funcién de ruteo de nuestro router en el servidor: var server = requize(" var router ~ requize(". server. iniciar(router-reute) ; Nuevamente , estamos pasando una funcidn como pardmetros, pero esto ya no es una novedad para nosotros. Si arrancamos nuestra aplicacion ahora (node index.js como siempre), y hacemos una peticién para una URL, puedes ver ahora por las respuestas de la aplicacion que nuestro servidor HTTP hace uso de nuestro router y le entrega el nombre de ruta requerido: bash$ node index.j2 Peticion para /foo recibida. A punto de rutear una peticion para /foo He omitido la molesta respuesta de la peticién para /favicon.ico Ejecucion en el reino de los verbos ¢Puedo divagar un vez mas por un momento y hablar acerca de la programacién funcional de nuevo? Pasar funciones no es sélo una consideracién técnica. Con respecto al disefio de software, esto es casi filoséfico. Tan solo piensa en ello: en nuestro archivo de index, podriamos haber entregado el objeto router al servidor, y el servidor hubiese llamado a la funcién route de este objeto. De esta manera, podriamos haber pasado una cosa, y el servidor hubiese usado esa cosa para hacer algo. Oye, "Cosa Router", éPodrias por favor rutear esto por mi? Pero el servidor no necesita la cosa. Sélo necesita hacer algo, y para que algo se haga, no necesitas cosas para nada, sdlo necesitas acciones. No necesitas sustantivos, sino que necesitas verbos. Entender este cambio de mentalidad fundamental que esta en el nticleo de esta idea es lo que realmente me hizo entender la programacion funcional. Y lo entendi mientras leia la obra maestra de Steve Yegge (en Inglés) Ejecucién en el Reino de los Sustantivos. Anda, léela, por favor. Es uno de los mejores articulos relacionados con el software que haya tenido el placer de encontrar. Ruteando a los verdaderos manipuladores de peticiones Volviendo al tema. Nuestro servidor HTTP y nuestro router de peticiones son ahora los mejores amigos y conversan entre ellos, tal y como pretendimos. Por supuesto, esto no es suficiente, "Rutear" significa que nosotros queremos manipular las peticiones a distintas URLs de manera, diferente. Nos gustaria tener la "logicas de negocios" para peticiones de /inicio manejadas en otra funcidn, distinta a la que maneja las peticiones para /subir. Por ahora, el ruteo "termina" en el router, y el router no es el lugar donde se esta "haciendo algo" con las peticiones, ya que esto no escalaria bien una vez que nuestra aplicacién se haga mas compleja. Llamemos a estas funciones, donde las peticiones estén siendo ruteadas, manipuladores de peticiones (6 request handlers). Y procedamos con éstos ahora, porque, a menos que no los tengamos en su lugar, no hay tiene mucho sentido en hacer nada con el router por ahora. Nueva parte de la aplicacion, significa nuevo médulo - no creo que haya sorpresa acd. Creemos un médulo llamado requestHandlers (N. del T.: por manipuladores de peticién), agreguemos un funcién de ubicacién para cada manipulador de peticin, y exportemos estos como métodos para el médulo: function inteiert) { console.log("Manipulador de peticién ‘iniciar’ ha sido Lamado.")7 , function subir() ¢ console.log("Manipulador de peticién ‘subir’ ha sido Llanado."); ) exports-iniciar = iniciar: exports /subir = cubiry Esto nos permitira atar los manipuladores de peticién al router, dandole a nuestro router algo que rutear. Llegado a este punto, necesitamos tomar una decisién: éIngresaremos las rutas del médulo requestHandlers dentro del cédigo del router (hard-coding), o queremos algo mas de dependencia por inyeccién? Aunque en la dependencia por inyeccién, como cualquier otro patron, no deberfa ser usada simplemente por usarla, en este caso tiene sentido acoplar el router débilmente a sus manipuladores de peticién, asi, de esta manera hacemos que el router sea reutilizable. Esto significa que necesitamos pasar los manipuladores de peticién desde nuestro server al router, pero esto se siente equivocado, dado que, éPor qué tenemos que hacer el camino largo y entregar los manipuladores desde el archivo principal al servidor y de ahi al router? ¢Cémo vamos a pasarlos? Ahora tenemos sdlo dos manipuladores, pero en una aplicaci6n real, este nimero se va a incrementar y variar, y nosotros no queremos estar a cada momento mapeando peticiones a manipuladores cada vez que una nueva URL o manipulador de peticion sea agregado. Y si tenemos un cédigo del tipo if peticion == then llama manipulador y en el router, esto se pondria cada vez mas feo. ¢Un nimero variable de items, cada uno de ellos mapeados a un string? (en este caso la URL requerida) Bueno, esto suena como que un array asociativo haria el truco. Bueno, este descubrimiento es obscurecido por el hecho que JavaScript no provee arrays asociativos - co si? !Resulta que lo que necesitamos usar son objetos si necesitamos un array asociativo! Una buena introduccién a esto esta (en Inglés) en http://msdn.microsoft.com/en-us/magazine/cc163419.aspx, Déjame citarte la parte relevante: En C++ 0 C#, cuando hablamos acerca de objetos, nos estamos refiriendo a instancias de clases de estructuras. Los objetos tienen distintas propiedades y métodos, dependiendo en las plantillas (esto es, las clases) desde donde éstos sean instanciados. Este no es el caso con los objetos de JavaScript. En JavaScript, los objetos son sélo colecciones de pares nombre/valor - piensa en un objeto JavaScript como en un diccionario con llaves de string. Si los objetos JavaScript son sdlo colecciones de pares nombre/valor, éCémo pueden entonces tener métodos? Bueno, los valores pueden ser strings, nimeros, etc... iO Funciones! OK, ahora, volviendo finalmente al cédigo. Hemos decidido que queremos pasar la lista de requestHandlers (manipuladores de peticién) como un objeto, y para lograr este acoplamiento débil, necesitamos usar la técnica de inyectar este objeto en la route() (ruta). Empecemos con poner el objeto en nuestro archivo principal indexjs: var server = require("./server"); var router ~ requize("./zoute: var requestHandlers = require("./requestHandlers") ; requestHandlers.iniciar; handle[*/subir"] = requestHandlers.subir; server. iniciar(router-route, handle); (N. del T.: Se Opta por dejar los verbos en Inglés ‘route’ para rutear y ‘handle’ para manipular). Aunque handle es mas una "cosa" (una coleccién de manipuladores de peticién), propongo que lo nombremos como un verbo, ya que esto resultara en una expresiOn fluida en nuestro router, como veremos a continuacién: Como puedes ver, es realmente simple mapear diferentes URLs al mismo manipulador de peticiones: Mediante la adicién de un par _ Ilave/valor de y requestHandlers.iniciar, podemos expresar en una forma agradable y limpia que no sdlo peticiones a /start, sino que también peticiones a / pueden ser manejadas por el manipulador inicio. Después de definir nuestro objeto, se lo pasamos al servidor como un pardmetro adicional. Modifiquemos nuestro server.js para hacer uso de este: var http = require ("heep* var url ~ require (*uzi"): function iniciar(route, handle) ( function onRequest (request, response) ( var pathname = url.parse (request .url) -pathnamo; console.log("Peticion para " + pathname + " recibida."); route (handle, pathname) + response.writetiead (200, ("content-Type": "text/html"}) ; response.write ("Hola Mundo"); response.end(); , http.createserver (onRequest) -1isten(8888) ; conale.log("Sexvidor Iniciada."): , exporte-iniciar ~ iniciar? Lo que hacemos aqui, es chequear si un manipulador de peticiones para una ruta dada existe, y si es asi, simplemente llamamos a la funci6n adecuada. Dado que podemos acceder a nuestras funciones manipuladoras de peticién desde nuestro objeto de la misma manera que hubiésemos podido acceder a un elemento de un array asociativo, es que tenemos Ja expresién fluida handle{[pathname]Q; de la que hablé antes, que en otras palabras es: "Por favor, handle (maneja) este(a) pathname (ruta)". Bien, iEsto es todo lo que necesitamos para atar servidor, router y manipuladores de peticiones juntos! Una vez que arranquemos nuestra aplicacién y hagamos una peticién en nuestro browser de http://localhost:8888/iniciar, vamos a probar que el manipulador de peticién correcto fue, de hecho, llamado: Servidor Inicsads. Peticion para /iniciar recibida. A punto de rutear una peticién para /iniciar Manipulador de peticion ‘iniciar' ha sido lamado. Haciendo que los Manipuladores de Peticiones respondan Muy bien. Ahora, si tan solo los manipuladores de peticion pudieran enviar algo de vuelta al browser, esto seria mucho mejor, écierto? Recuerda, que el "Hola Mundo" que tu browser despliega ante una peticién de una pagina, atin viene desde la funcién onRequest en nuestro archivo server.js. "Manipular Peticiones" no significa otra cosa que "Responder a las Peticiones" después de todo, asi que necesitamos empoderar a nuestros manipuladores de peticiones para hablar con el browser de la misma manera que la funcién onRequest lo hace. éCémo no se debe hacer esto? La aproximacién directa que nosotros - desarrolladores con un trasfondo en PHP o Ruby - quisiéramos seguir es de hecho conducente a errores: trabaja de manera espectacular al principio y parece tener mucho sentido, y de pronto, las cosas se arruinan en el momento menos esperado. A lo que me refiero con "aproximacién directa" es esto: hacer que los manipuladores de peticién retornen - return() - el contenido que ellos quieran desplegar al usuario, y luego, enviar esta data de respuesta en la funcién onRequest de vuelta al usuario. Tan sdlo hagamos esto, y luego, veamos por qué esto no es tan buena idea. Empecemos con los manipuladores de peticion y hagamoslos retornar, lo que nosotros queremos desplegar en el browser. Necesitamos modificar requestHandlers,js a lo siguiente: fonction inietar() { console. log ("Manipulador de peticion 'iniciar' fue llamado.) return "Hola Iniciar"; ) function subir () ( console. log ("Manipulador de peticion 'gubir! fue 1lamado."); return “Hola Subir"; ) exports.iniciar ~ iniciar; exports-subir = subir; Bien. De todas maneras, el router necesita retornar al servidor lo que los manipuladores de petici6n le retornaron a él. Necesitamos entonces editar router.js de esta manera: fanction route (handle, pathname) { console.log("A punto de rutear una peticion para " + pathname); if (typeof handle(pathnane] ==~ ‘functicn') { return handle (pathname) () } else ( console.log ("Wo 2¢ encontro manipulador para " + pathname)? return "404 No Encontrado"? ) ' exports-route = routes Como puedes ver, nosotros también retornaremos algtin texto si la peticion no es ruteada. Por ultimo, pero no menos importante, necesitamos refactorizar nuestro servidor para hacerlo responder al browser con el contenido que los manipuladores de peticion Je retornaron via el router, transformando de esta manera a server.js en: var http ~ require ("http"); var url = require (*url"); fonction inicier (route, handle) { function onFequest (request, response) { var pathname = url.parse (request.url) -pathname; console.log("Peticion para " + pathname +" recibida response-writeHead(200, ("Content-Type": “text/htmi"}); var content ~ route (handle, pathname) response.write (content) + response vend() 7 1 hetp.createServer (onfequest) -Lizten (8888) console.log ("Servidor Iniciadt , exporte-iniciar = iniciary Si nosotros arrancamos nuestra aplicaci6n re-escrita, todo va a funcionar a las mil maravillas: hacerle una peticion a http://localhost:8888/iniciar resulta en "Hola Iniciar" siendo desplegado en el browser, hacerle una peticion a http://localhost:8888/subir nos da "Hola Subir", y la peticion a http://localhost:8888/foo produce "404 No Encontrado". OK, entonces ¢Por qué esto es un problema? La respuesta corta es: debido a que si uno de los manipuladores de peticién quisiera hacer uso de una operacién no-bloqueante (non- blocking) en el futuro, entonces esta configuracién, como la tenemos, seria problematica. Tomémosnos algtin tiempo para la respuesta larga. Bloqueante y No-Bloqueante Como se dijo, los problemas van a surgir cuando nosotros incluyamos operaciones __no-bloqueantes en los manipuladores de peticion. Pero hablemos acerca de las operaciones bloqueantes primero, luego, acerca de las operaciones no-bloqueantes. Y, en vez de intentar explicar que es lo que significa "ploqueante" y "no bloqueante", demostremos nosotros mismo que es lo que sucede su agregamos una operacion bloqueante a nuestros manipuladores de petici6n. Para hacer esto, modificaremos nuestro manipulador de peticion iniciar para hacer una espera de 10 segundos antes de retornar su string "Hola Iniciar". Ya que no existe tal cosa como sleep( en JavaScript, usaremos un hack ingenioso para ello. Por favor, modifica requestHandlers.js como sigue: fonction iniciar() { console.1og("Manipulador de peticion ‘iniciar’ fue llamado."); function sleep (milliseconds) [ 7/ obten 1a hora actual var startTime ~ new Date() .getTime(); 1/ atasea la cpu while (new Date() .getTime() < startTime + milliseconds); ? sleep (10000) + return Hola Iniciar": function subir() { conaole.log("Manipulador de peticion ‘subir! fue llemado."); return "Hola Subir"s ) exports .iniciar = iniciary exporte.subir ~ subir? Dejemos claros que es lo que esto hace: cuando la funcién retorna "Hola Iniciar". Cuando esta4 llamando a subir(, retorna inmediatamente, la misma manera que antes. (Por supuesto la idea es que te imagines que, en vez de dormir por 10 segundos, exista una operacién bloqueante verdadera en iniciar(Q, como algun tipo de calculo de largo aliento.) Veamos qué es lo que este cambio hace. Como siempre, necesitamos reiniciar nuestro servidor. Esta vez, te pido sigas un "protocolo" un poco mas complejo de manera de ver que sucede: Primero, abre dos ventanas de browser o tablas. En la primera ventana, por favor ingresa http://localhost:8888/iniciar en la barra de direcciones, pero no abras atin esta url! En la barra de direcciones de la segunda ventana de browser, ingresa http://localhost:8888/subir y, muevamente, no presiones enter todavia. Ahora, haz lo siguiente: presiona enter en la primera ventana ("/iniciar"), luego, rapidamente cambia a la segunda ventana ("/subir") y presiona enter, también. Lo que veremos sera lo siguiente: La URL /inicio toma 10 segundos en cargar, tal cual esperamos. Pero la URL /subir también toma 10 segundos para cargar, iAunque no hay definido un sleep) en el manipulador de_peticiones correspondiente! éPor qué? simple, porque inicio() contiene una operacion bloqueante. En otras palabras "Esta bloqueando el trabajo de cualquier otra cosa". He ahi el problema, porque, el dicho es: "En Node,js, todo corre en paralelo, excepto tu cédigo". Lo que eso significa es que Node.js puede manejar un montén de temas concurrentes, pero no lo hace dividiendo todo en hilos (threads) - de hecho, Node.js corre en un sdlo hilo. En vez de eso, lo hace ejecutando un loop de eventos, y nosotros, los desarrolladores podemos hacer uso de esto - Nosotros debemos evitar operaciones bloqueantes donde sea posible, y utilizar operaciones no-bloqueantes en su lugar. Lo que exec() hace, es que, ejecuta un comando de shell desde dentro de Node.js. En este ejemplo, vamos a usarlo para obtener una lista de todos los archivos del directorio en que nos encontramos ("Is -lah"), permitiéndonos desplegar esta lista en el browser de un usuario que este peticionando la URL /inicio. Lo que el cédigo hace es claro: crea una nueva variable content() (con el valor incial de "vacio"), ejecuta "Is -lah", llena la variable con el resultado, y lo retorna. Como siempre, arrancaremos nuestra aplicacién y visitaremos http://localhost:8888 /iniciar. Lo que carga una bella pagina que despliega el string "vacio". ¢Qué es lo que esta incorrecto aca? Bueno, como ya habran adivinado, exec() hace su magia de una manera no-bloqueante. Buena cosa esto, porque de esta manera podemos ejecutar operaciones de shell muy caras en ejecucién (como, por ejemplo, copiar archivos enormes o cosas similares) sin tener que forzar a nuestra aplicacién a detenerse como lo hizo la operacion sleep. (Si quieres probar esto, reemplaza "Is -lah" con una operacién mas cara como "find /"). Pero no estariamos muy felices si nuestra elegante aplicacion no bloqueante no desplegara algtin resultado, écierto?. Bueno, entonces, arreglémosla. Y mientras estamos en eso, tratemos de entender por qué la arquitectura actual no funciona. E] problema es que exec(), para poder trabajar de manera no- bloqueante, hace uso de una funcién de callback. En nuestro ejemplo, es una funcién andnima, la cual es pasada como el segundo parametro de la llamada a la funcion exec(): function (error, stdout, stderr) { content = stdout; , Y aqui yace la raiz de nuestro problema: nuestro cédigo es ejecutado de manera sincronica, lo que significa que inmediatamente después de llamar a exec(), Node.js continta ejecutando return content;. En este punto, content todavia esta vacio, dado el hecho que la funcién de callback pasada a exec() no ha sido atin llamada - porque exec() opera de manera asincrona. Ahora, "Is -lah" es una operacion sencilla y rapida (a menos, claro, que hayan millones de archivos en el directorio). Por lo que es relativamente necesario llamar al callback - pero de todas maneras esto sucede de manera asincrona. Esto se hace mas obvio al tratar con un comando mis costoso: "find /" se toma un minuto en mi maquina, pero si reemplazo "Is -lah" con "find /" en el manipulador de peticiones, yo recibo inmediatamente una respuesta HTTP cuando abro la URL /inicio - esta claro que exec() hace algo en el trasfondo, mientras que Node.js mismo continta con el flujo de la aplicacién, y podemos asumir que la funcién de callback que Je entregamos a exec() sera llamada sélo cuando el comando "find /" haya terminado de correr. Pero, éc6mo podemos alcanzar nuestra meta, la de mostrarle al usuario una lista de archivos del directorio actual? Bueno, después de aprender como no hacerlo, discutamos cémo hacer que nuestros manipuladores de_peticién respondan a los requerimientos del browser de la manera correcta. Respondiendo a los Manipuladores de Peticion con Operaciones No Bloqueantes Acabo de usar la frase "la manera correcta". Cosa peligrosa. Frecuentemente, no existe una tinica "manera correcta". Pero una posible solucién para esto, frecuente con Node.js es pasar funciones alrededor. Examinemos esto. Ahora mismo, nuestra aplicacién es capaz de transportar el contenido desde los manipuladores de peticién al servidor HTTP retornandolo hacia arriba a través de las capas de la aplicacién (manipulador de peticién -> router -> servidor). Nuestro nuevo enfoque es como sigue: en vez de llevar el contenido al servidor, llevaremos el servidor al contenido. Para ser mas precisos, inyectaremos el objeto response (respuesta) (desde nuestra funcién de callback de servidor onRequest()) a través de nuestro router a los manipuladores de peticién. Los manipuladores seran capaces de usar las funciones de este objeto para responder a las peticiones ellos mismos. Suficientes explicaciones, aqui hay una receta paso a paso de como cambiar nuestra aplicacién. Empecemos con nuestro servidor, server,js: var htep = require (* var url = require ("url function iniciar (route, handle) { fonction onRequest (request, response) { var pathnane ~ url.parse (request -url) -pathnane; console.log(*Peticion para " + pathname + " reckbida."); route (handle, pathname, response); hetp-createserver (onRequest) -Listen (8888) "ys console, log(*Servidor Iniciad: , exports iniciar = iniciary En vez de esperar un valor de respuesta desde la funcién route(), pasémosle un tercer pardmetro: nuestro objeto response. Es mas, removamos cualquier llamada a response desde el manipulador onRequest(), ya que ahora esperamos que route se haga cargo de esto. Ahora viene router.js: function route (handle, pathname, response) { console.log("A punts de rutear una peticion para " + pathname); Af (typeof handle [pathname] === ‘function') ¢ handle[pathnane] (response) ; else { console.log(*No hay manipulador de peticion para " + pathname); response -writeHead(404, ("Content-Type": "coxt/html")) 7 response-write("404 No Encontrado™) reaponse.end(); ) , exports. route = route; Mismo patrén: En vez de esperar que retorne un valor desde nuestros manipuladores de peticién, nosotros traspasamos el objeto response. Si no hay manipulador de peticién para utilizar, ahora nos hacemos cargo de responder con adecuados encabezado y cuerpo "404". Y por ultimo, pero no menos importante, modificamos a requestHandlers.js (el archivo de manipuladores de peticién). var exec ~ require("child process") .exec: function iniciar (response) ( console, log("Manipulador de peticin ‘iniciar* fue 1lamado."); ders) { "vext/html")) + exec("1e -1ah", function (error, stdout response. writettead(200, {*content-Type": response. write (stdout) + response.end()7 De ) function subir (response) { conzole.log("Manipulador de peticién ‘subir' fue Lamado.") -iteHead(200, ("Content-Type": "text/ntmi"}) ; ite("Hola Subir"); a)? exports -iniciar = iniciar: exports .subir = subiry Nuestras funciones manipuladoras necesitan aceptar el parametro de respuesta response, y de esta manera, hacer uso de él de manera de responder a la peticion directamente. El manipulador iniciar respondera con el callback andnimo exec(), y el manipulador subir replicara simplemente con "Hola Subir", pero esta vez, haciendo uso del objeto response. Si arrancamos nuestra aplicacién de nuevo (node index,js), esto deberia funcionar de acuerdo a lo esperado. Si quieres probar que la operacion cara dentro de /iniciar no bloqueara mas las peticiones para /subir que sean respondidas inmediatamente, entonces modifica tu archivo requestHandlers.js como sigue: var exec ~ require ("child process") .2xec; function iniciar (response) ( console.log("Manipulador de peticién 'iniciar’ fue llanado."); exec ("find /*, { timeout: 10000, maxBuffer: 2000041024 }, Function (error, stdout, stderr) [ response.writeHiead (200, {"Content-Type": "text/html")}) response.write (stdout); response.end()7 Me , fonction subix (response) { console.1og("Manipulador de peticién ‘subir’ fue Mamado."); response.writeHead (200, {"Content-Type": “text/html"}); response.write("Hola Subir"): response end () : exports-iniciar - iniciary exports subir = subiry Esto hard que las peticiones HTTP a http://localhost:8888 iniciar tomen al menos 10 segundos, pero, las peticiones a http://localhost:8888/subir sean respondidas inmediatamente, incluso si /iniciar todavia esta en proceso. Sirviendo algo util Hasta akera; ls que hemes heeks es tds simpaties ¥ Benits; pero no hemos creado aun valor para los clientes de nuestro sitio web ganador de premios. Nuestro servidor, router y manipuladores de peticién estan en su lugar, asi que ahora podemos empezar a agregar contenido a nuestro sitio que permitira a nuestros usuarios interactuar y andar a través de los casos de uso de elegir un archivo, subir este archivo, y ver el archivo subido en el browser. Por simplicidad asumiremos que sdlo los archivos de imagen van a ser subidos y desplegados a través de la aplicaci6n. OK, vedmoslo paso a paso, pero ahora, con la mayoria de las técnicas y principios de JavaScript explicadas, acelerémoslo un poco al mismo tiempo. Aqui, paso a paso significa a grandes rasgos dos pasos: vamos a ver primero como manejar peticiones POST entrantes (pero no subidas de archivos), y en un segundo paso, haremos uso de un modulo externo de Node.js para la manipulacion de subida de archivos. He escogido este alcance por dos razones: Primero, manejar peticiones POST basicas es relativamente simple con Node.js, pero atin nos ensefia lo suficiente para que valga la pena ejercitarlo. Segundo, manejar las subidas de archivos (i.e. peticiones POST multiparte) no es simple con Nodes, consecuentemente esta mas alla del alcance de este tutorial, pero el aprender a usar un modulo externo es una leccién en si misma que tiene sentido de ser incluida en un tutorial de principiantes. Manejando Peticiones POST Mantengamos esto ridiculamente simple: presentaremos un area de texto que pueda ser llenada por el usuario y luego enviada al servidor en una peticin POST. Una vez recibida y manipulada esta peticién, desplegaremos el contenido del area de texto. El HTML para el formulario de esta area de texto necesita ser servida por nuestro manipulador de peticién /iniciar, asi que agreguémoslo de _ inmediato. En el archivo requestHandlers.js: function inieiar (response) ( console.1og("Manipulador de peticiones ‘iniciar’ fue lamado."); var body = ‘'+ " chead>' + temeta http-equiv-"Content-Type" content—"eext/html charset-uTF-8" />"+ "+ * cbody>"+ ‘
!+ ext" rows="20" cols="60">'+ ‘'+ ‘ef form'+ ’ s' 'ceextaren nae: response -uriteliead ( response write (bod response vend() 7 eext/atmi"}) ¢ ' function subir (response) ( console.log("Manipulador de peticiones ‘subir’ fue llamado."); response.writetiead(200, ("Content-Type": "text/html")); response.write("Hola Subir"); response.end() ' exports-iniciar = iniciary exports-subir ~ aubir; Ahora, si esto no va a ganar los Webby Awards, entonces no se que podria. Haciendo la peticién http://localhost:8888 /iniciar en tu browser, deberias ver un formulario muy simple. Si no, entonces probablemente no has reiniciado la aplicacién. Te estoy escuchando: tener contenido de vista justo en el manipulador de peticién es feo. Sin embargo, he decidido no incluir ese nivel extra de abstraccién (esto es, separar la légica de vista y controlador) en este tutorial, ya que pienso que es no nos ensefia nada que valga la pena saber en el contexto de JavaScript o Node.js. Mejor usemos el espacio que queda en pantalla para un problema mas interesante, esto es, manipular la peticion POST que dara con nuestro manipulador de peticién /subir cuando el usuario envie este formulario. Ahora que nos estamos convirtiendo en "novicios expertos", ya no nos sorprende el hecho que manipular informacion de POST este hecho de una manera no bloqueante, mediante el uso de llamadas asincronas. Lo que tiene sentido, ya que las peticiones POST pueden ser potencialmente muy grandes - nada detiene al usuario de introducir texto que tenga muchos megabytes de tamafio. Manipular este gran volumen de informacién de una vez puede resultar en una operacion bloqueante. Para hacer el proceso completo no bloqueante. Node.js le entrega a nuestro cédigo la informacion POST en pequefios trozos con callbacks que son llamadas ante determinados eventos. Estos eventos son data (un nuevo trozo de informacién POST ha Ilegado) y end (todos los trozos han sido recibidos). Necesitamos decirle a Node.js que funciones llamar de vuelta cuando estos eventos ocurran. Esto es hecho agregando listeners (N. del T.: del verbo listen - escuchar) al objeto de peticién (request) que es pasado a nuestro callback onRequest cada vez que una peticién HTTP es recibida. Esto basicamente luce asi: request .addbistener("data", function (chunk) { 7/ fancion Lamada cuando un nuevo trozo (chunk) 7/ de informacion (data) es recibido. he request .addListener("end", function() ( 77 fancion Mamada cuando todos los trozos (chunks) 7/ de informacion (data) han sido recibidos. ne La pregunta que surge es dénde implementar ésta légica. Nosotros sdlo podemos acceder al objeto request en nuestro servidor - no se lo estamos pasando al router o a los manipuladores de peticién, como lo hicimos con el objeto response. En mi opinion, es un trabajo del servidor HTTP de darle a la aplicacién toda la informacién de una peticién que necesite para hacer su trabajo. Luego, sugiero que manejemos el procesamiento de la peticion de POST en el servidor mismo y pasemos la informacién final al router y a los manipuladores de peticion, los que luego decidiran que hacer con ésta. Entonces, la idea es poner los callbacks data y end en el servidor, recogiendo todo los trozos de informacién POST en el callback data, y llamando al router una vez recibido el evento end, mientras le entregamos los trozos de informacion recogidos al router, el que a su vez se los pasa a los manipuladores de peticion. Aqui vamos, empezando con server.js: var http = require ("http"); var url = require ("url function iniciar(route, handle) { function onRequest (request, response) ( var dataPosteada — var pathname ~ url.parse (request url) .pathname; console.log("Peticion para " + pathname + * recibida."); xequest .setEncoding ("utt8") ; request .addListener ("data", function (trozoPosteade) ( ‘dataPosteada += trozoPosteado; console.log("Recibide trozo POST '* + trogoPosteada +" De request .addListener("end", function() ( route(handle, pathnane, response, dataPosteada) : De , http.createserver (onReguest) -1isten (8888) ; console. log ("Servidor Iniciade") ; ) exports-iniciar ~ iniciar: Basicamente hicimos tres cosas aqui: primero, definimos que esperamos que la codificacién de la informacién recibida sea UTF-8, agregamos un listener de eventos para el evento "data" el cual lena paso a paso nuestra variable dataPosteada cada vez que un nuevo trozo de informacién POST llega, y movemos la llamada desde nuestro router al callback del evento end de manera de asegurarnos que sélo sea llamado cuando toda la informacién POST sea reunida. Ademas, pasamos la informacién POST al router, ya que la vamos a necesitar en nuestros manipuladores de eventos. Agregar un logueo de consola cada vez que un trozo es recibido es una mala idea para cédigo de produccién (megabytes de informacién POST, érecuerdan?, pero tiene sentido para que veamos que pasa. Mejoremos nuestra aplicacion. En la pagina /subir, desplegaremos el contenido recibido. Para hacer esto posible, necesitamos pasar la dataPosteada a los manipuladores de peticion, en router,js. function route (handle, pathname, response, postData) { console.tog("A punto de rutear una peticion para " + pathname); if (typeof handle[pathname] ~-- ‘functicn') { handle (pathnane] (response, postData) ; } else ¢ console.log(*No se ha encontrado manipulador para " + pathname); response-uriteead (404, ("Content-Typa": "rext/atml"}); response.write("404 No encontrado") ; response -end() exports route = router Y en requestHandlers.js, incluimos la informacién de nuestro manipulador de peticion subir: fonction iniciar (response, postData) ( console-log("Manipulador de Feticion ‘iniciar' fue 1lamado."); var body = ‘chtml>'+ "ehead>'+ ‘"+ post >" + cole-"60">'+ ‘'+ *e/form'+ *cfbody>"+ vepnemi>'y response-writeHead (200, {"Content-Type": “text/html"}) ; response.write (body) 7 response.end() ; , function subir (response, dataPosteada) { console-1og("Manipulador de Peticion ‘subir’ fue Lamado."); response.writeHead (200, {"Content-Type": “text/html"}); response.write("Tu enviaste: " + dataPosteada) ; response.end() exports-iniciar - iniciar; exports.subir ~ subir; Eso es, ahora somos capaces de recibir informacién POST y usarla en nuestros manipuladores de peticién. Una ultima cosa para este tema: lo que le estamos pasando al router y los manipuladores de peticion es el cuerpo (body) de nuestra peticién POST. Probablemente necesitemos consumir los campos individuales que conforman la informacién POST, en este caso, el valor del campo text. Nosotros ya hemos leido acerca del médulo querystring, el que nos ayuda con esto: var querystring = require ("querystring") : fonction iniciar (response, postData) { console.1og("Nanipulador de peticion ‘inicio' fue Llamado."); var body = "" *ehead>! + ‘emata http-equiv-"content-Type" content="caxt/html; charset=UTE-8" />"+ *'+ ebody>!+ ‘'+ *'+ vepntml>"; response.writeHead (200, ("Content-Type": "text/html"}) ; Eesponce-weive (body) 7 zeaponse.end() , fonction subir (response, datePosteada) { console.1og("Manipulador de peticion ‘subir’ fue Llemado response -writeHead (200, ("Content-Type": “text/html")) ; response-write("Tu enviaste el texto: : "+ querystring. pares (dataPoateada) ["text"]) response.end(); , exporte-iniciar = iniciar? exporte subir = subizy Bueno, para un tutorial de principiantes, esto es todo lo que diremos acerca de la informacion POST. La proxima vez, hablaremos sobre como usar el excelente médulo node-formidable para permitirnos lograr nuestro caso de uso final: subir y desplegar imagenes. Sponsored Links Play this Game for 1 Minute and The 10 Most Expensive Colleges If You Eat 3 Dates Everyday For 1 ‘800 why everyone is addicted —_In The World Week This Is What Happens To 4 SS SS 10 Countries It’s Super Easy To 7 Things That Happen To Your _§ Herbs to Get Rid of Joint Pain Immigrate To If You Live In Cuba Body I You Eat A Banana Every ison eo Retcaton Trot Day 15Comments The Node Beginner Book @ Login ~ Recommend 12 Sort by Newest ~ Join the discussion. Losin on sion uP wri orsaus @ 9600 Leo Santana * 5 months ago yy la segunda parte pa’ cuando ? :/ > |v + Roply » Share> Proximity Costa Rica 9 months 299 Las nuevas tecnologias aportan varias ventajas para las empresas. En el caso de Node.JS, al ser de las Uittimas tecnologias, los desarrolladores tienen muchos beneficios para desarrollar herramientas y aplicaciones web. Otras buenas razones por las que recomendamos el uso de Node JS son: velocidad, universalidad de JavaScript, puede ser utilizado como servidor proxy, mayor productividad, efectividad en la creacién de herramientas con NPM, mejor alojamiento web. https: siwvaw.proximityer.com... 1A. v - Reply Ricardo Dolinski Garrido a yearag0, Gracias, ‘aqui inicio nodejs tp: www quickcode cote. 1 |v - Roply » Share» Abelard Leén Gonzdlez 2 year 292 Hola: Gracias por et tutorial de iniciacion a Node js, un seneillo ejemplo pero un gran paso para espantar mi (total) Ignorancia acerca de esta tecnologia. Lo tinico es que falta el ejemplo del cddigo exec; lo comentas pero no lo pones. Un lapsus que a buen seguro ppodrés corregir cuando puedas. Gracias. :) A |v + Reply» Share» [Abelardo Leén Gonzalez > abeurdo Leon Gonzster a year ago Ya diréis dénde puedo encontrar su continuacion. ;) Av» Reply » Share» Pablo Dante year a90 HOla ‘Me surge otro inconveniente... si alguien me puede ayudar. “Error: Error: Command failed: Is -lan "Is" no se reconoce como un comando interno o externo, programa 0 archivo por lotes ejecutable.” ‘Y me pasa en esta linea de cédigo: ‘exec("ls lah", function (error, stdout, stderr) { response.writeHead(200, "Content-Type": "textintm!”)}; response. write(stdout); response.end{), w Saludos + Reply » Share Ulises Hybrid Reactor > Pabio Dante a year ag0 <= Intenta agregando al path de windows el app Cygwin-Terminal 53.0 v « Reply » Share» Pe ‘Yuber Vasquez + Pablo Dante year ago Hola, ‘que yo sepa Is viene solo para linux, si usas windows tendrias que usar el comando dir. sds 1A + Reply» Share> lo Dante «year ago Hola foro. ‘Tengo el siguiente problema, me arroja siempre el mismo mensaje "404 No encontrado" y el Log me arroja lo sigui lente: Servidor Iniciado Peticion: fnicar..recibida. ‘A punto de rutear una peticion para [object Object] No se encontro manipulador para [object Object] ‘A punto de rutear una peticion para [object Object] 'No se encontro manipulador para [object Object] Peticion:/favicon io ..recbida. ‘A punto de rutear una peticion para [object Object] 'No se encontro manipulador para [object Object] ‘A punto de rutear una peticion para [abject Object] No se encontro manipulador para [object Object] ‘Aiguien me puede guiar para ver qué estoy haciendo mal!? Desde ya muchas gracias v= Reply » Share» Yuber Vasquez # Patio Dane yearago HOla, fijate si esta bien declara tu variable handle en index, luego fijate en server js que estes enviando correctamente los parametros route(handle,pathname, response); y finalmente en routers if (ypeot handlefpathname] === uncon)

También podría gustarte