Caso de éxito: Actualizaciones en tiempo real en Stream Congress

Luigi Montánez
Luigi Montánez

Introducción

A través de WebSockets y EventSource, HTML5 permite a los desarrolladores crear aplicaciones web que se comunican en tiempo real con un servidor. Stream Congress (disponible en Chrome Web Store) brinda actualizaciones en vivo sobre el funcionamiento del Congreso de Estados Unidos. Transmite actualizaciones de la Cámara y el Senado, actualizaciones de noticias relevantes, tuits de miembros del Congreso y otras actualizaciones en las redes sociales. La aplicación debe permanecer abierta todo el día, ya que captura el negocio del Congreso.

Comienza con WebSockets

La especificación de WebSockets recibió bastante atención en relación con lo que permite: un socket TCP estable y bidireccional entre el navegador y el servidor. No se impone un formato de datos al socket TCP; el desarrollador tiene la libertad de definir un protocolo de mensajería. En la práctica, pasar objetos JSON como cadenas es más conveniente. El código JavaScript del cliente para detectar actualizaciones en tiempo real es sencillo:

var liveSocket = new WebSocket("ws://streamcongress.com:8080/live");

liveSocket.onmessage = function (payload) {
  addToStream(JSON.parse(payload.data).reverse());
};

Si bien la compatibilidad del navegador con WebSockets es sencilla, la compatibilidad del servidor aún se encuentra en la etapa formativa. Socket.IO en Node.js proporciona una de las soluciones del servidor más consolidadas y sólidas. Un servidor controlado por eventos como Node.js es la opción adecuada para WebSockets. Para implementaciones alternativas, los desarrolladores de Python pueden usar Twisted y Tornado, mientras que los desarrolladores de Ruby tienen EventMachine.

Presentamos Cramp

Cramp es un framework web asíncrono de Ruby que se ejecuta en EventMachine. Está escrito por Pratik Naik, miembro del equipo principal de Ruby on Rails. Cramp, que proporciona un lenguaje específico de dominio (DSL) para aplicaciones web en tiempo real, es una opción ideal para los desarrolladores web de Ruby. Aquellos que estén familiarizados con la escritura de controladores en Ruby on Rails reconocerán el estilo de Cramp:

require "rubygems"
require "bundler"
Bundler.require
require 'cramp'
require 'http_router'
require 'active_support/json'
require 'thin'

Cramp::Websocket.backend = :thin

class LiveSocket < Cramp::Websocket
periodic_timer :check_activities, :every => 15

def check_activities
    @latest_activity ||= nil
    new_activities = find_activities_since(@latest_activity)
    @latest_activity = new_activities.first unless new_activities.empty?
    render new_activities.to_json
end
end

routes = HttpRouter.new do
add('/live').to(LiveSocket)
end
run routes

Debido a que Cramp se ubica sobre la EventMachine sin bloqueo, debes tener en cuenta varias consideraciones:

  • Se deben usar controladores de bases de datos que no se bloqueen, como MySQLPlus y em-mongo.
  • Se deben usar servidores web controlados por eventos. La compatibilidad está integrada para Thin y Rainbows.
  • La app de Cramp se debe ejecutar por separado de la app principal de Rails que impulsa Stream Congress, y se debe reiniciar y supervisar de forma independiente.

Limitaciones actuales

WebSockets sufrió un contratiempo el 8 de diciembre de 2010 cuando se publicó una vulnerabilidad de seguridad. Firefox y Opera eliminaron la compatibilidad del navegador con WebSockets. Si bien no existe un polyfill puro de JavaScript, el resguardo de Flash se ha adoptado ampliamente. Sin embargo, confiar en Flash está lejos de ser lo ideal. Si bien Chrome y Safari continúan admitiendo WebSockets, quedó claro que para admitir todos los navegadores modernos sin depender de Flash, se debía reemplazar WebSockets.

Reversión a los sondeos AJAX

Se tomó la decisión de alejarnos de WebSockets y volver a las encuestas de AJAX “antiguas”. Aunque fue mucho menos eficiente desde una perspectiva de E/S de disco y red, el sondeo de AJAX simplificó la implementación técnica de Stream Congress. Lo más importante fue que se eliminó la necesidad de una app de Cramp independiente. En su lugar, la app de Rails proporcionó el extremo AJAX. Se modificó el código del cliente para que sea compatible con el sondeo AJAX de jQuery:

var fillStream = function(mostRecentActivity) {
  $.getJSON(requestURL, function(data) {
    addToStream(data.reverse());

    setTimeout(function() {
      fillStream(recentActivities.last());
    }, 15000);
  });
};

AJAX polling, though, is not without its downsides. Relying on the HTTP request/response cycle means that the server sees constant load even when there aren't any new updates. And of course, AJAX polling doesn't take advantage of what HTML5 has to offer.

## EventSource: The right tool for the job

Up to this point, a key factor was ignored about the nature of Stream Congress: the app only needs to stream updates one way, from server to client - downstream. It didn't need to be real-time, upstream client-to-server communication. 

In this sense, WebSockets is overkill for Stream Congress. Server-to-client communication is so common that it's been given a general term: push. In fact, many existing solutions for WebSockets, from the hosted [PusherApp](http://pusherapp.com) to the Rails library [Socky](https://github.com/socky), optimize for push and don't support client-to-server communication at all.

Enter EventSource, also called Server-Sent Events. The specification compares favorably to WebSockets in the context to server to client push:

- A similar, simple JavaScript API on the browser side.
- The open connection is HTTP-based, not dropping to the low level of TCP.
- Automatic reconnection when the connection is closed.

### Going Back to Cramp

In recent months, Cramp has added support for EventSource. The code is very similar to the WebSockets implementation:

```ruby
class LiveEvents < Cramp::Action
self.transport = :sse

periodic_timer :latest, :every => 15

def latest
@latest_activity ||= nil
new_activities = find_activities_since(@latest_activity)
@latest_activity = new_activities.first unless new_activities.empty?
render new_activities.to_json
end
end

routes = HttpRouter.new do
add('/').to(LiveEvents)
end
run routes

Un problema importante a tener en cuenta con EventSource es que no se permiten las conexiones multidominio. Esto significa que la app de Cramp se debe entregar desde el mismo dominio streamcongress.com que la app principal de Rails. Esto se puede lograr con la creación de un proxy en el servidor web. Suponiendo que la app de Cramp usa la tecnología de Thin y se ejecuta en el puerto 8000, la configuración de Apache se ve así:

LoadModule  proxy_module             /usr/lib/apache2/modules/mod_proxy.so
LoadModule  proxy_http_module        /usr/lib/apache2/modules/mod_proxy_http.so
LoadModule  proxy_balancer_module    /usr/lib/apache2/modules/mod_proxy_balancer.so

<VirtualHost *:80>
  ServerName streamcongress.com
  DocumentRoot /projects/streamcongress/www/current/public
  RailsEnv production
  RackEnv production

  <Directory /projects/streamcongress/www/current/public>
    Order allow,deny
    Allow from all
    Options -MultiViews
  </Directory>

  <Proxy balancer://thin>
    BalancerMember http://localhost:8000
  </Proxy>

  ProxyPass /live balancer://thin/
  ProxyPassReverse /live balancer://thin/
  ProxyPreserveHost on
</VirtualHost>

Esta configuración establece un extremo de EventSource en streamcongress.com/live.

Polyfill estable

Una de las ventajas más significativas de EventSource en comparación con WebSockets es que el resguardo se basa completamente en JavaScript y no depende de Flash. El polyfill de Remy Sharp logra esto mediante la implementación de sondeos largos en navegadores que no admiten EventSource de forma nativa. Así, EventSource funciona en la actualidad en todos los navegadores modernos con JavaScript habilitado.

Conclusión

HTML5 abre la puerta a muchas posibilidades de desarrollo web nuevas y emocionantes. Con WebSockets y EventSource, los desarrolladores web ahora cuentan con estándares claros y bien definidos para habilitar aplicaciones web en tiempo real. Pero no todos los usuarios utilizan navegadores actualizados. A la hora de implementar estas tecnologías, se debe considerar la degradación correcta. Y las herramientas del servidor para WebSockets y EventSource aún se encuentran en sus primeras etapas. Es importante tener en cuenta estos factores al desarrollar aplicaciones HTML5 en tiempo real.