/ Javascript

Partager une session express.js / redis avec socket.io

Si vous avez une application basée sur express.js et que vous utilisez socket.io pour faire de la communication en temps réel, tous les deux rattachés au même serveur HTTP(S), il peut être utile d'accéder, dans certains cas, aux sessions depuis un gestionnaire d'évènement de votre instance socket.io.

Dans cet article, je vais utiliser les modules suivants pour le partage et le stockage des sessions :

  • express-session (session middleware pour express)
  • socket.io-express-session (socket.io middleware pour express-session)
  • connect-redis (pour le stockage des sessions dans Redis)

Stockage des sessions

Par défaut avec express, les sessions sont stockées en mémoire (MemoryStore), ce qui n'est pas adapté pour une application en production ou si tout simplement on veut une persistance au niveau des sessions (prolongation de la durée de vie).

Socket.io 1.x middleware

Depuis la version 1.x de socket.io, les développeurs ont inclu le concept de middleware, c'est un moyen simple de fournir à chaque socket un élement, un objet utile qui peut être utilisé. Dans notre cas, il s'agit bien entendu des sessions créées par express que l'on va rendre disponible pour tous nos sockets en utilisant ce système de middleware.

io.use() est décrit ici dans la documentation officielle.

La propriété « handshake »

Côté serveur, tous les sockets possèdent une propriété « handshake » qui apparaît lors de leur création, c'est l'endroit le plus adapté pour y stocker nos sessions vu que cette propriété est disponible très rapidement et accessible depuis n'importe quel gestionnaire d'évènement comme ceci :

io.on("connection", function(socket) {
  console.log(socket.handshake);
});

Combiner express-session et socket.io avec les middlewares

Voici les signatures de nos middlewares, pour express :

function(req, res, next) {}

Pour socket.io 1.x :

function(socket, next) {}

Nous devons créer une fonction compatible avec io.use() qui appelle le middleware du module express-session :

var session = require("express-session");

app.use(session);

var socketmiddleware = function(socket, next) {
  session(socket.handshake, {}, next);
}

io.use(socketmiddleware);

io.on("connection", function(socket) {
  console.log(socket.handshake.session);
});

Cet exemple très simple, résume tout ce qui a été dit jusqu'à maintenant, on donne à express la session via app.use(), ensuite on crée la fonction socketmiddleware possèdant la signature décrite un peu plus haut, on retrouve en premier argument notre socket, ce qui va nous permettre de passer la propriété handshake au middleware d'express-session.

La suite est assez simple à comprendre.

Il existe un tout petit module que j'ai listé au début de l'article qui fait ça automatiquement pour nous, il est légitime de se demander si un tel module est utile vu le peu de code nécessaire à la réalisation du partage de session, à vous de voir.

Exemple avec Redis

Voici un exemple un peu plus complet, où je combine express-session, socket.io-express-session et connect-redis pour la persistance :

// Pour l'utilisation des Promises
var Promise    = require('bluebird');

var express    = require('express');
var Session    = require('express-session');
var redis      = require('redis');
var RedisStore = require('connect-redis')(Session);

var app    = express();
var server = require("http").createServer(app);
var io     = require("socket.io")(server);
var ios    = require('socket.io-express-session');

app.set('env', process.env.ENV || 'development');
app.set('port', process.env.PORT || 5000);
app.set(...);
...

// Création d'un client Redis pour l'accès à la base de donnée
var redisInit = function() {
  var client = redis.createClient(process.env.REDIS_URL);
  return client.onAsync('ready')
  .then(function() {
    return Promise.resolve(client);
  });
}

// Initialisation de Redis
redisInit().then(function(client) {

  // Initialisation de la session
  var session = Session({
    store:new RedisStore({ client:client }),
    secret:process.env.SESSION_SECRET,
    resave:true,
    saveUninitialized:true
  });

  // On passe nos sessions à socket.io
  io.use(ios(session));

  // Express middlewares (logger, bodyParser, cookieParser, serveStatic...)
  app.use({/* ... */});
  app.use({/* ... */});
  app.use(session);
  app.use({/* ... */});

  io.on('connection', function(socket) {

    console.log(socket.handshake.session);

    if( ! session.user ) {
      console.log("Diable, vous n'êtes pas connecté !");
      return;
    }

    var session = socket.handshake.session;
    session.user.socket = socket.id;
    ...

  });

  server.listen(app.get('port'));

});

Notez l'utilisation des Promises (j'utilise Bluebird mais il existe d'autres modules), que je conseille fortement pour éviter le callback hell (piramide de doom) typique de l'async. Les promises ne sont pas très utiles dans cet exemple, mais ça devient très pratique quand le code devient plus volumineux et que le nombre d'intructions à la suite s'accroît.

Partager une session express.js / redis avec socket.io
Share this