Server-sent events

La communication entre un navigateur web (le client) et un site web (le serveur) se fait essentiellement dans un seul sens : du client vers le serveur, c'est-à-dire que c'est le navigateur web qui demande au serveur la ressource (page web, image, vidéo, etc.) à afficher. Mais il est parfois utile d'avoir l'inverse, que ce soit le serveur qui transmette de « son propre chef » une ressource au client. C'est à cela que servent les événements serveurs.

Présentation

Les événements serveur sont à utiliser quand on a besoin de ce canal de communication serveur ⇨ client et que la communication inverse, client ⇨ serveur est gérée classiquement par des requêtes http avec fetch ou XmlHttpRequest. Pour une communication bi-directionnelle, on préférera les WebSocket (qui nécessite cependant une autre connexion TCP et un protocole dédié). Une nouvelle spec, WebTransport, apportera aussi bientôt de nouvelles possibilités.

Utilisation dans le navigateur web

// Création d'une source d'événement pointant vers '/events'
const eventSource = new EventSource('/events');
// Écoute des messages provenant de cette source
eventSource.addEventListener('message', e => {
    // S'arrurer que le message vient du même domaine
    if (e.origin === globalThis.location.origin) {
        const data = e.data;
        // => do something with data
    }
});

Utilisation avec node.js

Une implémentation simple en node.js, sans avoir besoin de librairie tiers. D'abord création d'un serveur avec un RequestListener :

import http from 'node:http';

const server = http.createServer(requestListener);
server.listen(8000);

L'écouteur de requête doit gérer les requêtes pointant vers l'endpoint définit (/events) avec l'en-tête accept ayant pour valeur text/event-stream' :

const requestListener = (req, res) => {
    if (req.url === '/events' && req.headers.accept 
            && req.headers.accept === 'text/event-stream') {
        addSseClient(req.url, req, res);
    }
}

La gestion des « clients SSE » se fait à travers un tableau qui stocke les clients

const sseClients = [];
const addSseClient = (req, res) => {
    sseClients.push(new SSEClient(req, res, removeSseClient));
};
const removeSseClient = client => {
    sseClients = sseClients.filter(c => c.id !== client.id);
};

Le client SSE est un objet chargé de définir les en-êtes de la réponse http et s'assurer du nettoyage lors de la fermeture de la requête cliente :

class SSEClient {
    constructor(req, res, onclose) {
        this.id = Date.now();
        this.response = res;
        this.response.writeHead(200, {
            'Content-Type': 'text/event-stream',
            'Connection': 'keep-alive',
            'Cache-Control': 'no-cache',
        });
        req.on('close', () => {
            onclose(this);
            this.response = null;
            this.id = null;
        });
    }
}

Plus tard, sur une action quelconque, s'il y a besoin d'envoyer un événement au client, il suffit d'appeler une méthode pushEvent par exemple :

const pushEvent = data => {
    const message = `data: ${JSON.stringify(data)}\n\n`;
    sseClients.forEach(client => {
        client.response.write(message);
    });
};

Ressources et références

Titre
Stream Updates with Server-Sent Events
Auteurs
Eric Bidelman
Éditeur
web.dev
Date
Titre
Server-Sent Events- the alternative to WebSockets you should be using
Auteurs
@germanodev
Éditeur
germano.dev
Date
Titre
Server-Sent Events, WebSockets, and HTTP
Auteurs
Mark Nottingham
Éditeur
www.mnot.net
Date