
Le rapport, quand il arrive enfin, c’est que la page a cessé de se mettre à jour. Aucune erreur n’a été affichée. Les chiffres ont simplement cessé de bouger, et un rechargement l’a corrigé.
C’est pourquoi vous en entendez rarement parler. La personne qui rencontre cela a une réparation fonctionnelle en main en quelques secondes, donc la plupart d’entre eux ne vous le disent jamais. Ceux qui vous le disent n’ont rien à décrire sauf une absence.
Un socket fermé n’est pas une erreur
La première chose à désapprendre est que cela arrivera comme une demande échouée. Ce ne sera pas le cas.
Un WebSocket déclenche bien un événement error, et MDN décrit son type en trois mots : « Un Event générique. » Il n’y a pas d’interface personnalisée en dessous, ce qui signifie pas de code, pas de raison et pas de statut sur l’objet qui vous est remis. Rien ne lève une exception, donc rien n’atteint un try/catch. La connexion se termine, et la seule chose portant l’information est l’événement close qui suit.
Donc, le code de fermeture n’est pas un détail. C’est l’ensemble du message.
Les codes qui signifient quelque chose
Quatre méritent d’être connus par cœur, selon les propres termes de MDN.
- 1000, fermeture normale : « La connexion a complété avec succès l’objectif pour lequel elle a été créée. » Rien à investiguer.
- 1001, départ : « Le point de terminaison s’en va, soit à cause d’une défaillance du serveur, soit parce que le navigateur s’éloigne de la page qui a ouvert la connexion. »
- 1011, erreur interne : « Le serveur termine la connexion parce qu’il a rencontré une condition inattendue qui l’a empêché de remplir la demande. »
- 1006, fermeture anormale, qui est celui que vous verrez réellement et dont le reste de cet article parle.
Lisez 1001 deux fois, car il couvre deux événements non liés avec un seul numéro. Un serveur qui s’arrête et un utilisateur qui clique sur un lien produisent tous les deux. Si vous comptez les 1001 comme des incidents, vous comptez les gens qui quittent votre site comme des incidents, et le nombre suivra votre trafic plutôt que votre fiabilité.
1011 est l’opposé et c’est celui qui mérite d’être alerté. C’est le serveur admettant la faute dans ses propres termes.
Le code que vous voyez le plus ne pouvait venir de personne
Voici la partie qui n’est presque jamais écrite, et elle change votre façon de investiguer.
RFC 6455 définit 1006 comme ceci :
1006 est une valeur réservée et NE DOIT PAS être définie comme code de statut dans une trame de contrôle Close par un point de terminaison. Elle est désignée pour une utilisation dans les applications attendant un code de statut pour indiquer que la connexion a été fermée anormalement, par exemple, sans envoyer ou recevoir de trame Close.
1005 porte la même interdiction : « 1005 est une valeur réservée et NE DOIT PAS être définie comme code de statut dans une trame de contrôle Close par un point de terminaison. »
Donc aucun d’eux ne peut être envoyé. Ni par votre serveur, ni par le navigateur, ni par rien entre les deux. Ils sont interdits sur le fil. Quand votre gestionnaire lit 1006, il lit un numéro que votre propre navigateur a inventé localement pour décrire le fait que la connexion s’est terminée et qu’aucune trame de fermeture n’est jamais arrivée.
C’est une affirmation plus forte qu’elle n’y paraît. Cela signifie qu’il n’y a pas d’événement correspondant ailleurs à aller chercher.
- Ce que les gens cherchent
- Une ligne de log du serveur expliquant le 1006, en supposant que le serveur l'a envoyé
- Ce qui existe
- Rien. Le code a été généré dans le navigateur. Le serveur peut ne pas savoir que la connexion est partie, et ce qui l'a tuée n'est souvent pas le serveur du tout
1006 n’est donc pas une cause. C’est la forme de la preuve : quelque chose entre les deux extrémités a cessé de relayer, et aucune extrémité n’a pu dire au revoir.
Qui ferme réellement une connexion que personne n’a fermée
Une fois que vous acceptez que le navigateur signale le silence, les suspects sont les choses qui se tiennent au milieu et ont des opinions sur les connexions inactives.
Un proxy inverse ou un équilibreur de charge avec un délai d’inactivité est le courant. Ces délais d’expiration sont configurables et souvent courts, et un WebSocket qui ne transporte rien entre les mises à jour ressemble exactement à une connexion abandonnée. L’équipement réseau d’entreprise fait de même, plus agressivement. Un téléphone passant du wifi à la donnée mobile change l’adresse du client sous un socket ouvert, ce qui le termine sans cérémonie. Les ordinateurs portables en suspension font de même.
Notez ce que tous ces cas ont en commun : votre code d’application est correct, votre serveur est sain, et rien ne va pas avec les données. Le transport a disparu. C’est aussi pourquoi cela touche certains utilisateurs constamment et d’autres jamais, ce qui est la signature qui renvoie un rapport de bug marqué « impossible à reproduire ».
La défense standard est un battement : envoyer quelque chose de petit sur une minuterie, plus souvent que le délai d’inactivité le plus court entre vous et vos utilisateurs. Cela ne répare pas une connexion interrompue. Cela empêche une connexion inactive d’être confondue avec une morte, qui est un problème différent qui produit le même rapport.
Quoi enregistrer, car l’événement de fermeture est le seul témoin
L’événement de fermeture se produit une fois, dans le navigateur, puis la page se reconnecte généralement et la preuve disparaît. Quatre champs valent la peine d’être capturés à ce moment.
socket.addEventListener('close', (event) => {
report({
code: event.code, // 1006 signifie que personne n'a envoyé de code
wasClean: event.wasClean, // vrai si la connexion s'est fermée proprement
reason: event.reason, // généralement vide, et vide est informatif
secondsSinceLastMessage: (Date.now() - lastMessageAt) / 1000,
reconnects: reconnectCount
});
});
wasClean est le signal le moins cher des quatre : MDN le définit comme true « si la connexion s’est fermée proprement », donc une valeur false sépare une connexion qui s’est terminée d’une qui a été terminée. Et secondsSinceLastMessage est le champ qui attrape un délai d’inactivité, car l’intervalle se regroupera autour de la même valeur pour chaque utilisateur affecté. Un regroupement à juste en dessous de soixante secondes n’est pas une coïncidence, c’est un numéro configuré quelque part sur le chemin.
Session Replay
Extension Chrome gratuite. Un clic sur la page qui se comporte mal capture la capture d'écran, la console et le log réseau, et vous donne un lien à coller dans le ticket.
La question à poser à la personne qui l’a signalé
Une question, et ce n’est pas une question technique : un rechargement l’a-t-il corrigé ?
S’il l’a fait, les données n’ont jamais été fausses et votre rendu n’a jamais été faux. Une connexion fraîche était tout ce qu’il fallait, ce qui pointe vers le transport et loin de tout le reste. Cette seule réponse élimine plus qu’une quelconque supposition sur leur réseau, et ils peuvent la donner sans savoir ce qu’est un WebSocket.
La question de suivi qui vaut la peine d’être posée est depuis combien de temps la page était ouverte. Un onglet laissé ouvert pendant le déjeuner et un onglet ouvert il y a une minute sont des cas différents, et le rapporteur connaît la réponse.
Ce qu’il faut en retirer
L’échec est silencieux par construction. Rien ne lève une exception, l’événement error ne porte aucun détail, et la page continue à afficher la dernière chose qu’on lui a dit.
Le code de fermeture est le message entier, et le plus courant est une invention locale : 1006 est votre navigateur disant que rien n’est arrivé, pas votre serveur disant pourquoi il est parti. Les codes que personne ne peut envoyer sont des codes que personne ne peut expliquer, alors cessez de chercher la moitié côté serveur et commencez à regarder ce qui se tient entre vous et l’utilisateur.
La spécification elle-même est RFC 6455, et les codes de fermeture avec leurs significations d’une ligne se trouvent sur la page MDN CloseEvent.code.