2011/04/16

Comunicaciones con HTML5

El post de hoy es muy interesante ya que nos va a permitir romper con las limitaciones tradicionales de las páginas web. No os lo perdáis...

Casi todas las páginas que hacemos, tienen la necesidad de comunicarse o desde o hacia el exterior. Normalmente usamos AJAX, pero no siempre es la solución óptima. Con HTML5, han aparecido varias tecnologías que nos ayudan a rellenar los huecos dejados por AJAX para realizar operaciones complejas de forma nativa.

Concretamente, vamos a ver 5 métodos de comunicación
  • XHR & XHR2 con CORS. XHR = XMLHttpRequest, es decir, AJAX, pero en este caso, con esteroides.
  • Web Messaging
  • Web Sockets
  • Server Sent Events
  • Web Workers
Todos ellos menos XHR están basados en eventos lo que hace que todo sea más dinámico y por supuesto, sin necesidad de recargar la página.

XHR & XHR2 con CORS
Parece un gazpacho de letras aleatorias... En cristiano es XMLHttpRequest y XMLHttpRequest2 con Cross Origin Resource Sharing. Básicamente de lo que se trata es que habilitar las peticiones AJAX entre distintos dominios al de la página actual, pues ya sabéis que por motivos de seguridad, ese tipo de peticiones están capadas (aunque algunos navegadores se lo pasen por el forro en el mejor de los casos... no te escondas IE, que se te ve el plumero).

El secreto para permitir peticiones entre distintos dominios reside en el servidor de destino, de modo que en la cabecera se especifica el dominio o dominios desde los que permiten realizar peticiones AJAX. También admite comodines. Un ejemplo en PHP es el siguiente:
<?php
header('Access-Control-Allow-Origin: *');
?>

Entonces, os preguntaréis de dónde viene el objeto XHR2. Básicamente, lo que ocurre es que el objeto XHR original no permite o no debe permitir abrir un URL de un dominio distinto al actual. Sin embargo el uso de un objeto u otro es transparente al programador, menos para IE8, de nuevo, que ha de usar XDomainRequest. Un ejemplo de llamada usando XHR2 con CORS a un dominio distinto al de la página actual es éste.
var client = new XMLHttpRequest();
client.onreadystatechange = function () {
  if (this.readyState == 4 && this.status == 200) {
    alert('The most awesome-est person to follow: ' + this.responseText);
  }
};
client.open('GET', 'http://remysharp.com/demo/cors.php');
client.send();
Web Messaging con postMessage
Esta tecnología es bastante antigua ya en cierta manera está soportada desde IE6.

El principal punto fuerte de esta tecnología es el permitir la comunicación entre páginas de distintos dominios y es una de las maneras más sencillas de saltarse la limitación de la política de mismo dominio. El principio de funcionamiento es el siguiente. En una página tenemos un contenido con un iframe que apunta a otra página distinta que es con la que nos queremos comunicar. La página principal dispone de un dódigo javascript que permite recibir eventos onmessage del tipo
window.onmessage = function (event) {
  if (event.origin == 'mytrustedsite.com') {
    alert('mi sitio de confianza ha dicho: ' + event.data);
  }
};
 El contenido del iframe además podrá ser originador de mensajes mediante el siguiente código.
// iframe es el nodo DOM del propio iframe
iframe.contentWindow.postMessage("hola", location.host);
La comunicación está limitada a cadenas de texto, pero siempre se pueden realizar conversiones de tipo JSON-string y string-JSON con las funciones JSON.stringify y JSON.parse.

Para aumentar la compatibilidad a IE6, hay un proyecto llamado EasyXDM que lo que hace es en función de la versión del navegador, si es necesario añadir un flash que permite el envío de messages. Para más información, en su sitio.

Web Sockets
Web sockets es la tecnología para mí con más futuro ya que permite no tener que recurrir a inventos artificiosos para comunicar una página web con cualquier cosa ya que usa sockets y eventos.

Los web sockets son equivalentes a los sockets de comunicación de toda la vida pero no son lo mismo. Para poder usar web sockets, se usa un protocolo especial ws:// de modo que es necesario que el servidor de destino soporte ese protocolo.

Hay muchas implementaciones de servidores que soportan el protocolo, pero desafortunadamente, los servidores normales no lo soportan de forma nativa. Esto es debido a que el modelo de funcionamiento basado en conexión como por ejemplo el de apache, no es compatible con el modelo de funcionamiento basado en eventos como pasa con los web sockets. Además, existe un paradigma denominado el problema de las 10000 conexiones C10k, que especifica que un servidor debe ser capaz de soportar 10000 conexiones simultáneas. El problema viene en que para que un servidor de tipo tradicional orientado a la conexión soporte websockets, ha de mantener la conexión abierta continuamente para mantener levantado el canal de comunicación entre la página y el servidor... esa conexión no se cierra y si hacemos un cálculo rápido de unas 50 conexiones por página, la frontera de las 10000 conexiones se nos puede hacer muy pequeña.

Aquí entran en juego los servidores orientados a eventos, que mediante una única conexión abierta por servicio nos permiten servir a un número ilimitado de peticiones. Es decir, que por cada tipo de recurso (llamémosla página) solo habrá que tener una conexión abierta. Así, un servidor ligero puede ser capaz de servir de forma muy rápida no solo a 10000 clientes simultáneos, sino a 100000, 200000 etc. Hay varias alternativas de servidores pero las que más me gustan son las siguientes:
  • Node. Mi favorito. Servidor ligero basado únicamente en javascript V8. Es sencillo de implementar, extremadamente rápido y muy muy ligero. Hay servidores que te permiten hospedar gratuitamente servidores node como es el caso de Joyent. Hay extensiones para node para acceso a bases de datos e integraciones con otros servicios para respuestas en tiempo real. Un ejemplo de servidor node es tan sencillo como el siguiente, que responde con una página web con la cadena Hello World a peticiones websocket hechas sobre el servidor:
var http = require('http');
http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Hello World\n');
}).listen(8124, "127.0.0.1");
console.log('Server running at http://127.0.0.1:8124/');
otro ejemplo de servidor TCP con node que hace un echo de lo que se le envíe
var net = require('net');
var server = net.createServer(function (socket) {
  socket.write("Echo server\r\n");
  socket.pipe(socket);
})
server.listen(8124, "127.0.0.1");
  • WaterSpout. otro que también funciona muy bien. hecho en PHP y su principal ventaja es que soporta tanto web sockets como longpolling (tecnología sobre la que hablaré otro día). Evidentemente no es un PHP para ser servido por apache por ejemplo, si no para ser ejecutado desde línea de comandos y mantenerse en segundo plano. Nota para los losties, probad el chat multiusuario de John Locke ;)
  • Apache. Aunque apache no es la plataforma ideal, si se tiene el humor de leerse un par decientos de páginas de documentación y hacer cientos de cambios de parámetros de configuración, existe un módulo basado en pyton para apache para soportar websockets llamado pywebsockets. Más información aquí.
  • Nginx. Detrás de una web horrorosa que da pocas ganas de probarlo se esconde este servidor cuya principal virtud quizás es que dispone de binarios apra windows y que coge un 46% de la cuota de mercado de servidores de websockers (el 54% lo más probable es que sean node).
Bueno, ya tenemos los cimientos del edificio, ahora a por las paredes. Quién soporta websockets? pues todos los navegadores de verdad menos los sospechosos habituales (Internet Explorer, al rincón!) bueno, realmente, solo chrome y safari. Firefox tampoco lo soporta nativamente... pero realmente hay maneras de que desde el IE6 se puedan usar websockets mediante una librería que incorpora un flash que suple esta carencia a costa de menor rendimiento en la carga de la página debido a la carga del flash. El proyecto en cuestión se llama web-socket-js y solo se puede decir que funciona y es transparente, qué más se puede pedir?!. El desarrollador no asegura que en IE < 8 no haya errores, pero que directamente pasa de dar soporte para esas versiones aunque debería funcionar teniendo flash > v.10.

El tejado de nuestro edificio es el código dentro de la página y una vez tenemos toda la infraestructura montada es realmente sencillo... tan sencillo que da miedo...

var ws = new WebSocket('ws://somesite.com/updates');

ws.onmessage = function (event) {
  alert(event.data);
};

ws.onopen = function () {
  ws.send('yay! we connected!');
};
Lo más importante, es cambiar el "chip" de programación ya que los web sockets tanto en su parte servidora como en la cliente está 100% orientada a eventos y como tan, necesita que el código sea así ya que es la única manera de conseguir conexiones no bloquentes. Es un imperativo hacerlo así.

Server-Sent Events
Una tecnología que viene desde el 2006 promocionada por Opera. Es de un único sentido desde el servidor hacia el cliente y se usa para realizar push de información hacia los clientes además de implementar la limitación de mismo dominio. Soportada únicamente por Opera, Safari y Chrome, pero bajo mi punto de vista, la limitación de plataformas soportadas y el sentido único hace que no sea especialmente atractiva.

Hace falta un servidor preparado como pasa con los web sockets. Un ejemplo de servidor basado en Node es éste y en la parte cliente es prácticamente idéntico a web sockets, solo cambiando el objeto referenciado y el protocolo, claro.

var es = new EventSource('/sse');
es.onopen = function () {
  console.log('opened stream');
};

es.onmessage = function (event) {
  console.log('new message: ' + event.data);
};
Web Workers
En pocas palabras, un web worker es un javascript de una página que se ejecuta en segundo plano. Proporciona un sistema de comunicación bidireccional  de cadenas de texto con su página principal basado en mensajes. Evidentemente, no implementa "per se" comunicación cliente-servidor... solo de una página con un proceso en segundo plano creado por ella.

Ejemplo de creación de web worker en la página principal
var worker = new Worker('bigjob.js');
worker.onmessage = function (event) {
  alert('Message from worker: ' + event.data); // event.data es string!
};

worker.postMessage('task=job1');
Código fuente del worker bigjob.js.
this.onmessage = function (event) {
  var job = event.data;
  if (job == 'task=job1') {
    job1();
  } else if (job == 'task=job2') {
    job2();
  }
};

// just a pseudo example
function job1() {
  // do some big task
  while (working) {
    // continue task
    this.postMessage('job1 ' + amountComplete + '% complete');
  }
  this.postMessage('job1 100% complete');
}
Internet Explorer no soporta web workers para variar. Sin embargo, funcionan en Chrome, Safari, Opera y Firefox.

No hay comentarios:

Publicar un comentario