Manejo de varios navegadores (Chrome, Firefox)
Conocimientos básicos de HTML
Conocimientos básicos de JavaScript
Familiaridad con algún tipo de servidor web
(Java, PHP, Apache)
Manejo básico de consola Unix / DOS
Creado por Ryan Dahl (2009)
Hilos: Apache MTM, Java Servlets
Eventos: nginx, node.js
Asíncrono
No bloqueante
Dirigido por eventos
Servidores similares:
Ruby: EventMachine
Java: Akka
¿Pero cómo de rápido?
No para correr código secuencial
... pero sí en ejecución concurrente
Sólo un hilo en ejecución (sin hilos)
Multi-proceso usando el módulo cluster
Node.js es muy escalable
Página de Resumen de Cuentas
2 ingenieros durante 4 meses
Desarrollado dos veces más rápido que la versión Java
33% menos código, 40% menos ficheros
Dos veces más peticiones por segundo
Tiempos de respuesta un 35% más rápidos
Actualmente migrando toda su infraestructura
Backend de apps móviles (2011)
Migración desde Ruby on Rails
De 15 instancias a 4 con node.js
El doble de carga que Ruby on Rails
Actualmente migrando toda su infraestructura
Fuente: VentureBeat
Plataforma inicial: un ingeniero durante un año
Plataforma actual: tres ingenieros durante otro año
Sirve anuncios en móvil para campañas performance
Aguanta 20K peticiones/segundo (al menos)
Escala a 30 servidores (que sepamos)
Latencia < 80 ms
Bajar node.js e instalarlo
Crear proyecto node.js
Probar el código de muestra
¡Hola, mundo!
var http = require('http');
http.createServer(function (request, response) {
response.writeHead(200, {'Content-Type': 'text/plain'});
response.end('Hello World\n');
}).listen(1337, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');
Fuente: node.js
Servidor http en el puerto 1337
response.write()
)request.url
)
Clase EventEmitter
Emite evento data
:
emitter.emit('data', data);
Para recibir el evento data
:
emitter.on('data', function(data) {
console.log('Data: %s', data);
});
emitter.on(event, function(data) {});
Registra un listener para un evento
emitter.once(event, function(data) {});
Registra un listener para una sola vez
emitter.removeListener(event, listener);
Elimina un listener
emitter.removeAllListeners([event]);
Elimina todos los listeners, opcionalmente para un evento(node) warning: possible EventEmitter memory leak detected. 11 listeners added. Use emitter.setMaxListeners() to increase limit.
Ocurre cuando se añaden listeners a un emisor
y no se eliminan
Ejemplo: reutilización de conexiones en un servidor
Solución: al terminar cada petición eliminar listener
emitter.removeListener('event', listener);
Podemos crear nuestros propios EventEmitters:
var events = require('events'); var util = require('util'); function MyEmitter() { events.EventEmitter.call(this); http.createServer(function (request, response) { this.emit('request', request); res.end('Hello World\n'); }).listen(1337, '127.0.0.1', function() { this.emit('init'); }); } util.inherits(LimitsUpdater, events.EventEmitter);
var emitter = new MyEmitter(); emitter.on('init', function() { console.log('Server started'); });
Esto nos permite gestionar eventos en lugar de callbacks
Servidor de sockets
Control a bajo nivel de la conexión
Recibe datos, envía respuesta
Manejado con eventos
Servidor de sockets, puerto 1702
Cuando se abre una conexión pregunta "Hello?"
Tras recibir "hello" responde "world" y termina
Cualquier otro mensaje resulta en "ERROR"
Documentación: node.js net
var net = require('net');
var port = 1702;
var server = net.createServer(function(connection) {
console.log('Connection open');
connection.write('Hello?\r\n');
connection.on('data', function(data) {
if (String(data).trim() != 'hello') {
connection.write('ERROR\r\n');
} else {
connection.end('world\r\n');
console.log('connection closed');
}
});
});
server.listen(port);
Admitir 'hello', 'Hello', 'HELLO, 'HeLlO'...
(Pista: String.toLowerCase()
)
Manejar los eventos
error
y
end
Logar un error o un mensaje normal
Convertir a EventEmitter
Anónimo¿Quién automatiza al automatizador?
Pruebas unitarias
Pruebas de integración / de sistemas
Pruebas de carga
Pruebas automáticas: ¡las mejores!
No basta con correr el código y ver el resultado
Requieren una librería especial
Veremos cómo usar testing
Librería especializada
testing.failure("Failed to do something", callback);
Da la prueba por fallida con un mensaje
testing.success("Well done!", callback);
Da la prueba por buena con un mensaje
Para comprobar que un valor contiene lo que se espera
testing.assert(value, "Error in value", callback);
La variable value
debe ser cierta (true
o un objeto)
testing.assertEquals(sum(2, 2), 4, "Wrong sum", callback);
La función sum(2,2)
debe devolver 4testing sigue la convención habitual de node para callbacks:
doSomething(function(error, result) {
if (error) {
console.error('Could not do something: ' + error);
return;
}
....
});
El error contiene algo sólo si la función falló
testing.check(error, "There is an error", callback);
Comprueba que no haya error; si lo hay, falla el testComprobar que la función doSomethingAsync() no da error
y devuelve el valor 5
function testAsync(callback) {
doSomethingAsync(function(error, result) {
testing.check(error, 'Could not do something', callback);
testing.assertEquals(result, 5, 'Invalid result', callback);
testing.success(callback);
});
}
Casi todas las llamadas de testing terminan en una callback
testing.run([testFirst, testSecond], timeout, callback);
Corre las pruebas testFirst()
y testSecond()
testing.show(results);
Muestra el resultado de las pruebas
Uso habitual:
// run tests if invoked directly
if (__filename == process.argv[1])
{
testing.run([testFirst, testSecond], testing.show);
}
Diseña un API de control
Arranca y para el sistema usando el API
Tests auto-contenidos: limpia al terminar
Tres reglas básicas:
Diseñar las pruebas para el servidor de sockets
usando testing
Crear un API de control
Añadir a test.js
Arrancar el servidor de sockets en un puerto diferente (1705)
Conectar con un socket y enviar "hello"
Verificar que devuelve "world"
Cerrar el socket
Parar el servidor
Encapsular el arranque del servidor en una función
function start(port, callback) {
var server = net.createServer(function(connection) {
console.log('Conexión abierta');
...
});
server.listen(port, callback);
return server;
}
Añadir la parada del servidor: server.close()
Llamar en secuencia:
function testServer(callback) {
var server = start(1705, function(error, result) {
server.stop(function(error, result) {
....
});
});
}
function testServer(callback) {
var port = 1705;
var server = start(port, function(error) {
testing.check(error, 'Could not start server', callback);
console.log('started');
var socket = net.connect(port, 'localhost', function(error) {
testing.check(error, 'Could not connect', callback);
socket.on('data', function(data) {
console.log('Received ' + data);
var message = String(data).trim();
if (message == 'Hello?')
{
socket.write('hello');
return;
}
testing.assertEquals(message, 'world', 'Bad response', callback);
server.close(function(error) {
testing.check(error, 'Could not stop server', callback);
testing.success(callback);
});
});
});
});
}
Eliminar todos los console.log()
Añadir otra prueba testError()
:
envía otra cadena, comprueba que obtiene 'ERROR'
Añadir una prueba que arranque dos servidores
en puertos distintos y los pare
Concepto de programación funcional
CPS: Continuation-passing Style
¡También conocidas como callbacks!
El estado pasa como parámetros y clausuras
var net = require('net');
var port = 1702;
var server = net.createServer(function(connection) {
console.log('Connection to %s open', port);
connection.write('Hello?\r\n');
connection.on('data', function(data) {
if (String(data).trim() != 'hello) {
connection.write('ERROR\r\n');
} else {
connection.end('world\r\n');
console.log('Connection to %s closed', port);
}
});
});
server.listen(port);
Continuaciones en amarillo
La temida pirámide de callbacks
Infinitas continuaciones anidadas
Abuso de lambdas (funciones anónimas)
Difícil de seguir, depurar y refactorizar
Usar funciones con nombre
Crear un objeto con funciones con nombre
Las clausuras se convierten en atributos
Usar async: async.waterfall()
Usar eventos: EventEmitter
Revisar la pirámide de callbacks
Código original: test de socket server
Convertir a objeto con atributos
No dejar más de dos callbacks anidadas
start()
también a un objetoerror
Fichero Readme: README.md
Definición: package.json
Punto de entrada: index.js
Código: lib/
Documentación: doc/
Binarios: bin/npm install
: instala uno o más paquetesnpm install
-g: instalación globalnpm update
: actualiza uno o más paquetesnpm remove
: elimina un paquetenpm test
: corre las pruebasLocal:
node_modules
../node_modules
../../node_modules
...
$HOME/node_modules
./node_modules
Global:
{prefix}/lib/node_modules
donde {prefix} suele ser /usr/local
{
"name": "simplecached",
"version": "0.0.1",
"description": "Simplified memcached server.",
"contributors": ["Alex Fernández <alexfernandeznpm@gmail.com>"],
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/alexfernandez/simplecached"
},
"dependencies": {
"testing": "1.2.x"
},
"keywords" : ["simplecached", "didactic", "memcached", "caching"],
"scripts": {
"test": "node test.js"
},
"private": "false"
}
Instalar una dependencia con npm install testing
Inspeccionar la estructura de directorios resultante
Listar los paquetes instalados con npm list
Eliminar una dependecia con npm remove testing
Crear un package.json siguiendo la especificación
partiendo del ejemplo anterior
Cambiar nombre, versión y descripción
Correr npm install
Exportar test()
desde el servidor.
Importarlo desde test.js
.
Correr
npm test
Paquetizar los ficheros existentes
Código en lib/
Crear index.js
que exporte alguna función
Publicar con npm publish
¡Despublicarlo! con npm unpublish --force