Comment tester devise ? réélement ?

Alors que j'ai cherché comment tester facilement Devise. J'ai indiqué une technique dans mon précédent post. Mais cette technique est loin d'être la meilleure. Voici donc la nouvelle solution, la solution officielle.

Il suffit d'inclure Devise::TestHelpers. Ensuite pour se logger avec un utilisateur, on utilise la méthode sign_in. La méthode sign_out existe aussi.

My JRuby talk at BlockCampParis

I was at BlockCampParis today and I gave a talk about JRuby. I'm not a JRuby expert so it was not very technical but the thing I wanted to demonstrate was that JRuby is a viable Ruby implementation with the possibility to use a lot of great librairies from Java.

I hope everyone enjoyed the talk and I'll probably complete it for a next time. You can see the presentation here and play with the examples.

My JRuby talk at BlockCampParis

I was at BlockCampParis today and I gave a talk about JRuby. I’m not a JRuby expert so it was not very technical but the thing I wanted to demonstrate was that JRuby is a viable Ruby implementation with the possibility to use a lot of great librairies from Java.

I hope everyone enjoyed the talk and I’ll probably complete it for a next time. You can see the presentation here and play with the examples.

Server side Faye?s client ou le protocole Bayeux

devDans mon précédent article, je vous ai montré comment mettre en place un Comet avec Capcode. J’ai découvert que certains d’entre vous lisent ce que j’écris1 par le biais de quelques mails dans ma boite aux lettres. L’un d’entre vous m’a même demandé comment faire pour envoyer des évènements sur le bus Comet sans passer par l’application Web. Bonne question, sachant qu’en effet, faye ne propose aucune solution pour cela. Et bien je me suis amusé à en développer une…

Faye

Pour comprendre ce que nous allons faire, il faut prendre le temps de regarder comment fonctionne faye. Mais avant, nous devons choisir la solution à adopter pour écrire notre librairie. En effet, nous avons deux choix possibles : soit nous pouvons parler directement au serveur faye, soit nous pouvons simuler le comportement d’un client. En fonction de ce choix, nous regarderons telle ou telle partie du code de faye. Bon et puisque c’est moi qui choisis2, j’opte pour la seconde solution.

Avant de décortiquer le code, rappelons le cheminement par l’utilisateur lors de l’utilisation de soapbox. Avant tout nous démarrons l’application3. Nous donnons notre username. Ensuite nous indiquons qui nous voulons suivre. Enfin nous racontons notre vie. Ces deux dernières étapes pouvant se faire dans n’importe quel ordre, et autant de fois que nous voulons.

Regardons maintenant le code. Lors de l’accès à l’application (route /) nous recevons la page mise en place par la vue views/index.rhtml. Dans cette vue, ce qui nous intéresse ce sont les lignes suivantes :

40
41
42
43
44
45
  <script type="text/javascript">
    Comet = new Faye.Client('/comet');
    Comet.connect();
 
    Soapbox.init(Comet);
  </script>

Nous créons un client Comet (ligne 41) pour le bus accessible via la route /comet puis nous établissons la connexion (ligne 42). Ensuite nous initialisons l’application (ligne 44). Si nous regardons à quoi correspondent la création et la connexion du client Comet, il faut se plonger dans le code du script comet.js. Ce fichier est généré lors de la création du gem faye à partir de l’ensemble des fichiers contenus dans le répertoire client des sources. Le fichier qui nous intéresse principalement ici est client/client.js. Dans ce fichier vous trouverez le code correspondant à la connexion entre les lignes 75 et 113.

Si nous regardons maintenant le fichier soapbox.js nous pouvons étudier ce qui se passe lors de l’initialisation de l’application. Nous voyons dans ce code que soapbox attend que l’utilisateur saisisse son username. Une fois ceci fait, l’application souscrit au channel /mentioning/<username>, elle masque la zone de saisie du username et affiche les zones de saisie des personnes à suivre et des messages. Elle met ensuite en place les actions correspondantes pour ces deux zones de saisie. La première de ces actions se traduit par la souscription au channel /from/<follow> avec comme callback : accept. La seconde revoie vers la méthode post dont le rôle principal est de publier le message sur le channel /from/<username>.

Outch !

Ce qui est important ici c’est de voir ce dont nous avons besoin pour faire cela. Nous en retiendrons donc la nécessité de développer une méthode de connexion, une méthode de souscription et une méthode de publication. Pour comprendre comment ces méthodes agissent, il suffit de regarder ce qui est fait dans les méthodes correspondantes de Faye.Client.

Je ne vais pas pouvoir, au risque de perdre plus de monde que ceux qui ont déjà lâché prise avant la fin du dernier paragraphe, entrer plus dans les détails. Il va donc falloir me croire sur parole ;) En fait, ce que nous allons faire c’est mettre en place une version cliente du protocole des échanges Comet appelé protocole Bayeux. Je vous engage donc à en lire la documentation si vous souhaitez approfondir le sujet.

Messages

Les échanges entre le client Comet et le bus peuvent se faire de deux façons, soit en long-polling soit en callback-polling. Dans notre cas, nous utilisons la première solution en faisant un envoie en POST du paramètre message, ce dernier ayant pour valeur une chaine JSON.

Mettons cela en place :

module Faye
  class Client
    def initialize( uri_or_string )
      @uri = uri_or_string
      @uri = URI.parse(@uri) if @uri.class == String
 
      # ...
 
    end
 
    # ...
 
    private
    def send( message )
      res = Net::HTTP.post_form( @uri, { "message" => message.to_json } )
      return JSON.parse( res.body )
    end
  end
end

Je place cette méthode en private car elle n’a d’intérêt que pour les méthodes que nous allons écrire ensuite.

Comme vous pouvez le voir, le retour est également une structure JSON.

« Handshake »

Si vous regardez précisément ce qui se passe lors de l’initialisation de la connexion du client Comet, vous verrez qu’en fait, il n’y a pas de connexion véritable, mais un handshake. Cela consiste en fait à se présenter au bus Comet et à obtenir de sa part, un identifiant. Nous devrons, par la suite toujours, utiliser cet identifiant pour communiquer avec le bus.

Le message du handshake doit contenir au moins les champs suivants :

  • channel : le channel.
  • version : la version du protocole.
  • supportedConnectionTypes : les types de connexions supportées.

Un channel est représenté comme un chemin sous la forme /foo/bar/... et doit systématiquement commencer par un /. Il en existe deux types : ceux définis par l’application et ceux réservés par le protocole. Ces derniers commencent toujours par /meta/. Dans le cas du handshake, le channel doit être /meta/handshake.

La version doit correspondre au numéro de version du protocole. 1.0 ici.

Le paramètre supportedConnectionTypes contient la liste des types de transports supportés. Bien que nous ayons dit plus haut que nous emploierons du long-polling, nous positionnerons la valeur ["long-polling", "callback-polling"]. Ceci simplement parce que si nous souhaitons implémenter le callback-polling nous n’aurons pas de modification à faire ;)

Voici donc à quoi ressemblera la méthode de handshake :

module Faye
  class Client
    def initialize( uri_or_string )
      @uri = uri_or_string
      @uri = URI.parse(@uri) if @uri.class == String
      @clientId = nil
      @interval = nil
 
      # ...
 
    end
 
    # ...
 
    def handshake
      id = Faye.random(32)
      message = {
        "channel" => Faye::Channel::HANDSHAKE,
        "version" => Faye::BAYEUX_VERSION,
        "supportedConnectionTypes" => [ "long-polling", "callback-polling" ],
        "id" => id
      }
 
      response = send( message )[0]
      if response["successful"] and response["id"] == id
        @clientId = response["clientId"]
        @interval = response["advice"]["interval"]
      else
        raise
      end
    end
 
    # ...
 
  end
end

Dans cette méthode j’utilise Faye::Channel::HANDSHAKE et Faye::BAYEUX_VERSION, deux constantes déclarées dans faye. Si vous souhaitez être indépendant de ce dernier, vous pouvez remplacer ces valeurs par celle que j’ai indiquée. Vous noterez également que j’ai ajouté dans le message le paramètre optionnel id. Ce paramètre permettra de valider, dans certains cas, que la réponse que nous recevons est bien celle attendu. En effet, si en retour nous retrouvons ce même ID c’est que, à priori tout va bien. Je dis bien « à priori »…

Je vous ai indiqué que la raison d’être de ce handshake était de récupérer un identifiant client. C’est ce que nous faisons en parsant la réponse. Cette réponse peut avoir deux structures différentes en fonctions ou non de la présence d’erreur. Je ne détaillerai pas la structure complète, retenez seulement quand dans le cas présent nous devons avoir récupéré les données suivantes :

  • successful : suffisament parlant je pense ;)
  • id : qui doit correspondre à l’ID envoyé.
  • clientId : le clientId a utiliser par la suite.

Je ne parlerai pas de la valeur interval que je récupère simplement en pensant à une prochaine évolution de ce petit développement…

Connexion

La connexion se fait en envoyant un message contenant les données suivantes :

  • channel : dans le cas d’une connexion nous utiliserons /meta/connect qui sera ici récupéré via la constante Faye::Channel::CONNECT.
  • clientId : est la valeur récupérée lors du handshake
  • connectionType : je n’y reviens pas, mais vous l’avez compris, nous faisons du long-polling.
  • id : là encore, ce n’est que mimétisme.

Voici donc comment nous pouvons implémenter cela :

module Faye
  class Client
    def initialize( uri_or_string )
      @uri = uri_or_string
      @uri = URI.parse(@uri) if @uri.class == String
      @clientId = nil
      @interval = nil
 
      # ...
 
    end
 
    # ...
 
    def connect
      id = Faye.random(32)
      message = {
        "channel" => Faye::Channel::CONNECT,
        "clientId" => @clientId,
        "connectionType" => "long-polling",
        "id" => id
      }
      r = send( message )
 
      # ...
 
    end
  end
end

A ce niveau, si vous faites un petit test, vous vous rendrez compte que la connexion attend une réponse de la part du serveur. En effet cette connexion attend que le bus Comet envoie des données. C’est ainsi que nous simulons le push ! Ce sont ces données que nous devrons donc traiter en fonction des souscriptions.

Pour savoir si le retour est exact, nous devons retrouver dans la structure JSON le champ successful à true et le champ id avec la même valeur que celle envoyée.

Si tout se passe bien, alors nous pouvons récupérer les données du message dans la structure data :

module Faye
  class Client
    def initialize( uri_or_string )
      @uri = uri_or_string
      @uri = URI.parse(@uri) if @uri.class == String
      @clientId = nil
      @interval = nil
 
      # ...
 
    end
 
    # ...
 
    def connect
      id = Faye.random(32)
      message = {
        "channel" => Faye::Channel::CONNECT,
        "clientId" => @clientId,
        "connectionType" => "long-polling",
        "id" => id
      }
      r = send( message )
 
      if r[0]["id"] == id and r[0]["successful"] == true
 
        # traitement du message contenu dans r[1]["data"] pour la souscription au channel r[1]["channel"]
        # ...
 
      elsif r[0]["successful"] == false
 
        # ...
 
      end
 
      # ...
 
    end
 
    # ...
 
  end
end

Nous verrons au paragraphe souscription comment traiter le message de réponse. Avant cela, détaillons ce que nous devons faire en cas d’échec. Le protocole Bayeux nous indique que dans un tel cas, il faut refaire un handshake puis refaire la connexion. Cependant, comme la connexion elle-même est bloquante, dans le sens où elle attend une réponse, il serait bon de l’isoler dans un thread afin de permettre le déroulement de notre programme. De plus, si la connexion est un succès, une fois le message traité, il faut en rouvrir une de façon à se remettre en attente d’un nouveau push du serveur. Pour cela nous pouvons placer la connexion dans une boucle infinie.

Voici donc comment nous allons gérer cela :

module Faye
  class Client
    def initialize( uri_or_string )
      @uri = uri_or_string
      @uri = URI.parse(@uri) if @uri.class == String
      @clientId = nil
      @interval = nil
      @connection = nil
 
      # ...
 
    end
 
    # ...
 
    def connect
      @connection.kill unless @connection.nil?
      @connection = Thread.new {
        faild = false
        while true
          id = Faye.random(32)
          message = {
            "channel" => Faye::Channel::CONNECT,
            "clientId" => @clientId,
            "connectionType" => "long-polling",
            "id" => id
          }
          r = send( message )
 
          if r[0]["id"] == id and r[0]["successful"] == true
 
            # traitement du message contenu dans r[1]["data"] pour la souscription au channel r[1]["channel"]
            # ...
 
          elsif r[0]["successful"] == false
            faild = true
            break
          end
        end
 
        if faild
          handshake()
          connect()
        end
      }
    end
 
    # ...
 
  end
end

Souscription

La souscription se fait pour un ou plusieurs channels avec, en second paramètre, la méthode qui doit être utilisée pour traiter les massages venant de ces channels. Nous avons déjà parlé des channels. Bien entendu dans le cas présent il s’agira de channels spécifiques à l’application. Concernant le callback, nous utiliserons un block.

Le message envoyé au bus Comet doit contenir les informations suivantes :

  • channel : il ne s’agit pas, ici, du channel auquel nous souhaitons souscrire, mais celui utilisé par le protocole pour déclarer une souscription : /meta/subscribe, ou, dans notre cas ou nous utilisons faye : Faye::Channel::SUBSCRIBE.
  • clientId : le client ID récupéré lors du handshake.
  • subscription : ce paramètre prend en valeur un tableau des channels auxquels nous souhaitons souscrire.
  • id : l’id du message.

Comme vous pouvez le voir, nulle part nous ne stockons le callback à utiliser pour traiter les messages renvoyés. C’est normal puisque le bus Comet n’en à que faire puisqu’il seront exécutés côté client. Il nous appartient donc de les stocker. Nous ferons cela en mettant en place un hashage ayant pour clé le channel et comme valeur le block.

module Faye
  class Client    
    def initialize( uri_or_string )
      @uri = uri_or_string
      @uri = URI.parse(@uri) if @uri.class == String
      @clientId = nil
      @interval = nil
      @connection = nil
      @subscriptions = {}
    end
 
    # ...
 
    def subscribe( channels, &block )
      channels = [channels] unless channels.class == Array
      if block
        channels.each do |c|
          @subscriptions[c] = block
        end
      end
      message = {
        "channel" => Faye::Channel::SUBSCRIBE,
        "clientId" => @clientId,
        "subscription" => channels,
        "id" => Faye.random(32)
      }
 
      r = send(message)
    end
 
    # ...
 
  end
end

Je ne prends pas la peine de gérer le retour. Ce n’est pas bien !

Connexion

Nous pouvons maintenant revenir sur la connexion afin de traiter les messages. Il suffit donc de passer le message au block correspond au channel qui l’a poussé :

    # ...
 
    def connect
      @connection.kill unless @connection.nil?
      @connection = Thread.new {
        faild = false
        while true
          id = Faye.random(32)
          message = {
            "channel" => Faye::Channel::CONNECT,
            "clientId" => @clientId,
            "connectionType" => "long-polling",
            "id" => id
          }
          r = send( message )
 
          if r[0]["id"] == id and r[0]["successful"] == true
            @subscriptions[r[1]["channel"]].call( r[1]["data"])
          elsif r[0]["successful"] == false
            faild = true
            break
          end
        end
 
        if faild
          handshake()
          connect()
        end
      }
    end
 
    # ...

Publication

La publication se fait par l’envoi d’un message contenant les champs suivants :

  • channel : le channel destinataire du message.
  • data : les données, sous forme d’une structure JSON.
  • clientId : le fameux client ID.
  • id : par habitude ;)

Ceci nous donne donc le code suivant :

module Faye
  class Client
 
    # ...
 
    def publish( channel, data )
      message = [
        {
          "channel" => channel,
          "data" => data, 
          "clientId" => @clientId,
          "id" => Faye.random(32)
        }
      ]
      r = send(message)[0]
    end
 
    # ...
 
  end
end

Encore une fois, je ne prends pas le temps de traiter la réponse… Encore une fois, ce n’est pas bien !!!

… et leur contraire

Là où il y a connexion, il y a forcement « déconnexion », de même la où il y a souscription, il y a désabonnement. Je pense que vous avez compris le principe, et je me permets donc de vous livrer sans autre explication les méthodes correspondantes :

module Faye
  class Client
 
    # ...
 
    def disconnect
      unless @connection.nil?
        @connection.kill 
        message = {
          "channel" => Faye::Channel::DISCONNECT,
          "clientId" => @clientId,
          "id" => Faye.random(32)
        }
        r = send( message )
      end
    end
 
    # ...
 
    def unsubscribe( channels )
      channels = [channels] unless channels.class == Array
      channels.each do |c|
        @subscriptions.delete(c)
      end
      message = {
        "channel" => Faye::Channel::UNSUBSCRIBE,
        "clientId" => @clientId,
        "subscription" => channels,
        "id" => Faye.random(32)
      }
 
      r = send(message)
    end
 
    # ...
 
  end
end

Let’s play !

Maintenant que nous avons notre librairie cliente, nous pouvons développer un petit client en ligne de commande pour soapbox.

x = Faye::Client.new( 'http://localhost:3000/comet' )
 
puts "-- handshake"
x.handshake
 
puts "-- subscriptions"
x.subscribe( "/mentioning/daemon" )
 
x.subscribe( "/from/greg" ) { |r|
  puts "#{r["user"]} : #{r["message"]}"
}
 
puts "-- connect"
x.connect
 
msg = ""  
while msg != "quit"
  msg = $stdin.readline.chomp
  unless msg == "quit"
    channel = "/from/daemon"
    data = { "user" => "daemon", "message" => msg }
    r = x.publish( channel, data )
    unless r["successful"]
      puts "=> Message not send !"
    end
  end
end
 
x.disconnect

Bon, OK, c’est très très rudimentaire, mais vous avez compris ! Et le principal c’est que cela fonctionne :

soapbox-console

A+

Attention, n’oubliez pas la remarque faite sur la page du projet faye : it’s a toy. Donc, tout comme ce qui précède, n’utilisez pas cela en production4. Et bien cela s’applique, à plus forte raison, à ce qui est écrit ci-dessus. Maintenant, si vous êtes joueur, vous pouvez récupérer cela dans les sources de Capcode ou ici.

Notez que si je me suis attaché à faye, ce petit client devrait pouvoir fonctionner avec toute solution Comet respectant le protocole Bayeux.

1 Merci !
2 :P
3 Ca peut paraitre idiot, cela n’en reste pas moins vrai !
4 On vous aura prévenu !

Server side Faye’s client ou le protocole Bayeux

BlockCampParis

BlockCampParis

There's gonna be a Smalltalk and Ruby Barcamp named BlockCampParis on November 28th. I will talk about JRuby and the (many) things you can do with it.

Some links:

BlockCampParis

There’s gonna be a Smalltalk and Ruby Barcamp named BlockCampParis on November 28th. I will talk about JRuby and the (many) things you can do with it.

Some links:

Notification, Comet, ? Capcode

devDepuis que nous développons des sites Web, nous avons pris l’habitude du mode connecté imposé par le protocole HTTP. Rien ne nous surprend plus quand nos serveurs ne répondent qu’à des demandes explicites. Et nous vivons avec l’habitude de ce dialogue de sourds ou le serveur répond à une requête en oubliant totalement ce qu’il a dit à la demande précédente. Nous pallions généralement à ce problème en utilisant des principes de session et autre cookie, mais n’oublions pas que ces artefacts sont en fait gérés par le client à qui le serveur demande de stocker des souvenirs. Depuis l’apparition des frameworks JavaScript comme Dojo, prototype, jQuery et consorts, nous mettons de plus en plus de mécanismes dans les pages Web qui évitent de reloader toute une page. Là encore, cela ne fait plus rêver personne et nous restons toujours dans un mode requête/réponse. Là où nous écarquillons un peu plus les yeux, c’est quand une page Web affiche, sans que nous n’ayons rien demandé, de nouvelles informations. C’est tout le principe des notifications et c’est ce que nous allons mettre en place ici.

Notifications ?

Une notification intervient quand un serveur envoie des données au client quand il en a envie, et donc sans aucune demande du client. En dehors du Web, tous les systèmes de messagerie instantanés, par exemple, fonctionnent sur ce principe. Mais de par son côté stateful, le protocole HTTP ne permet pas ce genre de chose. Faux ! me direz-vous. La preuve par Google, ou plutôt Google Mail qui non seulement ne nous oblige pas à reloader sa page pour découvrir de nouveaux messages, mais qui en plus propose un service de messagerie instantanée aussi efficace que n’importe quel Skype ou MSN ! Et bien malheureusement, c’est vrai. Et si nous arrivons à faire de la notification via le Web ce n’est que par le truchement de bidouillages plus ou moins subtils qui vont permettre de le simuler. Depuis plusieurs années, nous désignons les mécanismes permettant de faire du push HTTP sous le terme Comet, inventé par Alex Russell.

Comet

Comet est une désignation peu claire. En effet, personne ne sait très bien ce que recouvre ce terme. Globalement on lui attribut tous les mécanismes permettant de faire de la notification.

Oui mais comment faire cela justement ?

Dans sont fonctionnement classique, un serveur HTTP attend une requête du client et renvoie une réponse :

ClassicHTTP

Avec AJAX, nous avons rajouté, côté client, une couche entre l’utilisateur et le serveur. C’est elle qui reçoit les demandes du client, qui les transmet au serveur, qui reçoit la réponse et qui se charge de mettre à jour la page. Ceci fait que les chargements sur la page ne sont pas forcement synchronisés avec les échanges qui ont lieu entre le moteur AJAX et le serveur :

AJAXHTTP

Cela permet ainsi de mettre à jour des informations dans la page sans tout recharger. Avec AJAX, nous pouvons déjà simuler un comportement de notifications. En effet, il suffit de demander au moteur AJAX d’envoyer une requête, à intervalle régulier au serveur, et si ce dernier à une nouvelle information, de l’afficher. Mais ce n’est pas du Comet. En effet, dans ce cas, ce n’est pas le serveur qui est à l’initiative de l’envoi de données. De plus, l’envoi de données par le serveur est rythmé par les demandes du client. Et même si elles peuvent être très rapprochées, elles ne donneront pas toujours le même sentiment de fluidité que nous retrouverons avec un Comet bien fait.

Avec Comet, nous allons remplacer le serveur AJAX pas un client Comet qui se placera entre le client et le serveur. Côté serveur, nous allons placer un bus Comet chargé de communiquer avec le service :

CometHTTP

Côté client, la connexion Comet est initialisée. A partir de là, le service enverra des évènements au bus Comet qui se chargera d’envoyer les informations nécessaires au client Comet qui à son tour mettra à jour la page Web. Bien entendu le client peut lui même envoyer de l’information au service par l’intermédiaire du client puis du bus Comet. Vous l’aurez compris, nous utilisons aussi de l’AJAX. Quant au service, cela peut être n’importe quoi : un service de messagerie instantané, un serveur de données, … bref n’importe quel service capable lui-même de pousser des données.

Capcode

Avec Capcode nous pouvons faire du Comet en utilisant un middleware Rack. Personnellement j’en ai trouvé 2 : Pusher et faye. Bien qu’un peu moins aboutit que le premier, je vous propose d’utiliser faye.

faye arrive avec un exemple sous forme de messagerie instantanée à la Twitter. Pour valider le fonctionnement de faye avec Capcode, je vous propose d’adapter cet exemple.

soapbox est écrit comme une application Sinatra. Le code de l’application est minimaliste :

require 'rubygems'
require 'sinatra'
 
get '/' do
  @server = env['faye.server']
  erb :index
end

La seule chose remarquable ici est le fait que lors de l’accès à la racine de l’application, le serveur et modifié pour devenir le serveur faye et que nous renvoyons la page index.erb. Transformer cela en code Capcode est ultra simple :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
require 'rubygems'
require 'capcode'
require 'capcode/render/erb'
 
module Capcode
  set :erb, "views"
 
  class Index < Route '/'
    def get
      @server = env['faye.server']
      render :erb => :index
    end
  end
end

Pour respecter ce qui est fait, nous créons un répertoire views dans lequel nous placerons les templates Erb, tout cela sans oublié de le déclarer (ligne 6).

Si nous regardons maintenant le fichier de configuration pour Rack, nous avons cela :

dir = File.dirname(__FILE__)
 
require dir + '/../../lib/faye'
require dir + '/app'
 
use Faye::RackAdapter, :mount => '/comet'
run Sinatra::Application

Ajouter cela dans Capcode est là encore trivial :

require 'rubygems'
require 'capcode'
require 'capcode/render/erb'
require 'faye'
 
module Capcode
  set :erb, "views"
  use Faye::RackAdapter, :mount => '/comet'
 
  class Index < Route '/'
    def get
      @server = env['faye.server']
      render :erb => :index
    end
  end  
end

Si maintenant nous regardons le template Erb nous voyons qu’il utilise une CSS et trois fichiers JavaScript :

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
 
<html>
<head>
  <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
  <title>Faye demo: chat client</title>
  <link rel="stylesheet" href="/style.css" type="text/css" media="screen">
  <script src="/jquery.js" type="text/javascript"></script>
  <script src="/comet.js" type="text/javascript"></script>
  <script src="/soapbox.js" type="text/javascript"></script>
</head>
<body>
<div class="container">
...

Les fichiers style.css, jquery.js et soapbox.js se trouvent dans le répertoire public. Nous devons donc créer ce même répertoire et déclarer son existence dans Capcode, puis ajouter un contrôleur permettant de charger les fichiers :

require 'rubygems'
require 'capcode'
require 'capcode/render/erb'
require 'capcode/render/static'
require 'faye'
 
module Capcode
  set :erb, "views"
  set :static, "public"
  use Faye::RackAdapter, :mount => '/comet'
 
  class Index < Route '/'
    def get
      @server = env['faye.server']
      render :erb => :index
    end
  end
 
  class Static < Route '/public/(.*)'
    def get( f )
      render :static => f
    end
  end
end

Puis nous modifions le template Erb :

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
 
<html>
<head>
  <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
  <title>Faye demo: chat client</title>
  <link rel="stylesheet" href="/public/style.css" type="text/css" media="screen">
  <script src="/public/jquery.js" type="text/javascript"></script>
  <script src="/comet.js" type="text/javascript"></script>
  <script src="/public/soapbox.js" type="text/javascript"></script>
</head>
<body>
<div class="container">
...

Oui, mais le fichier comet.js ??? Et bien si vous regardez la documentation de faye, vous verrez que le simple fait de déclarer l’utilisation du middleware faye met automatiquement en place un bus Comet accessible via la route /comet et met à disposition la librairie JavaScript cliente via la route /comet.js. Donc, nous n’avons rien de plus à faire pour ce fichier.

Vous pouvez maintenant démarrer l’application et tester :

comet-example

Cet exemple a été ajouté dans les sources de Capcode.

Toute l’intelligence de l’exemple se trouve non seulement fans faye mais également dans le fichier soapbox.js que je vous engage vivement à regarder, tout comme la documentation de faye.

Notification, Comet, … Capcode

BarCamp Ruby et Smalltalk (BlockCamp Paris) samedi 28 novembre 2009

Les inscriptions pour le BlockCamp Paris 2 sont ouvertes !

L’association Ruby France, l’ESUG , l’INSIA et AF83 ont le plaisir de vous annoncer l’organisation d’un BarCamp consacré à Ruby et Smalltalk dans les locaux de l’INSIA à Paris, le samedi 28 novembre 2009.

Le thème de cette non-conférence sera consacré bien sûr à Ruby,et ce sera l’occasion de découvrir également le monde Smalltalk : Squeak, Pharo, Seaside… Nous aurons ainsi parmi nous la présence de Lukas Renggli, committer Seaside (framework web en Squeak).

L’événement est gratuit et ouvert à tous, des Rubyistes débutants aux Rubyistes confirmés. Il suffit de s’inscrire (attention à la limite maximum !) et toutes les explications et les informations (code d’accès pour entrer…) sont sur la page du wiki BarCamp. L’inscription s’effectue par ailleurs ici

N’oubliez pas que vous pouvez participer en proposant des présentations (en éditant le wiki)(implémentations de Ruby, Ruby et le desktop, bibliothèques Ruby), en en parlant autour de vous ou sur le Net (blog, Twitter, etc.) en nous faisant des suggestions, en devenant sponsor !