Étude de cas : Informations en temps réel dans Stream Congress

Luigi Montanez
Luigi Montanez

Introduction

Avec WebSockets et EventSource, HTML5 permet aux développeurs de créer des applications Web qui communiquent en temps réel avec un serveur. Stream Congress (disponible sur le Chrome Web Store) fournit des informations en direct sur le fonctionnement du Congrès des États-Unis. Elle diffuse les nouvelles concernant les étages gravis de la Chambre des représentants et du Sénat, des nouvelles pertinentes, des tweets des membres du Congrès et d'autres informations sur les réseaux sociaux. L'application doit rester ouverte toute la journée, car elle enregistre les activités du Congrès.

Commencer avec WebSockets

La spécification WebSockets a fait l'objet de beaucoup d'attention pour ce qu'elle permet de faire: un socket TCP stable et bidirectionnel entre le navigateur et le serveur. Aucun format de données n'est imposé au socket TCP. Le développeur est libre de définir un protocole de messagerie. En pratique, il est plus pratique de transmettre des objets JSON sous forme de chaînes. Le code JavaScript côté client permettant d'écouter les mises à jour en temps réel est simple et clair :

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

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

Bien que la compatibilité des navigateurs avec WebSockets soit simple, la compatibilité côté serveur est encore à ses débuts. Socket.IO sur Node.js fournit l'une des solutions côté serveur les plus matures et les plus robustes. Un serveur basé sur des événements comme Node.js est la solution idéale pour WebSockets. Pour d'autres implémentations, les développeurs Python peuvent utiliser Twisted et Tornado, tandis que les développeurs Ruby ont EventMachine.

Présentation des crampes

Cramp est un framework Web Ruby asynchrone qui s'exécute sur EventMachine. Il est écrit par Pratik Naik, membre de l'équipe principale de Ruby on Rails. Cramp fournit un langage spécifique à un domaine (DSL) pour les applications Web en temps réel. Il est donc idéal pour les développeurs Web Ruby. Ceux qui ont l'habitude d'écrire des contrôleurs dans Ruby on Rails reconnaîtront le style 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

Étant donné que Cramp repose sur EventMachine non bloquant, plusieurs points sont à prendre en compte :

  • Vous devez utiliser des pilotes de base de données non bloquants, comme MySQLPlus et em-mongo.
  • Vous devez utiliser des serveurs Web basés sur les événements. La prise en charge est intégrée pour les applications Thin et Rainbows.
  • L'application Cramp doit être exécutée séparément de l'application Rails principale qui alimente Stream Congress, redémarrée et surveillée indépendamment.

Limites actuelles

WebSockets a subi un revers le 8 décembre 2010, lorsqu'une faille de sécurité a été rendue publique. Firefox et Opera ont supprimé la compatibilité WebSockets de leur navigateur. Bien qu'il n'existe pas de polyfill JavaScript pur, il existe une création de remplacement Flash largement utilisée. Toutefois, s'appuyer sur Flash est loin d'être idéal. Même si Chrome et Safari sont toujours compatibles avec WebSockets, il est devenu évident que les WebSockets devaient être remplacés pour prendre en charge tous les navigateurs modernes sans Flash.

Rollback du sondage AJAX

Nous avons décidé d'abandonner WebSockets et de revenir à l'ancienne méthode de requête AJAX. Bien qu'il soit beaucoup moins efficace du point de vue des E/S sur disque et réseau, les sondages AJAX ont simplifié la mise en œuvre technique de Stream Congress. Plus important encore, il n'est plus nécessaire d'avoir une application Cramp distincte. Le point de terminaison AJAX a été fourni par l'application Rails. Le code côté client a été modifié pour prendre en charge l'interrogation 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 problème important à garder à l'esprit avec EventSource est que les connexions multidomaines ne sont pas autorisées. Cela signifie que l'application Cramp doit être diffusée à partir du même domaine streamcongress.com que l'application Rails principale. Cela peut être réalisé à l'aide du proxy sur le serveur Web. En supposant que l'application Cramp est alimentée par Thin et exécutée sur le port 8000, la configuration Apache se présente comme suit :

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>

Cette configuration définit un point de terminaison EventSource à streamcongress.com/live.

Polyfills stables

L'un des principaux avantages d'EventSource par rapport à WebSockets est que le remplacement est entièrement basé sur JavaScript, sans dépendre de Flash. Le polyfill de Remy Sharp permet d'implémenter le long-polling dans les navigateurs qui ne sont pas compatibles avec EventSource en mode natif. Ainsi, EventSource fonctionne aujourd'hui sur tous les navigateurs modernes avec JavaScript activé.

Conclusion

Le HTML5 ouvre la porte à de nombreuses nouvelles possibilités intéressantes en matière de développement Web. Avec WebSockets et EventSource, les développeurs Web disposent désormais de normes claires et bien définies pour permettre les applications Web en temps réel. Cependant, tous les utilisateurs n'utilisent pas de navigateurs modernes. La dégradation élégante doit être prise en compte lorsque vous choisissez d'implémenter ces technologies. Les outils côté serveur pour WebSockets et EventSource en sont encore à leurs débuts. Il est important de garder ces facteurs à l'esprit lorsque vous développez des applications HTML5 en temps réel.