Socket.io

Maintenant que nous avons une base avec un Hello World, nous allons rajouter une fonctionnalité pour que vous puissiez mieux comprendre le fonctionnement de socket.io dans cette application.

Avertissement

Dans cette section, nous verrons juste comment intégrer socket.io à notre Hello World pour que l’application reste bien structurée (le fonctionnement général de socket.io étant expliqué sous pré-requis).

Code côté serveur

Tout d’abord, nous allons créer un nouveau fichier nommé socket.js que nous mettrons dans le dossier serveur.

.
└── serveur
    ├── routes.js
    └── socket.js

Dans ce fichier, nous écrivons le code suivant :

./serveur/socket.js
// comme dans le fichier routes.js, la fonction principale est lancée depuis app.js
// nous passons le paramètre io, qui contient le module socket.io, depuis app.js
exports.f = function(io) {

  /* cette fonction de callback s'exécute à chaque fois qu'un client
  émet l'événement "connection" */
  io.on('connection', function (socket) {

    /* lorsque le client effectue un socket.emit('bouton_client'),
    cette fonction s'exécute */
    socket.on('bouton_client', function(){
      console.log('Le client a cliqué sur le bouton !')
    });

  });

}

Nous exécutons un require pour que app.js charge le module socket.io :

./app.js
var io = require('socket.io')(server);

Nous chargeons le fichier socket.js :

./app.js
var socket = require('./serveur/socket.js');

Puis, nous demandons à app.js d’exécuter la fonction que nous venons de créer en lui passant le module socket.io :

./app.js
socket.f(io);

Votre fichier app.js devrait alors ressembler à ceci :

./app.js
var http = require('http');
var path = require('path');
var express = require('express');

// nous faisons appel à expressJS en tapant app.nom_de_la_fonction()
var app = express();
var server = http.Server(app);
var io = require('socket.io')(server);

var routes = require('./serveur/routes.js');
var socket = require('./serveur/socket.js');

// appel de la fonction qui se trouve dans "routes.js"
routes.f(app, __dirname);
// appel de la fonction qui se trouve dans "socket.js"
socket.f(io);

server.listen(8888);

Important

Il est essentiel de respecter l’ordre des require. Comme vous pouvez le constater, certains modules dépendent d’autres modules. C’est par exemple le cas de socket.io qui a besoin de la variable server, qui nécessite elle-même la variable app d’expressJS pour fonctionner correctement. Si vous inversez l’ordre de ces instructions, des erreurs peuvent survenir.

Code côté client

Maintenant que le serveur peut faire fonctionner socket.io, il reste quelques modifications à effectuer du côté client. Nous commençons par ajouter une ligne au début du fichier general.js :

./static/js/general.js
var socket = io.connect('http://' + window.location.host);

Astuce

window.location.host contient le nom de domaine avec le port sur lequel est connecté le client. En l’occurence, cette commande équivaut à ceci :

var socket = io.connect('http://localhost:8888')

Ensuite, nous ouvrons un nouveau fichier pour séparer le javascript du HTML sur le côté client.

Question

Mais pourquoi ne pas utiliser general.js ?

Parce que ce fichier contient des fonctions javascript qui s’appliquent à toutes les pages de l’application. Dans notre cas, le socket.emit ne sert qu’à une seule page, index.ejs. Il faudra donc plutôt créer un fichier du nom de index.js :

Dans ce fichier, nous rédigeons une fonction avertit le serveur que le client a interagi avec la page web :

./static/js/index.js
function avertir_serveur() {
  socket.emit('bouton_client');
}

Ensuite, nous ajoutons un bouton qui exécutera cette fonction lorsqu’il sera cliqué sur la page index.ejs :

./views/index.ejs
<!-- la fonction avertir_serveur() s'exécute
lorsque nous cliquons sur ce bouton -->
<button class="btn btn-default" onclick="avertir_serveur()">
  <span class="glyphicon glyphicon-link"></span>
  Cliquez ici !
</button>

N’oublions pas de faire référence au fichier index.js que nous venons de réaliser en ajoutant cette ligne juste avant la fin de la balise head :

./views/index.ejs
<script src="static/js/index.js"></script>

Notre page index.ejs contient alors contenir ceci :

./views/index.ejs
<!DOCTYPE html>
<html>
  <head>
    <% include balise_head %>
    <script src="static/js/index.js"></script>
  </head>
  <body>
    <% include barre_navigation %>
    <div class="container">
      <div class="row">
        <div class="col-lg-offset-2 col-md-offset-1 col-md-10 col-lg-8">
          <p>Hello World !</p>
          <!-- la fonction avertir_serveur() s'exécute
          lorsque nous cliquons sur le bouton -->
          <button class="btn btn-default" onclick="avertir_serveur()">
            <span class="glyphicon glyphicon-link"></span>
            Cliquez ici !
          </button>
        <div class="col-lg-offset-2 col-md-offset-1"></div>
      </div>
    </div>
  </body>
</html>

Astuce

Vous remarquerez que nous faisons référence au fichier index.js juste après l’inclusion de la balise head. Alors pourquoi ne pas mettre cette ligne dans le fichier balise_head ? Tout simplement parce que la page index.ejs est la seule à avoir besoin de ce fichier. Il est donc inutile de demander aux autres pages de le charger alors qu’elles n’utilisent pas les fonctions que contient ce fichier.

Enfin, nous incluons le fichier socket.io.js dans balise_head.ejs pour que le client reconnaisse la fonction socket.emit() :

./views/balise_head.ejs
<script src="/socket.io/socket.io.js"></script>

Avertissement

Cette balise doit apparaître avant l’appel de general.js. Sinon, le navigateur ne trouvera pas la variable io lors de l’exécution de l’instruction qui permet d’initier la connexion socket.io entre le client et le serveur.

Résultat

Et voilà ! Maintentant, si nous relançons notre serveur, nous obtenons la page suivante :

_images/7.png

En cliquant sur le bouton, un message s’affiche dans la console du serveur :

Le client a cliqué sur le bouton !
Le client a cliqué sur le bouton !
...

Astuce

Vous pouvez télécharger le contenu du programme actuel en cliquant ici.