Hola comunidad. Luego de una espera interminable y de innumerables obstáculos para tratar de sacar al aire el blog BlackHat, al final me he dado por vencido y me he incorporado a apoyar este magnifico blog, espero mis entradas sean del agrado de todos.
Para mi primer artículo me gustaría compartir con ustedes un poco acerca de NodeJS, una tecnología que en estos momentos está bastante de moda y que se define a si misma como una biblioteca y entorno de ejecución de E/S dirigida por eventos y por lo tanto asíncrona que se ejecuta sobre el intérprete de JavaScript creado por Google V8, y para ello les explicaré como crear un servidor web, similar a Apache o a cualquier otro en sus funciones básicas.
Bueno, manos a la obra. Para ello es necesario preguntarnos qué características vamos a incorporar a nuestro servidor web. Por ser un servidor sencillo y de ejemplo solo voy a explicar como servir ficheros estáticos (HTML, JS, PNG, GIF, JPEG, etc), como satisfacer la solicitud del favicon y como lograr una configuración flexible del servidor.
Para comenzar creemos un archivo basicServer.js con el siguiente código:
var http = require('http'); var url = require('url'); exports.createServer = function() { var htserver = http.createServer(function(req, res) { req.basicServer = { urlparsed: url.parse(req.url, true) }; processHeaders(req, res); dispatchToContainer(htserver, req, res); }); htserver.basicServer = { containers: [] }; htserver.addContainer = function(host, path, module, options) { if (lookupContainer(htserver, host, path) !== undefined) { throw new Error("Already mapped " + host + "/" + path); } htserver.basicServer.containers.push({ host: host, path: path, module: module, options: options }); return this; } htserver.useFavIcon = function(host, path) { return this.addContainer(host, "/favicon.ico", require('./faviconHandler'), { iconPath: path }); } htserver.docroot = function(host, path, rootPath) { return this.addContainer(host, path, require('./staticHandler'), { docroot: rootPath }); } return htserver; } var lookupContainer = function(htserver, host, path) { for (var i = 0; i < htserver.basicServer.containers.length; i++) { var container = htserver.basicServer.containers[i]; var hostMatches = host.toLowerCase().match(container.host); var pathMatches = path.match(container.path); if (hostMatches !== null && pathMatches !== null) { return { container: container, host: hostMatches, path: pathMatches }; } } return undefined; }
Lo que tenemos acá es el núcleo de nuestro servidor, que básicamente crea y retorna un objeto HTTP Server, luego de añadirle algunas funcionalidades extras. La estrategia que se persigue es, primero, añadir información utilitaria al objeto request (en la función proccessHeaders que detallaré más adelante) y luego enviar la petición al manejador adecuado (en la función dispatchToContainer que estaremos viendo más adelante). La otra cosa que hacemos aquí es añadir tres funciones para gestionar una lista de contenedores, la primera agrega un contenedor al servidor (addContainer), la segunda se encarga de configurar el Favicon (useFavIcon) y la última se utiliza para manejar los archivos estáticos (docroot).
Hay varias funciones que usamos antes, pero no hemos mirado todavía. La primera de ellas, lookupContainer, mira en la matriz contenedores buscando un contenedor que coincida con el el host y la ruta de la petición HTTP. Ésta es una exploración bastante sencilla en la que si se encuentra uno es devuelto o de lo contrario se devuelve indefinido.
Otra de las funciones de la que hemos hablado es la que procesa las cabeceras, proccessHeaders, la cual escanea el arreglo contenido en req.headers buscando encabezados de las cookies y de host, ya que ambos son útiles para el despacho de las peticiones. Esta función se llama en cada petición. Acá les dejo el código de esta función, el cual debe incorporarse al final del archivo basicServer.js creado al principio.
var processHeaders = function(req, res) { req.basicServer.cookies = []; var keys = Object.keys(req.headers); for (var i = 0, l = keys.length; i < l; i++) { var hname = keys[i]; var hval = req.headers[hname]; if (hname.toLowerCase() === "host") { req.basicServer.host = hval; } if (hname.toLowerCase() === "cookie") { req.basicServer.cookies.push(hval); } } }
La última de las funciones es dispatchToContainer, la cual hace lo que su nombre indica, envía las peticiones al contenedor correspondiente si este existe, sino devuelve una página 404. Es en esta función donde el servidor llama al manejador que debe procesar la petición. Esta función, al igual que la anterior, se llama en cada petición. Al igual que en el caso anterior deben agregar el código de esta función al archivo basicServer.js.
var dispatchToContainer = function(htserver, req, res) { var container = lookupContainer(htserver, req.basicServer.host, req.basicServer.urlparsed.pathname); if (container !== undefined) { req.basicServer.hostMatches = container.host; req.basicServer.pathMatches = container.path; req.basicServer.container = container.container; container.container.module.handle(req, res); } else { res.writeHead(404, { 'Content-Type': 'text/plain' }); res.end("no handler found for " + req.host + "/" + req.urlparsed.path); } }
Ahora pasemos a ver en detalle los dos controladores que utilizamos en la confección de nuestro servidor. El primero de ellos, faviconHandler.js, es el encargado de responder a las peticiones favicon, y se incorpora a nuestro servidor cuando se llama a la función useFavIcon, acá el código:
var fs = require('fs'); var mime = require('mime'); exports.handle = function(req, res) { if (req.method !== "GET") { res.writeHead(404, {'Content-Type': 'text/plain'}); res.end("invalid method " + req.method); } else if (req.basicServer.container.options.iconPath !== undefined) { fs.readFile(req.basicServer.container.options.iconPath, function(err, buf) { if (err) { res.writeHead(500, {'Content-Type': 'text/plain'}); res.end(req.basicServer.container.options.iconPath + " not found"); } else { res.writeHead(200, { 'Content-Type': mime.lookup(req.basicServer.container.options.iconPath), 'Content-Length': buf.length }); res.end(buf); } }); } else { res.writeHead(404, {'Content-Type': 'text/plain'}); res.end("no favicon"); } }
El módulo MIME se utiliza en este controlador para determinar el tipo del archivo de icono suministrado. Un favicon puede ser cualquier tipo de imagen y hay que informar al navegador web del tipo de imagen que se envía. El otro controlador es el encargado de responder cuando se solicitan ficheros estáticos, a este le llamaremos staticHandler.js y contiene el siguiente código:
var fs = require('fs'); var mime = require('mime'); var sys = require('sys'); exports.handle = function(req, res) { if (req.method !== "GET") { res.writeHead(404, {'Content-Type': 'text/plain'}); res.end("invalid method " + req.method); } else { var fname = req.basicServer.container.options.docroot + req.basicServer.urlparsed.pathname; if (fname.match(/\/$/)) fname += "index.html"; fs.stat(fname, function(err, stats) { if (err) { res.writeHead(500, {'Content-Type': 'text/plain'}); res.end("file " + fname + " not found " + err); } else { fs.readFile(fname, function(err, buf) { if (err) { res.writeHead(500, {'Content-Type': 'text/plain'}); res.end("file " + fname + " not readable " + err); } else { res.writeHead(200, { 'Content-Type': mime.lookup(fname), 'Content-Length': buf.length }); res.end(buf); } }); } }); } }
Acá también se utiliza el módulo MIME para determinar la cabecera Content-Type correcta. Se requiere MIME para que el navegador web puede interpretar los datos correctamente. Un caso especial es cuando la URL solicitada termina en /, entonces el manejador cambia la petición añadiendo index.html.
Bueno, luego de la confección de nuestro servidor web básico solo nos resta utilizarlo, para ello creemos un archivo server.js e incorporemos el siguiente código:
var port = 8888; var server = require('./basicserver.js').createServer(); server.useFavIcon("localhost", "./www/favicon.png"); server.docroot("localhost", "/", "./www"); server.listen(port);
En este archivo se crea el servidor, se le configura la ruta desde donde debe cargar el favicon, y se le notifica cual es el directorio que debe publicar, en mi caso cree una carpeta en el directorio de mi servidor llamada www en la cual pondré los archivos estáticos que deseo publicar. Por último llamamos a la función listen del servidor y le pasamos el puerto de escucha. Solo nos resta levantar el servidor con el comando node server.js e ir en el navegador a la dirección http://localhost:8888/, para poder disfrutar de nuestro servidor en funcionamiento.
Al final de la entrada les dejo un comprimido con los archivos que hemos visto hoy. Espero les sea útil e interesante este artículo, en otras entradas seguiré adicionando funcionalidades al servidor para con ello seguir aprendiendo de NodeJS y sus potencialidades. Hasta la próxima !!!
BasicServer (291 descargas)
Comentarios ( 28 )
Muy bueno el artículo, tiene nivel para ser publicado en mycyberacademy.com.
Muy buen artículo. Felicidades y bienvenido al equipo!!! 😀
Mu gustaría que me aclararan una duda, un poco relacionado con esto. Y es que quiero crear un cliente en nodejs que se pueda interactuar con servidor web (ejemplo Apache). Es decir en vez de crear un servidor como lo hecho en este artículo, crear un cliente web.
¿Me podrías poner un ejemplo de cómo se haría?
Gracias por los comentarios, espero poder estar a la altura de este gran colectivo en cada uno de mis entradas.
@Roniel López Alvarez
Muchas felicidades, es un gran post. Ya ha tenido varios favoritos y retwits en twitter.
Pregunto soy neofito en esto que acabas de poner pero esto corre en windows. si asi es como se hace
@zero1
Esto corre tanto en Windows como en Linux siempre que tengas la version de NodeJS en tu sistema.
Probado en Windows 7 64 bits y en Linux Mint 17 (Qiana) y en ambos funciona a las mil maravillas.
Excelente artículo. Este tipo de servidores que manejan peticiones asincrónicamente son el futuro de Internet. La gente de Apache tienen que ponerse “pa’ la cosa” si no quieren que los tumben del puesto número 1.
@Dariem
La gente de Apache tambien deben preocuparse por Nginx tambien, debido a que ha demostrado con el paso del tiempo que su modelo de arquitectura basado en workers es mas eficientes de los basados en hilos , incluso en las nuevas versiones de Apache, sobre todo a partir de la 2.4 estan preparando algo similar.
Con respecto a NodeJS, estoy completamente de acuerdo contigo, no por gusto empresas como Joyent, StrongLoop e incluso Red Hat le han puesto tanto empeño en su desarrollo y auge. Incluso LinkedIn ha migrado sus aplicaciones móviles a NodeJS (la parte del backend) por su desempeño. En el blog de desarrollo de LinkedIn pueden leer algo al respecto.
@Marcos Ortiz
De hecho lo que escribí lo hice pensando sobre todo en Nginx, y es que tanto este como Node.js utilizan modelos asincrónicos para recibir y mandar a procesar las peticiones, en eso se parecen y por eso es que se comen a Apache por una pata cuando se trata de escalabilidad. Ambos proyectos usan modelos que tratan de impedir a toda costa los bloqueos a la hora de realizar I/O, y eso es algo que Apache tiene que superar. Veremos si en próximas versiones se pone a la altura.
Exactamente. Esperemos que se superen de verdad !!!
Acabo de instalar Node desde los repos de Debian testing, pero resulta que el comando node no existe, exite uno que se llama nodejs, esté será el que corre el servidor, porque he intentado con un ejemplo y no funciona???
@Skywalker
Mira el comando es nodejs, pero puedes hacer sudo ln -s /usr/bin/nodejs /usr/bin/node, para poder utilizar cualquiera de los dos de forma indistinta. Esto es lo que se acostumbra a hacer.
Offtopic
Roniel López Alvarez dijo:
“Luego de una espera interminable y de innumerables obstáculos para tratar de sacar al aire el blog BlackHat, al final me he dado por vencido”
Y como todo bueno proyecto, muere y pasa a las paginas de la historia, una verdadera lastima, demasiado obstaculos y muchos otros factores conllevaron a esto, mi reconocimiento a los que de una u otra forma lo apoyaron y lo hicieron posible, en especial a Alien, su creador
Muy bueno, pero javascript sigue sin convencerme para hacer cosas fuera de la web, nodejs es bueno, pero en mi opinión no compite ni en estabilidad ni en rendimiento, al menos por ahora, con otras tecnologías. Soy de la opinión de que esta adopción masiva de javascript fuera de los campos para lo que fue concebido, a la larga va a traer mas problemas que ventajas.
Saludos.
@Cesar
En ese sentido difiero contigo, yo pensaba lo mismo de JavaScript hasta que comencé a utilizarlo de lleno, desde ese momento mi forma de verlo cambió radicalmente. NodeJs es en estabilidad y rendimiento muy muy bueno siempre que se utiliza para lo que fue diseñado, prueba de eso son las múltiples migraciones a esa tecnología por parte de grandes compañías. Te sugiero probarlo de lleno y después me dices.
@Rhack
Has probado hacer una aplicacion web con Node.js con un acceso a datos intensivo a nivel de aplicacion, o una transaccion con bastantes consultas a BD? Si lo intentas moriras.
@Rhack
Bueno, no me gusta hablar de una tecnología sin primero haberla probado, en los últimos años he usado javascript incluso mas de lo que me hubiera gustado, desde aplicaciones web, hasta aplicaciones para móviles con Cordova.
Nodejs es productivo, eso si, se aprovecha el conocimiento previo del javascript, una tecnología que casi todo el mundo domina, pero eso también es un problema, quizá uno de sus mayores problemas.
Voy a comenzar por el propio núcleo de nodejs, es muy rápido, pero brinda muy poca escalabilidad, cualquier código que conlleve mucho trabajo del procesador, puede bloquear todo el sistema, haciendo que la aplicación no responda bien, y provocando unos buenos dolores de cabeza, como todos los procesos ocurren en un solo hilo, en muchas ocasiones se termina gastando mas tiempo solucionando como manejar la concurrencia, que en las funcionalidades de la aplicación, eso lleva al tema de las primitivas de concurrencia, al ser javascript un lenguaje con otro propósito (estar del lado del cliente, no del servidor), no tiene ninguna que funcione decentemente, provocando a la larga, código muy difícil de entender y mucha duplicación.
Otro problema es que hereda de javascript muchos aspectos, tanto semánticos y sintácticos como culturales que están adaptados a la web, como el uso de cadenas (string) en vez de estructuras de datos apropiadas, si bien esto es mas un problema de mentalidad de los desarrolladores que de la tecnología en si, provoca bastantes errores y problemas de compatibilidad incluso en el propio núcleo de nodejs.
Mi opinión es que javascript tiene una razón de ser, y al comenzar a usarse con otros propósitos, solo provocará que se vayan agregando funcionalidades y características (en forma de remiendos) que al final provocarán que se siga quedando corto del lado del servidor, y que esté demasiado “gordo” para funcionar apropiadamente del lado del cliente.
Saludos.
@Cesar
No le heches la culpa a javascript, la culpa es de la arquitectura Event Driven que se queda corta.
Estan fritos, les recomiendo el libro Hands on NodeJs, y el que crea que javascript no sirve, que pruebe AngularJs.
Realmente si dices esto, es que no has probado a fondo NodeJS.
Las últimas versiones han demostrado que NodeJS está preparado para grandes aplicaciones de alta concurrencia. No por gusto empresas como Joyent, StrongLoop, Red Hat (NodeJS tiene soporte en OpenShift, la PaaS empresarial de la compañía) y muchas otras le están brindando soporte a nivel empresarial, porque saben el potencial que tiene la tecnología.
Ejemplos de uso de NodeJS para grandes aplicaciones hay varios, pero creo que el que mejor puede ilustrar eso es la reescritura de la aplicación móvil de LinkedIn, los cuales migraron de Ruby on Rails en el backend hacia NodeJS. Puedes preguntarle a Kiran Prasad, Director of Engineering, Mobile en LinkedIn. LinkedIn usa los servicios de Joyent Cloud con la versión Enterprise de NodeJS.
Otros que usan NodeJS: Sprint, eBay, Microsoft, Voxer, Yahoo!, Sabre, Social Game Universe, StackMob, Gilt Group.
¿En serio? Ojalá y tuviéramos el video del fundador de NodeJS donde dio una explicación de Event-Driven Architecture y el por qué es lo más lógico para usar en este tipo de sistemas. Todo estos conceptos están basados en el problema que planteó Dan Kegel llamado C10K, así que no creo para nada que se quede corta.
Si quieren les envío una presentación acerca de los mecanismos internos de Nginx, que usa la misma arquitectura y hay una buena explicación de la misma.
@Marcos Ortiz
Concuerdo completamente contigo, esta diseñado para alta concurrencia, pero se vuelve un problema cuando hay peticiones que requieren bastante uso del CPU (cálculos grandes, múltiples consultas a BD etc…) porque es muy difícil gestionarla (en buena medida por el javascript), por ejemplo, LinkedIn usa nodejs para proveer la API JSON de su sitio y app para móviles, pero no para el backend de LinkedIn, nodejs actúa como una capa intermedia para hacer consultas (mediante RPC/REST, creo) al backend, aprovechando así las bondades de su arquitectura (la arquitectura de nodejs, que yo no he criticado), que le permite manejar las peticiones con un mejor uso recursos, pero solo como una capa, ya que esa misma arquitectura que permite procesar mas peticiones, se enlentece cuando aparece alguna petición que requiere bastante uso del cpu, comprometiendo incluso a las demás peticiones, por lo que el backend real, no esta hecho con nodejs.
Yo para nada critico el modelo arquitectónico basado en workers, esta mas que demostrado que es muy eficiente, ni la arquitectura de nodejs, lo que si no me gusta, es que se base en un lenguaje que tiene un propósito muy bien definido (y distinto) como javascript.
Saludos.
Ahí estoy de acuerdo contigo, pero Ryan Dahl pensó en Javascript porque constituye una parte fundamental en todas las aplicaciones web de hoy en día. Con el tema de las tareas que consumen bastante CPU, eso está mejorando poco a poco, y ya existen algunas buenas prácticas de cómo desplegar NodeJS para aplicaciones con alta concurrencia, que usen este tipo de tareas. Desgraciadamente nosotros acá no tenemos acceso a la nube de Joyent para desplegar NodeJS, pero podemos aprender mucho de ellos y de sus tips que usan para ello. Les recomiendo las presentaciones de Ryan (ya no forma parte de Joyent o del proyecto NodeJS), Brendan Gregg (Joyent), David Pacheco (Joyent), Sean Hess (GitHub) sobre como optimizar NodeJS en producción. Algunas las tengo, así que cualquier cosa, me tiran un correo y se las envío.
Además sigan de cerca el desarrollo y crecimiento de la empresa StrongLoop, la cual emplea a los principales desarrolladores de NodeJS y está trabajando en cosas bastante interesantes como un framework para el desarrollo de aplicaciones móviles basadas en NodeJS. Pueden seguir su blog aquí:
http://www.strongloop.com/blog
@Marcos Ortiz
Estas enamorado de Event Driven, eso lo puedo entender. Pero si lees todos los comentarios veras que me refiero al campo de las aplicaciones web y especialmente con transacciones de mas de 5 consultas a una BD, lo que serian 5 I/O y -hablando en lenguaje de Node.js- 5 callbacks anidados mas el #6 para cerrar el stream http. Mas doloroso es cuando las consultas son interdependientes unas de otras. Por otro lado has probado cuanto cuesta hacer callbacks reusables? Bienvenido a la realidad, aqui Node.js si se queda corto.
PD: Para que continues hablando de empresas que usan Node.js, el mismo sitio de Node.js no esta hecho sobre Node.js 😉
@Yosbel Marín
Vuelvo a sugerir que revises a fondo la tecnología, para lo que sugieres hay múltiples soluciones, lo que pasa es que a veces las cosas no se hacen del todo bien y hay empiezan los problemas. No es necesario anidar callbacks, por ejemplo, eso es un mal habito cuando se usa esta tecnología. Si quieres más velocidad y menor consumo implementas un módulo en c++ o c y lo compilas o creas una dll y la utilizas. En fin, variantes hay muchas. Vuelvo a recalcar que es necesario valorar los propósitos de la creación de esta tecnología y sus campos de aplicación, pues esta lógicamente no es la solución a todos los problemas, eso lo deja claro su propio creador. Salu2
Bro estas duro… Un abrazo
Saludos a todos, bueno ahora soy un principiante con esto de Node JS, pero por favor quien me puede dar los pasos para montar esto en mi pc con wind y Linux, que tengo que instalar en los dos SO??, me gustaria trabajar con esto… saludos, ya descargue los ficheros que estan publicados aca.. me hace falta una ayuda en eso.