Créer une application web en temps réel.

3 janvier 2010

La nouvelle direction du web, c’est le temps réel. Dans cet article je vais vous montrer comment créer un compteur de visite en temps réel grâce au serveur de push APE. Si vous ne connaissez pas le principe de « push » je vous invite à lire la bande-dessinée suivante qui explique le principe. Pour comprendre cet article vous avez besoin de quelques connaissances en JavaScript. Dans mon exemple j’utiliserais le framework mootools, mais c’est un choix personnel, libre à vous d’utiliser votre framework préféré.

Je sais qu’on pourrait faire ce compteur de 10 façons différentes et que ce n’est pas le meilleur exemple pour une application en temps réel. Mais je voulais une application simple. Vous pouvez trouver d’autres démonstrations sur le site de APE ou ici.

Démonstration d’une application web en temps réel

Vous trouverez les sources de cette démonstration ici.

Le compteur de visite en temps réel

Ceci est l’application que nous allons réaliser dans cet article.

Installons un serveur de Push (APE)

Premièrement nous allons utiliser un serveur de Push, je vais prendre Ajax Push Engine. Comme je suis sous Linux, rien de plus simple. Je télécharge le .tar.gz à cette adresse.

Comme je n’ai qu’une IP sur ma machine je vais lancer APE sur le port 8080 (apache utilisant le port 80) en éditant le fichier de configuration situé dans « ape-server/bin/ape.conf » et ensuite démarrer APE via « ape-server/bin/aped ». Ci dessous ma configuration pour le serveur APE.

1
2
3
4
5
6
7
8
Server {
   port = 8080 # Port du serveur
   daemon = no # Lancer en service.
   ip_listen = 0.0.0.0 # (ne pas modifier) IP à écouter.
   domain = auto # (ne pas modifier) Configuration du domaine automatique.
   rlimit_nofile = 10000  # (ne pas modifier)
   pid_file = /var/run/aped.pid  # (ne pas modifier)
}

Pour vérifier le bon fonctionnement du serveur APE je vais via mon navigateur web sur http://{mon-ip}:8080/, une page s’affiche, victoire \o/. Il faut aussi configurer votre nom de domaine, dans cet exemple je fais pointer ape.dev.fy.to (l’adresse de mon aplication) et *.ape.dev.fy.to vers mon IP.

Installer le client JavaScript pour APE

Je télécharge le client à l’adresse suivante et l’extrait dans mon répertoire de travail. J’organise mes fichiers de la façon suivante:

  • Un répertoire « ape-jsf » avec le client APE.
  • Un répertoire « counter » avec les fichiers pour mon application

Dans mon répertoire counter je vais créer un fichier appelé config.js qui servira à la configuration du client:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Adresse des scripts du client.
APE.Config.baseUrl = 'http://ape.dev.fy.to/ape-jsf';

// Configuration du domaine automatique.
APE.Config.domain = 'auto';

// Adresse et port du serveur APE.
APE.Config.server = 'ape.dev.fy.to:8080';

// Liste des scripts :: Ne pas modifier
(function(){
   for (var i = 0; i < arguments.length; i++)
      APE.Config.scripts.push(APE.Config.baseUrl + '/Source/' + arguments[i] + '.js');
})('mootools-core', 'Core/APE', 'Core/Events', 'Core/Core', 'Pipe/Pipe', 'Pipe/PipeProxy', 'Pipe/PipeMulti', 'Pipe/PipeSingle', 'Request/Request','Request/Request.Stack', 'Request/Request.CycledStack', 'Transport/Transport.longPolling','Transport/Transport.SSE', 'Transport/Transport.XHRStreaming', 'Transport/Transport.JSONP', 'Core/Utility', 'Core/JSON');

Je vais également créer une classe JavaScript pour mon compteur avec le minimum requis (counter.js):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
APE.Counter = new Class({
   Extends: APE.Client,
   
   Implements: Options,

   options:{
      container: null,
      logs_limit: 10,
      container: document.body
   },

   initialize: function(options){
      // Mise à jour des options
      this.setOptions(options);

      // Une fois les scripts chargé on initialisez la connexion avec le serveur.
      this.addEvent('load', this.start);
   },

   start: function() {
      this.core.start();
   }
});

Et enfin mon fichier HTML situé à la racine de mon répertoire de travail (index.html):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" dir="ltr" lang="en">
<head>   
   <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
   <script type="text/javaScript" src="/ape-jsf/Clients/mootools-core.js"></script>
   <script type="text/javaScript" src="/ape-jsf/Clients/MooTools.js"></script>
   <script type="text/javaScript" src="/counter/config.js"></script>
   <script type="text/javaScript" src="/counter/counter.js"></script>
</head>
<body>
   <div id="ape"></div>
   <script type="text/javaScript">
      // Ajout de session.js (Multitab & actualisation)
      APE.Config.scripts.push(APE.Config.baseUrl+'/Source/Core/Session.js');

      // Initialisation de APE_Client
      var counter = new APE.Counter({'container':$('ape')});

      // Connexion au serveur APE
      counter.load({
         identifier: 'counter',
         channel: 'counter'
      });
   </script>
   </body>
</html>

Créer un système de compteur :: Coté serveur

Je vais devoir maintenant codé la partie serveur. Je vais donc éditer le fichier ape-server/scripts/main.ape.js. Je dois compter le nombre de visiteurs, et je vais aussi enregistré le nombre maximum en base de donnée. C’est surtout pour faire un exemple d’utilisation de MySQL même si dans cette application ce n’est pas réellement utile. Le code est relativement simple.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
Ape.addEvent("init", function() {
   include("framework/mootools.js");

   var Counter = new Class({
      count: 1,

      // On initialise le nombre maximum d'utilisateur en fonction de la base de donnée.
      initialize: function(idCounter) {
         this.id  = idCounter;
         this.sql = new Ape.MySQL("127.0.0.1:3306", "user", "password", "database"); // Connexion MySQL.

         this.sql.query("SELECT most FROM counter WHERE id="+this.id+" LIMIT 1", function(result) {
            this.most = result[0].most.toInt();
         }.bind(this));
      },

      // Mise à jour du compteur (+1);
      up: function() {
         this.count++;
         this.update();

         // On envoit les informations sur le channel "counter".
         var channel = Ape.getChannelByName('counter');
         if (channel) channel.pipe.sendRaw('count', {'count':this.count, 'most':count.most});
      },

      // Mise à jour du compteur (-1);
      down: function() {
         this.count--;

         // On envoit les informations sur le channel "counter".
         var channel = Ape.getChannelByName('counter');
         if (channel) channel.pipe.sendRaw('count', {'count':this.count, 'most':count.most});
      },

      // Vérification du nombre maximum d'utilisateur.
      update: function() {
         if (this.count > this.most) {
            this.most = this.count;
           
            // Si le maximum change on enregistre dans la base de donnée.
            this.sql.query('REPLACE INTO counter VALUES('+this.id+','+this.most+')', function() {});
         }
      }
   });

   var count = new Counter(1);

   // Envoie des informations à la restauration de session.
   Ape.registerHookCmd("SESSION", function(params, infos) {
      infos.user.pipe.sendRaw('count', {'count':count.count, 'most':count.most});
   });

   // Ajout d'un utilisateur :: Incrémentation du compteur & envoie des informations.
   Ape.addEvent('adduser', function(user) {
      user.pipe.sendRaw('count', {'count':count.count, 'most':count.most});
      count.up();
   })
   
   // Départ d'un utilisateur :: Décrémentation du compteur.
   Ape.addEvent('deluser', function(user) {
      count.down();
   });
});

Créer un système de compteur :: Coté client

Niveau client c’est assez simple il suffit de mettre à jour le texte quand le serveur envoie une information. On va premièrement modifier le HTML, faire un petit style CSS, histoire que le compteur soit beau. Vous trouverez le HTML ci-dessous.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
< !DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" dir="ltr" lang="en">
<head>   
   <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
   <script type="text/javaScript" src="/ape-jsf/Clients/mootools-core.js"></script>
   <script type="text/javaScript" src="/ape-jsf/Clients/MooTools.js"></script>
   <script type="text/javaScript" src="/counter/config.js"></script>
   <script type="text/javaScript" src="/counter/counter.js"></script>
   <style type="text/css">
      html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,button{margin:0;padding:0;border:0;outline:0;font-weight:normal;font-style:inherit;font-size:100%;font-family:inherit;vertical-align:baseline;}:focus{outline:0;}body{line-height:1;color:black;background:white;}ol,ul{list-style:none;}table{border-collapse:separate;border-spacing:0;}caption,th,td{text-align:left;font-weight:normal;}blockquote:before,blockquote:after,q:before,q:after{content:"";}blockquote,q{quotes:"""";}
      #counter { border: 1px solid #AAA; padding: 10px; background: #F1F1F1; width: 300px; line-height: 20px; }
      body { font-family: Verdana; font-size: 12px; }
      h2 { font-size: 16px; margin-bottom: 10px; text-align: center; font-family: Georgia; }
      #users,#max { font-weight: bold; }
   </style>
</head>
<body>
   <div id="ape"></div>
   <script type="text/javaScript">
      // Add Session.js to APE JSF to handle multitab and page refresh
      APE.Config.scripts.push(APE.Config.baseUrl+'/Source/Core/Session.js');

      // Initialize APE_Client
      var chat = new APE.Counter({'container':$('ape')});

      // Connect to the APE Server
      chat.load({
         identifier: 'counter',
         channel: 'counter'
      });
   </script>
   <div id="counter">
      <h2>Compteur de visites en temps réel</h2>
      <span id="users"></span> utilisateurs en ligne.<br />
      Un maximum de <span id="max"></span> utilisateurs simultanés.
   </div>

   </body>
</html>

Et enfin la partie JavaScript client ou il faut mettre à jour le nombre d’utilisateur en ligne et le nombre maximum quand le serveur le demande.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
APE.Counter = new Class({
   Extends: APE.Client,
   
   Implements: Options,

   options:{
      container: null,
      logs_limit: 10,
      container: document.body
   },

   initialize: function(options){
      // Mise à jour des options
      this.setOptions(options);

      // Une fois les scripts chargé.
      this.addEvent('load', this.start);

      // A la réception du raw "count" on appelle rawCount().
      this.onRaw('count', this.rawCount);
   },
   
   // A la réception du raw "count" on met à jour les compteurs.
   rawCount: function(raw, pipe) {
      this.update(raw.data.count, raw.data.most);
   },

   // Mise à jour des textes des compteurs.
   update: function(number, most) {
      $('users').set('text', number);
      $('max').set('text', most);
   },

   // Démarage de APE.
   start: function() {
      this.core.start();
   }
});

Télécharger les sources

Besoin d’aide?

Vous trouverez de la documentation sur APE à ces adresses:

Si vous avez des questions, un bout de code que vous ne comprenez pas n’hésitez pas à demander via les commentaires, j’éditerais l’article en fonction. J’espère avoir pu motiver certaines personnes au développement d’application avec APE :-)


Google Buzz :: Créer une application web en temps réel.
  1. FGRibreau dit :

    J’ai adoré lire cet article ça donne bien envie de tester.

    Cependant dans « Ape.addEvent(« init »…) »:
    L51 « infos.user.pipe »
    L56 « user.pipe »

    Tu n’expliques pas à quoi ils correspondent et j’avoue être un peu perdu. En réalité, j’aurai supprimé la ligne 56 afin d’envoyer (sur les événements « addUser » et « delUser ») les infos uniquement via le channel « counter ».

    Si tu pouvais m’éclairer :)

  2. En fait je ne voulais pas faire une documentation de l’API APE dans cet article. Mais dans le addUser j’envoie les informations à l’utilisateur (et uniquement à l’utilisateur) qui vient de se connecter au serveur. J’envoie le raw dès sa connexion pour que l’information soit disponible immédiatement au niveau du client. Les autres utilisateurs à ce moment là ont déjà l’information puisqu’elle est disponible sur le channel ou ils sont connectés (envoyée via Counter.up()).

    user.pipe :: http://www.ape-project.org/docs/server/users/user.pipe.html

  3. iMeee dit :

    J’aimerais bien savoir quels sont les avantages par rapport à XMLHttpRequest en JS ? Je l’utilise et sa fonctionne très bien ! Pas besoin d’un environnement dédié

  4. Phenix dit :

    Article très intéressant, ca faisait un moment que j’entends parler de APE et je comprends mieux le fonctionnement avec du concret.
    Mais il y a une petite notion qui me pose question: votre script est hébergé sur http://www.lezard-spock.com, or les appels ajax se font sur 1.ape.dev.fy.to. Normalement les appels Ajax sont bloqués au domaine sur lequel le script s’exécute, comment cela peut-il fonctionner avec un autre domaine?

  5. Ici c’est une iframe (le compteur est dans une frame qui va vers ape.dev.fy.to, mon ordi en fait :p). Si j’avais voulu faire directement le script sur lezard-spock j’aurais pu utiliser du JSONP (http://fy.to/ao8) car APE supporte ce mode de transport :)

  6. oxman dit :

    Je ne trouve pas l’exemple très pertinent car on peut facilement faire sans avoir à installer un soft (ici APE) en plus.

    Tu sais tout le bien que je pense de APE, je trouve juste qu’ici l’exemple n’est pas pertinent :)

  7. @oxman je sais bien mais je voulais choisir un exemple simple. Un compteur de visite c’était une bonne idée — Le résultat est simple. Je devrais peut être rajouté dans l’article d’autres démos.

  8. Phenix dit :

    Ok, j’aurai dû mieux regarder le code, merci pour la réponse! :)

  9. Selim dit :

    Merci pour l’article, j’attendais que ça pour tester :)

  10. Bonjour en fait j ai du mal à saisir, le compteur en temps réel dont vous parlez c’est pour afficher directement sur le site et visible de tout le monde ou bien plutot pour des stats comme google analytics?

  11. Fy- dit :

    C’est pour afficher directement sur le site, et c’est un exemple ;)

doFollow Répondre