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

Luigi Montanez
Luigi Montanez

Introduction

Grâce à 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. Il diffuse des informations sur les débats à la Chambre et au Sénat, des actualités pertinentes, des tweets de 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 attiré beaucoup d'attention pour ce qu'elle permet: un socket TCP bidirectionnel et stable 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 adapté aux 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 de Cramp

Cramp est un framework Web Ruby asynchrone qui s'exécute sur EventMachine. Il a été é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. Les personnes qui savent écrire des contrôleurs en 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 compatibilité est intégrée pour 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, un remplacement Flash a été largement adopté. Cependant, s'appuyer sur Flash est loin d'être idéal. Même si Chrome et Safari continuent de prendre en charge WebSockets, il est devenu clair qu'il fallait remplacer WebSockets pour être compatible avec tous les navigateurs modernes sans dépendre de Flash.

Revenir à l'interrogation AJAX

Nous avons décidé d'abandonner WebSockets et de revenir à l'ancienne méthode de requête AJAX. Bien que beaucoup moins efficace du point de vue des E/S disque et réseau, le sondage AJAX a simplifié l'implémentation 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.

Polyfill stable

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

Conclusion

Le langage HTML5 ouvre de nombreuses possibilités de développement Web nouvelles et passionnantes. 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 aux premiers stades. Il est important de garder ces facteurs à l'esprit lorsque vous développez des applications HTML5 en temps réel.