É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 mises à jour des étages de la Chambre des représentants et du Sénat, des informations pertinentes, des tweets de membres du Congrès et d'autres informations sur les réseaux sociaux. L'application est conçue pour rester ouverte toute la journée, car elle reflète l'activité du Congrès.

Commencer avec WebSockets

La spécification WebSockets a fait l'objet de nombreuses recherches sur son fonctionnement: un socket TCP bidirectionnel 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 direct est clair et simple:

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

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

Bien que la prise en charge du navigateur WebSockets soit simple, la prise en charge côté serveur n'en est encore qu'à 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 disposent de EventMachine.

Présentation de Cramp

Cramp est un framework Web Ruby asynchrone qui s'exécute sur EventMachine. Il est écrit par Pratik Naik, un membre de l'équipe centrale de Ruby on Rails. Proposant un langage spécifique au domaine (DSL) pour les applications Web en temps réel, Cramp est un choix idéal pour les développeurs Web Ruby. Ceux qui connaissent bien l'écriture de 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 l'EventMachine non bloquant, il convient de tenir compte de plusieurs points:

  • Des pilotes de base de données non bloquants doivent être utilisés, comme MySQLPlus et em-mongo.
  • Vous devez utiliser des serveurs Web basés sur des événements. La prise en charge est intégrée aux modèles fin et arc-en-ciel.
  • L'application Cramp doit être exécutée séparément de l'application Rails principale sur laquelle repose Stream Congress. Elle doit être redémarrée et surveillée indépendamment.

Limites actuelles

WebSockets a subi un revers le 8 décembre 2010 lors de la publication d'une faille de sécurité. Firefox et Opera ont supprimé la compatibilité des navigateurs avec WebSockets. Bien qu'il n'existe pas de polyfill JavaScript pur, il existe une création de remplacement Flash qui a été largement adoptée. Toutefois, recourir à Flash est loin d'être idéal. Même si Chrome et Safari continuent à prendre en charge WebSockets, il est devenu clair que pour prendre en charge tous les navigateurs modernes sans recourir à Flash, WebSockets devait être remplacé.

Rollback vers le sondage AJAX

La décision a été prise d'abandonner WebSockets pour revenir aux sondages AJAX plus anciens. Bien que 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, l'application Cramp distincte n'est plus nécessaire. À la place, le point de terminaison AJAX a été fourni par l'application Rails. Le code côté client a été modifié pour permettre 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

Lorsque vous utilisez EventSource, gardez à l'esprit que les connexions interdomaines 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. Pour ce faire, vous pouvez utiliser un proxy au niveau du serveur Web. En supposant que l'application Cramp soit 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 sur streamcongress.com/live.

Polyfill stable

L'un des avantages les plus importants d'EventSource par rapport à WebSockets est que la création de remplacement est entièrement basée sur JavaScript, sans aucune dépendance à Flash. Pour ce faire, le polyfill de Remy Sharp est implémenté en implémentant l'interrogation longue dans les navigateurs qui ne sont pas compatibles avec EventSource de manière native. Par conséquent, EventSource fonctionne aujourd'hui sur tous les navigateurs récents dans lesquels JavaScript est activé.

Conclusion

Le HTML5 ouvre la voie à de nombreuses possibilités inédites et intéressantes en matière de développement Web. Avec WebSockets et EventSource, les développeurs Web disposent désormais de normes propres et bien définies pour permettre des applications Web en temps réel. Cependant, tous les utilisateurs ne disposent pas de navigateurs récents. La dégradation progressive doit être prise en compte lorsque vous choisissez d'implémenter ces technologies. De plus, 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.