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 in tempo reale con un server. Stream Congress (disponibile nel Chrome Web Store) fornisce aggiornamenti in tempo reale sul funzionamento del Congresso degli Stati Uniti. Trasmette in streaming gli aggiornamenti della Camera e del Senato, notizie pertinenti, tweet dei membri del Congresso e altri aggiornamenti sui social media. L'app è pensata per essere lasciata aperta tutto il giorno perché riprende gli affari del Congresso.

Iniziare con WebSocket

La specifica WebSockets ha prestato un po' di attenzione per ciò che consente: un socket TCP stabile e bidirezionale tra il browser e il server. Non è imposto alcun formato di dati sulla socket TCP; lo sviluppatore è libero di definire un protocollo di messaggistica. In pratica, è più pratico passare gli 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());
};

Sebbene il supporto dei browser per WebSocket sia semplice, il supporto lato server è ancora in fase di definizione. Socket.IO su Node.js offre una delle soluzioni lato server più mature e affidabili. Un server basato su eventi come Node.js è la scelta giusta per WebSocket. Per implementazioni alternative, gli sviluppatori Python possono utilizzare Twisted e Tornado, mentre gli sviluppatori Ruby hanno EventMachine.

Ti presentiamo Cramp

Cramp è un framework web Ruby asincrono eseguito su EventMachine. È stato scritto da Pratik Naik, un membro del team principale di Ruby on Rails. Fornendo un linguaggio specifico del dominio (DSL) per le app web in tempo reale, Cramp è la scelta ideale per gli sviluppatori web Ruby. Chi ha dimestichezza con la scrittura di controller in Ruby on Rails riconoscerà lo stile di 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 bloccante, è necessario tenere presente diversi aspetti:

  • Devono essere utilizzati driver di database non bloccanti, come MySQLPlus ed em-mongo.
  • È necessario utilizzare server web basati su eventi. L'assistenza è integrata per Thin e Rainbows.
  • L'app Cramp deve essere eseguita separatamente dall'app Rails principale che alimenta Stream Congress, 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 sia Opera hanno rimosso il supporto dei browser per WebSocket. Sebbene non esista un polyfill JavaScript puro, esiste un ripiego per Flash che è stato ampiamente adottato. Tuttavia, affidarsi a Flash è tutt'altro che ideale. Anche se Chrome e Safari continuano a supportare WebSocket, è emerso chiaramente che per supportare tutti i browser moderni senza fare affidamento su Flash, WebSocket dovrà essere sostituito.

Eseguire il rollback al polling AJAX

È stata presa la decisione di abbandonare WebSocket e di tornare al polling AJAX "old school". Sebbene molto meno efficiente dal punto di vista dell'I/O del disco e della rete, il polling AJAX ha semplificato l'implementazione tecnica di Stream Congress. In particolare, è stata eliminata la necessità di un'app Cramp separata. L'endpoint AJAX è stato invece fornito dall'app Rails. Il codice lato client è stato modificato per supportare il polling AJAX 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 a mente con EventSource è che le connessioni interdominio non sono consentite. Ciò significa che l'app Cramp deve essere pubblicata 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 basata su Thin e in esecuzione sulla porta 8000, la configurazione di Apache è la seguente:

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 WebSockets è che il fallback è completamente basato su JavaScript, senza alcuna dipendenza da Flash. Il polyfill di Remy Sharp lo ottiene implementando il polling lungo nei browser che non supportano EventSource in modo nativo. Pertanto, EventSource funziona oggi su tutti i browser moderni con JavaScript abilitato.

Conclusione

HTML5 apre le porte a molte nuove ed entusiasmanti possibilità di sviluppo web. Con WebSockets ed EventSource, gli sviluppatori web ora dispongono di standard chiari e ben definiti per abilitare le app web in tempo reale. Tuttavia, non tutti gli utenti utilizzano browser moderni. Il degradamento graduale deve essere preso in considerazione quando si sceglie di implementare queste tecnologie. Gli strumenti sul lato server per WebSockets ed EventSource sono ancora nelle fasi iniziali. È importante tenere a mente questi fattori quando sviluppi app HTML5 in tempo reale.