Introduction
Dans les applications Node.js en temps réel, une connexion WebSocket n'est pas un simple « tuyau ouvert ». C'est une entité vivante qui s'ouvre, envoie des données, peut mourir silencieusement et peut échouer de manière inattendue. Si votre code ne gère pas explicitement chaque phase de ce cycle de vie, votre serveur va accumuler des fuites de mémoire, accumuler des connexions zombies, et finir par planter. Cette leçon couvre le cycle de vie complet : l'événement open, l'événement close, l'événement error, et les pings heartbeat utilisés pour détecter les pairs morts.
Le cycle de vie de la connexion
Chaque connexion WebSocket dans Node.js passe par quatre états observables. La bibliothèque (ici nous utilisons le célèbre package `ws`) émet des événements auxquels vous pouvez vous abonner : `connection`, `message`, `close` et `error`. Un modèle courant consiste à regrouper tous les gestionnaires dans une seule fonction afin que chaque nouveau client bénéficie de la même gestion du cycle de vie.
Le `code` de fermeture suit la spécification WebSocket. Valeurs courantes : `1000` signifie une fermeture normale initiée par l'une ou l'autre des parties, `1001` signifie que le client navigue ailleurs, `1006` est la fameuse « abnormal closure » qui indique souvent que le réseau est tombé de manière inattendue.
Heartbeats : Détecter les clients inactifs
TCP ne vous indique pas quand un client a disparu silencieusement — la connexion reste simplement là, semi-ouverte, jusqu'à ce que quelque chose d'autre le remarque. Le protocole WebSocket résout ce problème grâce à des trames de contrôle appelées ping (opcode 0x9) et pong (opcode 0xA). Chaque côté peut envoyer un ping ; le pair doit répondre par un pong contenant la même charge utile. Si aucun pong n'arrive dans un délai imparti, la connexion est morte et doit être terminée.
La bibliothèque `ws` expose `ws.ping()`, `ws.pong()`, et une option `heartbeat` intégrée qui fait le gros du travail. Le modèle ci-dessous est l'implémentation canonique.
Comment cela fonctionne en pratique : au démarrage du serveur, `isAlive` est défini sur `true` pour chaque nouveau client. Toutes les 30 secondes, le serveur itère sur tous les clients. Si un client a déjà été marqué comme mort au tour précédent, `terminate()` est appelé, ce qui ferme de force la socket TCP sous-jacente. Sinon, le drapeau est réinitialisé et une trame ping est envoyée. Un client sain répond automatiquement par un pong, ce qui déclenche le gestionnaire `pong` et redéfinit `isAlive` sur `true`. Un client mort ne renvoie jamais de pong, donc au tour suivant il est terminé.
Gérer les erreurs avec élégance
L'événement `error` se déclenche pour les violations de protocole, les trames invalides et les défaillances de socket de bas niveau. Sans écouteur, un événement d'erreur non géré fera planter votre processus Node.js. Attachez-en toujours un, et associez-le à l'événement `close` car dans la plupart des cas, une erreur est immédiatement suivie d'une fermeture. Une règle utile : enregistrez l'erreur pour le diagnostic, mais n'essayez pas d'appeler `send()` à l'intérieur du gestionnaire d'erreur — la socket est peut-être déjà inutilisable.
La propriété `code` de l'erreur contient souvent des valeurs de niveau système comme `ECONNRESET` ou `EPIPE`. Ce sont des indices de débogage, et non une logique applicative à laquelle réagir.
Résumé
Gérer le cycle de vie d'une WebSocket est une discipline qui repose sur trois habitudes. Écoutez toujours `close` et `error` pour que les zombies et les défaillances soient nettoyés. Implémentez une boucle de heartbeats ping/pong avec un intervalle raisonnable (30 secondes est la norme) pour détecter les déconnexions silencieuses. Et ne laissez jamais un événement `error` non géré atteindre le comportement de plantage par défaut de Node.js. Ensemble, ces patterns maintiennent les serveurs temps réel de longue durée stables, même dans des conditions réseau instables et avec des milliers de clients simultanés.
Point de contrôle de la leçon
Révisez la leçon et recevez des conseils sur votre travail.