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

Introducción

A través de WebSockets y EventSource, HTML5 permite a los desarrolladores compilar apps web que se comunican en tiempo real con un servidor. Stream Congress (disponible en Chrome Web Store) proporciona actualizaciones en vivo sobre el funcionamiento del Congreso de Estados Unidos. Transmite actualizaciones de la Cámara y el Senado, noticias relevantes, tuits de miembros del Congreso y otras actualizaciones de redes sociales. La app está diseñada para permanecer abierta todo el día, ya que captura las actividades del Congreso.

Comienza con WebSockets

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

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

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

Si bien la compatibilidad de los navegadores con WebSockets es sencilla, la compatibilidad del servidor aún se encuentra en etapa formativa. Socket.IO en Node.js proporciona una de las soluciones del servidor más maduras y sólidas. Un servidor orientado a 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. Fue escrito por Pratik Naik, miembro del equipo principal de Ruby on Rails. Debido a que proporciona un lenguaje específico de dominio (DSL) para apps web en tiempo real, Cramp es una opción ideal para los desarrolladores web de Ruby. Quienes 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 coloca sobre la EventMachine sin bloqueo, debes tener en cuenta varias consideraciones:

  • Se deben usar controladores de base de datos sin bloqueo, como MySQLPlus y em-mongo.
  • Se deben usar servidores web orientados a eventos. Se agregó compatibilidad con Thin y Rainbow.
  • La app Cramp debe ejecutarse por separado de la app principal de Rails que potencia Stream Congress, reiniciarse y supervisarse de forma independiente.

Limitaciones actuales

WebSockets sufrió un revés el 8 de diciembre de 2010 cuando se publicó una vulnerabilidad de seguridad. Tanto Firefox como Opera quitaron la compatibilidad del navegador con WebSockets. Si bien no existe un polyfill de JavaScript puro, hay un reemplazo de Flash que se adoptó ampliamente. Sin embargo, depender de Flash está lejos de ser 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ían reemplazar WebSockets.

Reversión a la sondeo AJAX

Se tomó la decisión de dejar de usar WebSockets y volver a la sondeo AJAX "anticuado". Si bien es mucho menos eficiente desde la perspectiva de la E/S de disco y red, el sondeo AJAX simplificó la implementación técnica de Stream Congress. Lo más importante es que se eliminó la necesidad de tener una app de Cramp independiente. En cambio, la app de Rails proporcionó el extremo AJAX. Se modificó el código del cliente para admitir la 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 que debes tener en cuenta con EventSource es que no se permiten las conexiones multidominio. Esto significa que la app de Cramp debe entregarse desde el mismo dominio streamcongress.com que la app principal de Rails. Esto se puede lograr con el proxy en el servidor web. Si suponemos que la app de Cramp se ejecuta con Thin y en el puerto 8000, la configuración de Apache se ve de la siguiente manera:

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 sobre WebSockets es que el resguardo se basa completamente en JavaScript, sin dependencia de Flash. El polyfill de Remy Sharp logra esto mediante la implementación de la solicitud de larga duración en navegadores que no admiten EventSource de forma nativa. Así, EventSource funciona actualmente en todos los navegadores modernos con JavaScript habilitado.

Conclusión

HTML5 abre las puertas a muchas posibilidades nuevas y emocionantes de desarrollo web. Con WebSockets y EventSource, los desarrolladores web ahora tienen estándares claros y bien definidos para permitir aplicaciones web en tiempo real. Sin embargo, no todos los usuarios ejecutan navegadores modernos. Se debe tener en cuenta la degradación elegante cuando se elige implementar estas tecnologías. Las herramientas del servidor para WebSockets y EventSource aún se encuentran en las primeras etapas. Es importante tener en cuenta estos factores al desarrollar aplicaciones HTML5 en tiempo real.