Mejorar eficiencia en peticiones AJAX

JSON logoLa mayoría de desarrolladores de aplicaciones web desean que éstas sean usadas por miles de personas (y si es a la vez, mejor). Cuando se llega a un determinado número de usuarios, es difícil que la aplicación aguante esa carga si no se han tomado ciertas decisiones. La más simple (y probablemente más cara) pasa por añadir más prestaciones a los servidores con los que trabaja nuestra app, pero evidentemente podemos encontrar soluciones ligeramente mejores.

Hoy en día es extraño encontrar una aplicación web que no haga uso de AJAX, unas lo hacen de manera inteligente y otras no tanto. Vamos a ver qué podemos hacer para reducir un poco(en caso de no haberlo hecho ya) la carga que deben soportar nuestros servidores.

El consejo básico es simple: es preferible que el contenido dinámico sea generado en el lado del cliente con Javascript a que el trabajo lo hagan nuestras máquinas. Es claro que no siempre se podrá hacer eso, pero por lo general sí, como ejemplo tenemos datos que se actualizan periódicamente o que se estructuran de forma tabular o en listados.

La justificación del consejo radica en 2 puntos, en primer lugar podemos reducir el tiempo de CPU en el servidor consumido por petición, en segundo lugar podemos reducir el ancho de banda usado si enviamos datos como JSON en vez de contenido HTML pregenerado. Así dicho suena creíble, pero tenemos que comprobarlo (me fijaré en el caso concreto de PHP).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
 
// Codigo que sirve petición con JSON
$link = mysql_connect('xxx', 'xxx', 'xxx');
 
mysql_select_db('xxx');
 
$dbresult = mysql_query('SELECT nick, passwd, name FROM json');
 
$outresult = array();
 
while ($outresult[] = mysql_fetch_row($dbresult));
array_pop($outresult);
 
echo json_encode($outresult);
 
mysql_close($link);
 
?>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
 
// Codigo que sirve la petición con HTML
 
$link = mysql_connect('xxx', 'xxx', 'xxx');
 
mysql_select_db('xxx');
 
$dbresult = mysql_query('SELECT nick, passwd, name FROM json');
 
while ($row = mysql_fetch_row($dbresult)) {
    echo '<tr><td>',$row[0],'</td><td>',$row[1],'</td><td>',$row[2],'</td></tr>';
}
 
mysql_close($link);
 
?>

Antes de hablar sobre como se procesa la petición en el lado del cliente haré algunas observaciones. Aunque en este código no está, añadí código para medir cuantos milisegundos tardaba cada script en ejecutarse. Para aumentar la fiabilidad de las pruebas conté solo el tiempo que se tarda en procesar el resultado de la consulta a la base de datos y enviarla. Así pues excluyo el acceso a la base de datos y el cierre de la conexión de las medidas efectuadas, que son las siguientes (había 200 registros en la base de datos):

  • Petición servida en JSON (10 muestras):
    • Número de bytes enviados: 18830
    • Pico de memoria consumida: 305128 bytes ~ 297.976 Kb
    • Tiempo mínimo: 0.62799 ms
    • Tiempo máximo: 1.2610 ms
    • Tiempo medio: 0.9064 ms
    • Desviación típica: 0.1436 ms
  • Petición servida en HTML (10 muestras):
    • Número de bytes enviados: 23854
    • Pico de memoria consumida: 100008 bytes ~ 97.664 Kb
    • Tiempo mínimo: 0.7529 ms
    • Tiempo máximo: 2.4579 ms
    • Tiempo medio: 1.2909 ms
    • Desviación típica: 0.4956 ms

Lo más destacable de estas medidas es el número de bytes enviados, con JSON podemos ahorrar (como mínimo) un 21.06% de ancho de banda. Digo como mínimo porque en este ejemplo el código HTML generado es más bien austero, ya que no incorpora clases CSS ni identificadores para facilitar el manejo del DOM.

El segundo dato nos muestra la cantidad máxima de memoria consumida durante la petición procesada. Como era esperable, la primera versión del código consume más memoria debido al array que va construyendo y a la cadena JSON que ensambla finalmente a partir de la lista generada. En este punto hay que tener en cuenta que, si somos listos, esto no tiene por qué suponer un gran problema ya que ese mismo array podríamos (y de hecho es recomendable) guardarlo usando (por ejemplo) Memcached de forma que ahorramos posteriores consultas a la base de datos.

Respecto a los otros datos tengo que decir que no son significativos en lo tocante al asunto JSON vs HTML, pues la comparativa no es del todo justa. Sin embargo, lo que sí podemos ver es qué supone usar repetidas veces la primitiva echo frente a un solo uso. Por un lado aumentan los tiempos de ejecución, y por otro aumenta la dispersión de las medidas (ver desviación típica), lo que puede tocar un poco las narices cuando queremos hacer cálculos de cara a escalar nuestra infraestructura.

Otra versión del código que genera HTML es la siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
 
$link = mysql_connect('xxx', 'xxx', 'xxx');
 
mysql_select_db('xxx');
 
$dbresult = mysql_query('SELECT nick, passwd, name FROM json');
 
$outresult = '';
 
while ($row = mysql_fetch_row($dbresult)) {
    $outresult .= '<tr><td>'.$row[0].'</td><td>'.$row[1].'</td><td>'.$row[2].'</td></tr>';
}
 
echo $outresult;
 
mysql_close($link);
 
?>

Los datos que arroja esta versión son los siguientes:

  • Petición servida en HTML (2ª versión, 10 muestras):
    • Número de bytes enviados: 23854
    • Pico de memoria consumida: 126432 bytes ~ 123.469 Kb
    • Tiempo mínimo: 0.3531 ms
    • Tiempo máximo: 0.8111 ms
    • Tiempo medio:  0.5435 ms
    • Desviación típica: 0.1577 ms

Podemos extraer algunas conclusiones más de esta tercera lista de cifras. En primer lugar se nos podría ocurrir adoptar el mismo enfoque para la generación de JSON, es decir, olvidarnos de construir un array y pasar a construir un string directamente (de hecho obtendríamos mejores resultados, pues la cadena JSON es más corta que la cadena HTML). Si no vamos a usar más esos datos en un lapso de tiempo breve parece lo más acertado, pero si tenemos planeado acceder a ellos otra vez puede que sí nos convenga construir la lista como en el primer ejemplo de código.

Un detalle interesante es que teniendo un array probablemente sea más rápido generar un texto JSON con la función json_encode que concatenando strings en un bucle. El problema que tenemos (en este caso) es que el driver nativo de MySQL para PHP no permite obtener una lista con todas las filas directamente, y consecuentemente nos vemos forzados a construirla nosotros (que es menos eficiente que usar un método nativo de PHP programado en C, y de ahí que el tercer ejemplo rinda mejor que el segundo).

Tengo que admitir que no conozco por qué no hay un método nativo de PHP para obtener en forma de array todos los resultados de una consulta a una base de datos MySQL, pero creo que, en caso de no haber razones de peso... puede ser una buena jugada implementar en C una extensión del lenguaje que haga precisamente eso para ahorrar un valioso tiempo de ejecución.

En el lado del cliente las diferencias no han sido estadísticamente significativas para mi ejemplo, aunque es de esperar que los tiempos de descarga sean menores con JSON, mientras que los de proceso sean menores con HTML. El tiempo total dependerá de la potencia de cómputo del cliente y del ancho de banda de la conexión, por lo que algunos usuarios saldrán beneficiados mientras que otros puede que tarden algunos milisegundos más en obtener la página web completamente renderizada.

Sin más rodeos, resumen rápido: JSON permite reducir el ancho de banda consumido en más de un 21%, hacer un solo echo reduce el tiempo de ejecución (la reducción puede sobrepasar fácilmente el 50%, en el caso del ejemplo es un 57.89%) y la dispersión estadística de esos mismos tiempos (lo que nos puede ayudar a realizar mejores cálculos para escalar la plataforma o puede servir para que los balanceadores de carga tengan datos más fiables con los que trabajar).

Como apunte final solo quiero destacar que es importante realizar experimentos y comprobar los hechos por uno mismo, pues la tecnología evoluciona constantemente y es probable que ciertas afirmaciones no se puedan sostener en el tiempo. Además me he dejado algunas técnicas en el tintero, como usar los métodos ob_start y ob_end_clean, que nos permiten aprovechar el buffer de escritura para evitar la necesidad de concatenaciones. Supongo que viendo la extensión del artículo comprenderéis que no lo haya comentado en profundidad. Espero que os haya sido útil :) .

Añadir filtro de búsqueda en una tabla con jQuery

Gnome System Search IconPara completar el anterior artículo que escribí sobre ordenación de tablas con jQuery [1] hoy os hablaré sobre la creación de un filtro de búsqueda para nuestras tablas, algo prácticamente imprescindible hoy en día en multitud de aplicaciones de gestión que presentan parte de sus datos de forma tabular.

El código que veréis a continuación es bastante sencillo, no permite búsquedas con operadores lógicos (AND, OR, NOT), ni que se restrinjan a columnas concretas, sin embargo sí que aprovecha toda la potencia de las expresiones regulares para realizar búsquedas relativamente complejas. Para mi gusto es más importante la característica de los operadores lógicos y las restricciones a columnas concretas que la posibilidad de usar expresiones regulares, pero creo que no vale la pena complicar el artículo por algo que no tiene demasiada miga a nivel técnico.

El código es tal que así:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function setup_searchable (selector) {
	var search_input = $("#"+selector+"-searcher");
 
	search_input.keyup ((function () {
		var tbody = $("#"+selector+"-table").find('tbody');
		var hidden_rows = tbody.find('tr');
 
		var searcher = function () {
			var search_text = search_input.val().toUpperCase();
 
			tbody.html('');
 
			var shown_rows = hidden_rows.filter (function () {
				var re = new RegExp (search_text),
					 matched = false;
 
				$(this).find('td').each(function (i2) {
					matched = matched || ($(this).text().toUpperCase().match(re) != null);
				});
 
				return matched;
			});
 
			tbody.append (shown_rows);
		};
 
		return searcher;
	})());
}

En este ejemplo trabajamos con dos elementos básicos, un elemento input de tipo texto y una tabla. El código asume que el selector se referirá a su identificador único id, y que los respectivos ids tendrán una estructura del tipo selector-searcher para el elemento input, y selector-table para la tabla.

Sobre el código, puede ser interesante notar que uso el evento keyup en vez del evento change, esto permite que la tabla se actualice en tiempo real, en contraposición a lo que sucedería con change, que necesita que apretemos la tecla Enter y cambiemos o cambiemos el foco de nuestro cursor. Otro detalle importante es que guardamos en memoria la totalidad de las filas antes de efectuar el filtrado, de modo que evitamos perderlas, y cuando cambiamos los criterios de búsqueda no tenemos que recargar ningún dato desde el servidor. Este último hecho implica que conviene llamar a la función setup_searchable cada vez que recarguemos la tabla con nuevos datos desde el servidor, pues de lo contrario nuestro buscador trabajará con datos "antiguos".

Algunas mejoras obvias para este código aparte de las mencionadas anteriormente serían las siguientes:

  • Posibilidad de trabajar con selectores más generales (lo que puede permitir, por ejemplo, tener dos campos de búsqueda para una misma tabla, uno en la cabecera y otro en el pie).
  • Añadir un parámetro callback que permita actualizar ciertos datos no mostrados en la tabla en función de lo que haya filtrado nuestra búsqueda. Un ejemplo muy simple sería la actualización de un contador, o en casos más complejos podríamos estar tratando datos como totales, medias, varianzas, etc.

Espero que esto le resulte útil a alguien, agradeceré cualquier comentario y propuesta de mejora, saludos! :)

  1. Ordenación de elementos del DOM con Javascript y jQuery

Ordenación de elementos del DOM con Javascript y jQuery

Shellsort gráfico (from Wikipedia)Hace unos días tuve que lidiar con un problema común en el diseño de aplicaciones web: ordenar bajo demanda las filas de una tabla según el valor de una columna concreta. Evidentemente lo primero que hice fue buscar por Internet alguna solución que me permitiera no tener que programar demasiado... y tengo que decir que lo encontré, pero por pequeños detalles nada de eso me sirvió.

Así que en este artículo explicaré el código que acabé picando yo por mi cuenta. Lo que he escrito depende de jQuery, aunque en realidad se puede sustituir por su "subconjunto" Sizzle, o incluso por la microbiblioteca Zepto.JS (que tiene una sintaxis compatible con la de jQuery).

Vayamos pues al código directamente, he creado un fichero llamado tablesorter.js en el que está todo el código necesario, salvo la llamada a una función, que se hace allí donde nos convenga :) .

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
function row_comparer (row1, row2, index) {
	var r1 = $(row1).find('td').filter(function(){
                        return $(this).index() === index;
        }).text().toUpperCase();
	var r2 = $(row2).find('td').filter(function(){
                        return $(this).index() === index;
        }).text().toUpperCase();
 
	return  (r1 > r2)? 1:
	       ((r1 < r2)?-1:0);
}
 
function shsort (list, comparer, order) { // Shell sort (with Marcin Ciura's gaps)
	var gaps = [701, 301, 132, 57, 23, 10, 4, 1],
	    s = list.length,
	    t;
 
	for (var k=0; k<8; k++) { // 8 is gaps size
		for (var g = gaps[k], i=g; i<s; i++) {
			t = list[i];
			for (var j = i; j >= g && order*comparer(list[j-g], t) == 1; j-= g) {
				list[j] = list[j - g];
			}
			list[j] = t;
		}
	}
}
 
function sort_table (table, index, order) {
	var tbody = table.find ('tbody');
	var rows = tbody.find('tr');
 
	var comparer = function (row1, row2) {
		return row_comparer (row1, row2, index);
	}
 
	shsort (rows, comparer, order);
 
	tbody.html('');
	tbody.append (rows);
}
 
function setup_sortable (selector) {
	$(selector).each (function (i1) {
		$(this).find('th').each (function (i2) {
			var order = 1;
 
			$(this).click ( function () {
				sort_table ($(this).closest('table'), i2, order);
 
				order *= -1;
			})
		});
	});
}

Supongo que algunos de los que leéis esto preferiríais que hubiese creado un plugin para jQuery, siento decir que no es así. Eso no es por ninguna razón en particular, simplemente pasa que todavía no me he tomado la molestia de aprender como se hace, y no sé si es complicado o no, supongo que en breve habrá una versión en forma de plugin para jQuery :) .

En cuanto a como usarlo, la función setup_sortable debe ser llamada utilizando como parámetro un selector para las tablas a las que queramos aplicar ordenación "automática" (con este script las tablas se ordenan cuando se hace click en los elementos th de la tabla, básicamente las cabeceras). En mi caso añadí la clase sortable a todas las tablas que quería añadir esta característica y usé el selector ".sortable".

En cuanto al código, supongo que es destacable que hago un uso intensivo de las funciones anónimas y las clausuras, aunque no es nada que deba sorprender a nadie. En mi humilde opinión, lo más interesante es el algoritmo de ordenación, un "simple" Shell sort (basado en Insertion Sort).

Escogí este algoritmo por las siguientes razones:

  • Es estable, es decir, se mantiene el orden relativo entre elementos con la misma clave (el valor que se usa para realizar las comparaciones) que había en la lista antes de la ordenación. Esto es útil cuando queremos ordenar por distintos criterios a la vez.
  • Es de fácil implementación, solo tenéis que ver el código. Aquí puede parecer un poco más complicado porque uso la función comparer en vez del típico operador de comparación, pero no es una gran dificultad.
  • No usa recursión, ni pilas: los requisitos de memoria son pequeños y constantes.
  • Dependiendo de la implementación (y en particular con esta se consigue), se tiene que para ordenar una lista se deben realizar solo una cantidad de operaciones del orden de O(n·log²(n)).
  • Se trata de una rara avis, le cogí cariño. Aunque no se trata de un algoritmo destacable por su rendimiento, resulta que es tremendamente complicado a nivel teórico. Tanto que su velocidad depende fuertemente de una serie de parámetros de los que casi no se sabe nada a día de hoy. Yo he usado los de Marcin Ciura, que hasta donde yo sé, son los mejores encontrados hasta el momento.

Bien, hasta aquí el apunte de hoy :) . Espero que os pueda resultar útil, agradeceré cualquier comentario.. y si a alguien le interesa, estoy abierto a aceptar ayuda en la jqueryzación de esta minilibrería. Saludos!

Primeros pasos con CoffeeScript

CoffeeScript LogoComo dije en el anterior artículo sobre Nodejs, en este haré una introducción al lenguaje CoffeeScript.

CoffeeScript está diseñado para ser compilado a JavaScript, y parece estar basado en lenguajes como Python o Ruby. Mi empeño en explicar CoffeeScript antes de continuar con la guía de Nodejs radica en que probablemente voy a usarlo con asiduidad. Me gusta Nodejs... pero no me gusta Javascript.

Reconozco que JS es un lenguaje que obliga a pensar de manera diferente, y que en cierta manera le obliga a uno a lucir su ingenio cuando quiere crear código mínimamente interesante, pero a la vez me parece agobiante su falta de "limpieza" y la gran cantidad de construcciones "adhoc" que existen para poder trabajar siguiendo el paradigma OOP.

Empecemos con un ejemplo sencillo, transformaremos el "Hello world" que realizamos anteriormente en Javascript para Nodejs a código CoffeeScript. En primer lugar presentaremos el código que ya escribimos para usarlo de base:

1
2
3
4
5
6
7
8
var http = require('http');
 
http.createServer(function (req, res) {
	res.writeHead(200, {'Content-Type': 'text/plain'});
 	res.end('Hello Worldn');
}).listen(1337, "127.0.0.1");
 
console.log('Server running at http://127.0.0.1:1337/');

En segundo lugar, veamos como debería ser su equivalente en CoffeeScript:

1
2
3
4
5
6
7
8
http = require "http"
 
http.createServer((req, res) ->
  res.writeHead 200, {"Content-Type": "text/plain"}
  res.end "Hello Coffee!!\n"
).listen 1337, "127.0.0.1"
 
console.log "Server running at http://127.0.0.1:1337/"

Intentemos analizar las particularidades de este código. Podemos observar en la primera línea que no es necesario declarar las variables con la palabra reservada var, y que además las funciones son llamadas escribiendo sus parámetros después de sus nombres sin tener que rodearlos con paréntesis, un espacio basta. En la línea 4 podemos ver que cuando hay más de un parámetro estos deben separarse con comas.

Otro punto interesante es la no necesidad de punto y coma final, lo que puede ayudarnos a evitar algún que otro error tonto. Sigamos, pues hay una particularidad que podría darnos algunos dolores de cabeza si no caemos en la cuenta: en la línea 3 podemos ver como se llama a una función usando paréntesis, contrariamente a lo que había indicado antes. Esto solo se puede hacer si los paréntesis están pegados al nombre de la función.

Si hubiera un espacio entre createServer y el paréntesis, entonces se consideraría como parámetro no lo que hay dentro del paréntesis, sino el resultado de la llamada al  método listen (CoffeeScript creería que ese método pertenece al objeto retornado por el interior de los paréntesis).

Respecto a las funciones anónimas en CoffeeScript, estas se declaran de una forma que puede extrañar un poco, pero que para nada es complicada. Las funciones en CoffeeScript tienen estructuras como las que siguen:

1
2
3
4
5
funcionA = (param_1, param2) -> param_1*param_2
 
funcionB = (param_1, param_2) ->
    alert param_1
    param_1*param_2

Es decir, se indican los parámetros que aceptará dentro de un paréntesis, y el cuerpo de la función se escribe después de la flecha formada por el guión y el "mayor que". El último valor calculado se tomará como valor de retorno, y tal como pasa en Python, la identación es importantísima para definir el ámbito de las instrucciones. CoffeeScript tiene muchas otras características (bastante más) interesantes pero creo que será mejor que quien esté interesado las aprenda directamente de su sitio oficial :) .

Para acabar, veamos como se transforma este código nuevamente a Javascript para poder ser ejecutado por Nodejs o aplicado directamente en la parte del cliente de alguna aplicación web que estemos desarrollando. Antes será preciso instalar el compilador de CoffeeScript, y para ello usaremos NPM, como indiqué en el anterior artículo. Realizar la instalación es tan sencillo como teclear en la línea de comandos:

1
sudo npm -g install coffee-script

La opción -g sirve para indicarle a npm que queremos una instalación "global", que el compilador coffee sea accesible desde todo el sistema. Ahora, suponiendo que hayamos llamado ejemplo.coffee a nuestro código, para compilarlo a Javascript solo tenemos que escribir en la línea de comandos:

1
coffee -c ejemplo.coffee

Y obtendremos un fichero llamado ejemplo.js apto para ser ejecutado en Nodejs :D . Podéis comprobar como el código obtenido será ligeramente diferente a nuestra primera versión escrita en Javascript, ya que estará contenido dentro de una función autoejecutable. Si queréis, también podéis hacer el experimento de poner un espacio entre createServer y el paréntesis para comprobar lo que os he dicho anteriormente. Bien, esto es todo por hoy ^_^ , otro día más.

Primeros pasos con Node.js

Logotipo NodejsEstos días estaré experimentando con Nodejs y aprocecharé para escribir un poco sobre mi aprendizaje en el blog. Nodejs es primo-hermano de herramientas como Twisted o Tornado, pero escrito mayoritariamente en Javascript y no en Python, y que aprovecha toda la potencia de la máquina virtual V8 que adquirió Google para su navegador Chrome, que no es moco de pavo :) . Empecemos pues!

Instalación de Nodejs

En todo momento presupongo que estamos trabajando sobre Linux y que tenemos instaladas ciertas herramientas básicas de desarrollo: compiladores, make, python, y la biblioteca libssl-dev. En caso de que nos falte algo no creo que suponga ningún impedimento, normalmente el sistema nos indicará qué nos falta y la instalación por lo general se podrá hacer a través de los gestores de paquetes más usados: apt-get, aptitude, yum y alguno que otro más.

En primer lugar descargaremos el paquete de código fuente estable (a día de hoy es la versión 0.4.12, del día 15 de Septiembre de 2011), lo descomprimimos y entramos desde la línea de comandos en el nuevo directorio creado tras la descompresión. Allí debemos ejecutar los siguientes comandos:

1
2
3
./configure
make
sudo make install

¿Ya está? No, casi... Nos falta NPM, el gestor de paquetes de Nodejs que nos facilitará la vida (aunque no es imprescindible por el momento).

Instalación de NPM

NPM logoPara instalar NPM nos "arriesgaremos" a usar la versión disponible en su repositorio Git oficial... y es que no hay ninguna versión "estable" por el momento. Así que, evidentemente, tendremos que tener instalado git en nuestro sistema.

Lo primero que tenemos que hacer es teclear lo siguiente en la línea de comandos:

1
git clone git://github.com/isaacs/npm.git

Seguidamente debemos entrar en el nuevo directorio creado y ejecutar la siguiente orden:

1
sudo make install

Como podemos ver el proceso no tiene nada de complicado :) , puede que fuera preferible usar los acostumbrados gestores de paquetes... pero la comodidad que nos aportan tendría que pagarse con una falta importante de actualización, y más teniendo en cuenta el temprano estado de desarrollo de estas piezas de software y la cantidad de cambios que se suceden constantemente en ellas.

Bien, pasemos a la acción, vayamos a crear nuestra primera aplicación basada en Nodejs! El código es bastante simple y se entiende perfectamente, aunque en cierto modo puede asustar un poco porque un simple "Hello World!" es algo más complicadillo que si lo hiciéramos directamente en PHP. Podemos llamar al fichero ejemplo.js .

1
2
3
4
5
6
7
8
var http = require('http');
 
http.createServer(function (req, res) {
	res.writeHead(200, {'Content-Type': 'text/plain'});
 	res.end('Hello World\n');
}).listen(1337, "127.0.0.1");
 
console.log('Server running at http://127.0.0.1:1337/');

Como podemos observar el código debe tener en cuenta incluso las cabeceras HTTP, pero no hay que preocuparse, en artículos posteriores veremos como gracias a un gran conjunto de librerías y microframeworks el trabajo con Nodejs puede llegar a ser incluso divertido (y nos serviremos de NPM para cargar esas librerías de forma sencilla). Por cierto, para ver el resultado en el navegador deberéis visitar la dirección http://127.0.0.1:1337/ tal y como indica el propio ejemplo a través de la línea de comandos.

Como segundo avance de lo que veremos en los próximos artículos, entre otras cosas revisitaremos el primer ejemplo mencionado, pero esta vez escrito en Coffeescript, un lenguaje similar a Python (y por lo tanto mucho más agradable que Javascript) que generalmente se compila a Javascript.