WebSocket et multiplexing entre les onglets/fenêtres

Lorsque vous avez une web app qui utilise des websockets, le fait que chaque onglet/fenêtre ouvre une nouvelle connexion entre le client et le serveur peut devenir problématique sur un site à fort trafic. En effet, lors d’une propagation d’un événement par le serveur, celui-ci va le propager de manière distincte à chaque instance de l’application. Pour caricaturer, un client qui aurait trois onglets ouverts sur l’app équivaut à augmenter par trois sa charge sur le serveur.

La logique voudrait que le navigateur n’utilise qu’une seule connexion et que les multiples instances soient mises à jour en même temps lors de la réception d’un événement. Des solutions qui vont en ce sens sont possibles grâce à HTTP/2 ou encore avec les shared workers mais il n’y a actuellement aucune solution pleinement compatible avec l’ensemble des navigateurs.

Partant de cette problématique, j’ai imaginé une solution consistant à n’utiliser qu’un seul websocket sur une instance, faisant passer toutes les requêtes/réponses par celle-ci.

L’idée est simple, la première instance de l’application lancée ouvre une connexion avec le serveur. Si une autre instance est ouverte on est en mesure de détecter qu’un socket est déjà présent via un échange de données par le localstorage. Lorsqu’un événement est envoyé par le serveur, l’instance possédant le socket va propager (toujours via le localstorage) cet événement à toutes les autres instances. Ce système étant couplé à une architecture d’état (state design pattern), de la même manière, lorsqu’un changement d’état survient sur l’une des instances, l’état est propagé à toutes les autres instances. Ainsi, quoi qu’il survienne (requête, réponse…) tout est propagé à l’ensemble des instances. Celle possédant la connexion se charge de relayer requêtes et réponses, les autres instances mettent simplement à jour leur état.

L’intérêt est double : Outre le fait d’éviter la propagation par le serveur du même événement x fois, on évite également des requêtes pour mettre à jour l’état de chaque instance et des problématiques d’états désynchronisés (par exemple un message qui vient d’être lu sur une instance et apparaît non lu sur une autre).

Il faut cependant garder à l’esprit que l’instance possédant la connexion peut ne plus être disponible, entraînant une coupure de communication avec le serveur. C’est pourquoi il est important d’avoir un système de file d’attente pour les requêtes et être en mesure de basculer la connexion sur une autre instance en cas de non réponse de l’instance connectée initiale.