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

Introducción

A través de WebSockets y EventSource, HTML5 permite a los desarrolladores crear apps web que se comuniquen 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

Las especificaciones de WebSockets han recibido bastante atención por lo que permiten: 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 escuchar actualizaciones en vivo es simple y claro:

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 sólidas y maduras. 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 sobre EventMachine. Fue escrito por Pratik Naik, miembro del equipo principal de Ruby on Rails. Cramp proporciona un lenguaje específico de dominio (DSL) para apps web en tiempo real, por lo que 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 encuentra en la parte superior de EventMachine no bloqueador, hay varias consideraciones que debes tener en cuenta:

  • Se deben usar controladores de base de datos no bloqueantes, como MySQLPlus y em-mongo.
  • Se deben usar servidores web orientados a 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 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. Aunque Chrome y Safari siguen admitiendo WebSockets, quedó claro que, para admitir todos los navegadores modernos sin depender de Flash, se deberían reemplazar los WebSockets.

Cómo revertir 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 depender 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. Por lo tanto, 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 habilitar apps 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. Además, las herramientas del servidor para WebSockets y EventSource aún están en las primeras etapas. Es importante tener en cuenta estos factores cuando desarrolles apps HTML5 en tiempo real.