Faye, le pub/sub simplifié pour le Web

Publié le 4 juin 2013 par François Vaux | back

Cet article est publié sous licence CC BY-NC-SA

Les applications Web ont de plus en plus la nécessité d’être dynamiques et de se comporter comme des applications de bureau traditionnelles.

Du fait de la décentralisation des données et des traitements par rapport à l’utilisateur, il devient important de pouvoir interagir de façon bidirectionnelle entre le serveur et les clients.

Dans cet article nous allons voir comment Faye, une bibliothèque JavaScript et Ruby peut nous aider a écrire de telles applications.

Introduction

Faye est une bibliothèque divisée en deux parties : le serveur, disponible en JavaScript ou Ruby, et le client, disponible uniquement en JavaScript (puisque chargé dans le navigateur).

Cette bibliothèque permet d’implémenter le protocole de Bayeux dans votre application.

Pour faire court, ce protocole décrit une façon pour un serveur et plusieurs clients de mettre en place un modèle publish/subscribe par HTTP.

L’objectif étant d’établir des connexions persistantes, il faut une technologie le permettant. C’est la partie transport du protocole.

Faye assure cette partie en utilisant l’une des techniques suivantes :

  • WebSockets ;
  • Long-polling via un POST HTTP ;
  • CORS (Cross Origin Resource Sharing) ;
  • Callback-polling via JSON-P.

Comme un bon exemple est sûrement plus parlant, nous allons écrire une petite application de chat utilisant Faye.

Préparation

On commence par créer un dossier pour notre projet, puis après s’être déplacé dedans on installe les module node nécessaires :

$ npm install express faye

Le serveur

La partie serveur est plutôt simple, on va utiliser Express pour servir un dossier qui contiendra un fichier index.html où l’on écrira plus tard le code du client.

On écrit le fichier index.js qui, lancé avec node, démarrera l’application en écoutant sur localhost:3000 :

var faye     = require('faye'),
    express  = require('express'),
    bayeux   = new faye.NodeAdapter({mount: '/faye'})
    app      = express()

app.use(express.static(__dirname))
app.use(bayeux)

app.listen(3000)

On peut alors lancer ce fichier à l’aide de la commande node index.js et ouvrir son navigateur sur http://localhost:3000.

Il permet de servir les fichiers statiques du dossier avec Express, et d’avoir un serveur Faye qui écoute à l’adresse /faye.

Ce serveur simple ne fait rien à par permettre de relayer des messages entre les différents clients connectés.

Le client

Commençons par écrire l’interface de notre client de chat :

<!DOCTYPE HTML>
<html>
<head>
  <meta charset="utf-8" />
  <title>faye demo</title>
  <link rel="stylesheet" href="stylesheets/reset.css" type="text/css" media="screen" charset="utf-8" />
  <link rel="stylesheet" href="stylesheets/main.css" type="text/css" media="screen" charset="utf-8" />
</head>
<body>
  <div id="toolbar">
    <form action="/" name="chat-form">
      <input id="nick" name="nick" type="text" placeholder="nick" />
      <input id="message" name="message" type="text" placeholder="message" />
      <input type="submit" value="Send" />
    </form>
  </div>

  <dl id="message-list"></dl>

  <script type="text/javascript" src="faye/client.js"></script>
  <script type="text/javascript" src="script/client.js"></script>
</body>
</html>

Vous remarquerez que l’on inclut le script faye/client.js. Celui-ci est servi par le serveur que l’on a écrit précédemment.

Le code même du client est placé dans le fichier script/client.js :

;(function () {
  var client = new Faye.Client('/faye'), // (1)
      ui = {}

  ui.messageList  = document.getElementById('message-list') // (2)
  ui.chatForm     = document.forms['chat-form']
  ui.nickField    = document.getElementById('nick')
  ui.messageField = document.getElementById('message')

  function addMessage(message) { // (3)
    var dt = document.createElement('dt'),
        dd = document.createElement('dd'),
        frag = document.createDocumentFragment()

    dt.textContent = message.nick
    dd.textContent = message.message

    frag.appendChild(dt)
    frag.appendChild(dd)

    ui.messageList.appendChild(frag)
  }

  client.subscribe('/chat', function(message) { // (4)
    addMessage(message)

  })

  ui.chatForm.addEventListener('submit', function(event) { // (5)
    client.publish('/chat', {
      nick: ui.nickField.value,
      message: ui.messageField.value
    })

    ui.messageField.value = ""
    ui.messageField.focus()

    event.preventDefault()
  }, true)
})(window);

Détaillons le pas à pas.

  1. On initialise un nouveau client à l’aide du constructeur Faye.Client ;
  2. On stocke des références vers les différents éléments de l’interface pour simplifier leur modification par la suite ;
  3. La fonction addMessage permet d’ajouter une ligne de message dans la liste des messages reçus ;
  4. On s’abonne au canal /chat en passant une fonction de callback qui sera appelée à chaque message reçu sur ce canal et à laquelle le message sera passé en argument. Ici, cette fonction appelle simplement addMessage ;
  5. On ajoute un handler d’événement pour publier un message lorsque l’utilisateur valide le formulaire.

Vous voyez rapidement que la partie la plus compliquée est celle qui concerne la manipulation du DOM, et non la communication entre les clients.

Vous pouvez maintenant ouvrir deux navigateurs et accéder à http://localhost:3000 puis tester l’échange de message entre deux clients.

Extensions

Un mécanisme d’extensions permet de rajouter des fonctionnalités aux clients ou au serveur.

Nous allons rajouter un mécanisme de logging au serveur. Ceci est presque trivial !

On rajoute dans le fichier index.js, avant la configuration de l’application :

var consoleLogger = function(type) {
  return function(message, callback) {
    console.log(type + ': ' + JSON.stringify(message, null, 4))

    callback(message)
  }
}

var consoleLoggerExtension = {
  incoming: consoleLogger('incoming'),
  outgoing: consoleLogger('outgoing'),
}
bayeux.addExtension(consoleLoggerExtension)

Un extension est un objet possédant des propriétés incoming et/ou outgoing, permettant respectivement d’agir sur les messages entrant ou sortants (ici du côté du serveur).

Le mécanisme est strictement identique au niveau du client.

On peut notamment bloquer des messages en ajoutant un champ error sur l’objet du message.

Si vous relancez le serveur, vous verrez apparaître dans la console tous les messages reçus et émis par le serveur.

Pour aller plus loin…

La lecture du protocole de Bayeux est le point de départ pour pouvoir bâtir des applications plus conséquentes en utilisant Faye. Il est assez simple en soi et les concepts sont implémentés fidèlement dans Faye, à l’exception des canaux de service (entre un client et le serveur uniquement), l’auteur n’étant pas convaincu de leur intérêt.

On peut aussi obtenir un client côté serveur (server-side client) qui permet de publier sur les canaux auxquels les clients réguliers sont abonnés.

Le code de cette exemple est disponible sur notre compte GitHub, vous pouvez le télécharger librement et le modifier pour expérimenter avec Faye.

La documentation sur les extensions montre comment implémenter un système d’authentification basique pour que les clients ne voient que les messages auxquels ils ont accès.

Avec cette brique supplémentaire et la flexibilité de Faye, on peut développer des applications beaucoup plus riches faisant intervenir la communication entre clients.

Il faut enfin noter qu’une version Ruby du serveur est disponible dans le cas de l’utilisation au sein d’une stack Rack ou Rails. Elle reprend les mêmes fonctionnalités et quasiment la même API (en plus ruby-esque) que la version JavaScript.

L’équipe Synbioz.

Libres d’être ensemble.