Case study - Aggiornamenti in tempo reale in Stream Congress

Luigi Montanez
Luigi Montanez

Introduzione

Tramite WebSockets ed EventSource, HTML5 consente agli sviluppatori di creare app web che comunicano con un server in tempo reale. Stream Congress (disponibile nel Chrome Web Store) fornisce aggiornamenti in tempo reale sul funzionamento del Congresso degli Stati Uniti. Trasmette aggiornamenti di piano sia dalla Camera che dal Senato, aggiornamenti di notizie pertinenti, tweet dei membri del Congresso e altri aggiornamenti sui social media. L'app deve essere lasciata aperta tutto il giorno, in quanto rispecchia gli affari del Congresso.

Iniziare con WebSocket

Le specifiche WebSockets hanno ottenuto un po' di attenzione in merito a ciò che consente: un socket TCP stabile e bidirezionale tra il browser e il server. Non esiste un formato dati imposto al socket TCP; lo sviluppatore è libero di definire un protocollo di messaggistica. In pratica, la soluzione più comoda è trasferire oggetti JSON come stringhe. Il codice JavaScript lato client per ascoltare gli aggiornamenti in tempo reale è semplice e chiaro:

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

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

Il supporto dei browser per WebSocket è semplice, ma il supporto lato server è ancora in fase formativa. Socket.IO su Node.js fornisce una delle soluzioni lato server più mature e solide. Un server basato su eventi come Node.js è perfetto per WebSocket. Per implementazioni alternative, gli sviluppatori Python possono utilizzare Twisted e Tornado, mentre gli sviluppatori Ruby possono utilizzare EventMachine.

Scopri i crampi

Cramp è un framework web Ruby asincrono che viene eseguito su EventMachine. Scritto da Pratik Naik, membro del team principale di Ruby on Rails. Grazie a un linguaggio specifico per il dominio (DSL) per le app web in tempo reale, Cramp è la scelta ideale per gli sviluppatori web Ruby. Coloro che hanno familiarità con la scrittura di controller in Ruby on Rails riconosceranno lo stile dei 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

Poiché Cramp si basa su EventMachine non bloccabile, occorre tenere presente diversi fattori:

  • È necessario utilizzare driver di database che non blocchino, come MySQLPlus ed em-mongo.
  • È necessario utilizzare server web basati su eventi. Il supporto è integrato per Sottili e Rainbows.
  • L'app Cramp deve essere eseguita separatamente dall'app Rails principale alla base dello Stream Congress, nonché riavviata e monitorata in modo indipendente.

Limitazioni attuali

WebSocket ha subito una battuta d'arresto l'8 dicembre 2010, quando è stata pubblicizzata una vulnerabilità di sicurezza. Sia Firefox che Opera hanno rimosso il supporto dei browser WebSocket. Sebbene non esistano polyfill JavaScript puri, esiste un elemento di riserva Flash che è stato ampiamente adottato. Tuttavia, affidarsi a Flash è tutt'altro che ideale. Anche se Chrome e Safari continuano a supportare i WebSocket, è emerso chiaramente che, per supportare tutti i browser moderni senza fare affidamento su Flash, i WebSocket avrebbero dovuto essere sostituiti.

Rollback al polling AJAX

Abbiamo preso la decisione di abbandonare WebSocket e di tornare ai sondaggi AJAX "vecchio scuola". Sebbene molto meno efficiente dal punto di vista dell'I/O su disco e rete, il polling AJAX ha semplificato l'implementazione tecnica di Stream Congress. Ma soprattutto, la necessità di un'app Cramp separata è stata eliminata. L'endpoint AJAX è stato invece fornito dall'app Rails. Il codice lato client è stato modificato per supportare il polling AJAX di 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 significativo da tenere presente con EventSource è che le connessioni interdominio non sono consentite. Ciò significa che l'app Cramp deve essere gestita dallo stesso dominio streamcongress.com dell'app Rails principale. A questo scopo, puoi utilizzare il proxy sul server web. Supponendo che l'app Cramp sia alimentata da Thin e in esecuzione sulla porta 8000, la configurazione Apache appare così:

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>

Questa configurazione imposta un endpoint EventSource su streamcongress.com/live.

Polyfill stabile

Uno dei vantaggi più significativi di EventSource rispetto a WebSocket è che l'elemento di riserva è completamente basato su JavaScript, senza alcuna dipendenza da Flash. Il polyfill di Remy Sharp a questo scopo prevede l'implementazione del lungo sondaggio nei browser che non supportano EventSource in modo nativo. Pertanto, EventSource funziona attualmente su tutti i browser moderni con JavaScript attivato.

Conclusione

HTML5 apre le porte a molte nuove ed entusiasmanti possibilità di sviluppo web. Con WebSocket ed EventSource, gli sviluppatori web ora dispongono di standard puliti e ben definiti per abilitare le app web in tempo reale. Tuttavia, non tutti gli utenti utilizzano browser moderni. Quando si sceglie di implementare queste tecnologie, è necessario considerare il degrado graduale. Inoltre, l'uso di strumenti lato server per WebSocket ed EventSource è ancora nelle fasi iniziali. È importante tenere a mente questi fattori durante lo sviluppo di app HTML5 in tempo reale.