A l'heure où les données transitent plus vite que la pensée et où la capacité des machines à les traiter est toujours plus élevée, la communication en temps réel dans les technologies web apparaît comme un enjeu majeur.
Cet article portera donc sur le thème du temps réel dans le web, et plus concrètement sur sa mise en place à l'aide du protocole Websocket.
Mais qu'est-ce que le protocole Websocket ? D'ailleurs, qu'entend-on réellement par "temps réel" ?
Nous verrons en 3e partie une manière concrète d'implémenter le temps réel avec Laravel côté serveur, et Nuxt côté client.

I- Le protocole Websocket en lui-même


Longtemps, le temps réel dans les technologies web n'a été qu'une illusion.
On pouvait seulement parler de "pseudo temps-réel", mis en place à l'aide de diverses techniques au travers du protocole HTTP et grâce notamment à la technologie Ajax.
En 2011 est alors apparu le protocole WebSocket, normalisé par l'IETF et candidat à la norme du W3C depuis 2012. Bien que réutilisant l'architecture réseau HTTP, il s'agit bien d'un protocole à part entière. La norme standard RFC 6455 est d'ailleurs supportée par la plupart des navigateurs aujourd'hui. Il permet la communication bi-directionnelle client/serveur ou "full-duplex".
La connexion n'est plus alors établie à chaque requête comme dans un échange HTTP classique, mais de manière persistante !
Concrètement, avant, cela se passait comme suit : le client envoyait une requête HTTP (protocole dit "stateless" : qui n'a pas d'état et ne garde pas de donnée en mémoire d'une requête à l'autre) au serveur, qui envoyait une réponse. Point final.

Schéma requête HTTP

Avec le protocole web socket, le processus est un peu plus complexe, comme le montre le schéma ci-dessous.

Schéma requêtes Websockets

On envoie d'abord une requête HTTP de type handshake (en français "établissement d'une liaison"), en lui indiquant que l'on veut initier une connexion de type Websocket (Upgrade: websocket).Le serveur retourne ensuite une réponse avec le code HTTP 101 indiquant qu'il accepte la connexion et change de protocole (avec les headers Connection: Upgrade et Upgrade: WebSocket).
La connexion est alors établie et la communication peut désormais se faire en full-duplex, entre le client et le serveur, via le protocole Websocket.

Très bien, le client et le serveur peuvent désormais communiquer ensemble de manière asynchrone, mais pour se transmettre quoi et comment ? Quelles peuvent en être les applications concrètes ?

II- Pourquoi mettre en place un système temps réel basé avec les websockets ?


D'abord, il faut savoir que la quasi-totalité des systèmes de chats en ligne / messageries instantanées sur le web aujourd'hui utilisent ce système. Mais pas seulement !

En réalité, la plupart des plateformes qui rafraichissent les données en temps réel -sans que vous ayez besoin de changer de page- implémentent le protocole Websocket. Tableaux de bord avec des statistiques remontées en temps réel, cotes de la bourse, jeux sur navigateur, système d'enchères,... Les applications possibles ne manquent pas !
Bien sûr, il faut que cela ait un sens. Par exemple, inutile d'utiliser le protocole web socket pour retourner des données au clic sur un bouton : une requête HTTP suffit largement. Pour que le serveur envoie des données au client, il faut bien évidemment qu'à un moment ou un autre ce mécanisme soit programmé...il ne s'agit pas ici d'intelligence artificielle !
Ci-dessous un schéma vulgarisé.

Deux exemples concrets pour bien comprendre quel peut être le fonctionnement concret des websockets, sans rentrer dans les détails techniques. Considérons ici que la connexion est déjà établie.

Premier cas : nous avons une machine industrielle qui envoie, en temps réel, un indice de frottement à un serveur via une API (considérant que la machine ne peut utiliser le protocole websocket directement).
Côté client applicatif, nous avons une application web dans laquelle nous voulons afficher un graphique restituant les données de cette machine, en temps réel, sans que l'utilisateur n'ait à rafraichir la page.

D'abord, à la réception des données entrantes depuis la machine dans l'API, il nous faut émettre un signal pour transmettre les nouvelles données au client, on parle alors de "broadcast" (diffusion). Le message est alors diffusé sur le canal écouté par notre client. Notre client peut alors, à la réception de ce signal, mettre à jour le graphique avec les données reçues.
Ci-dessous, un schéma pour bien comprendre cet exemple concret.

Deuxième cas : nous sommes dans une application mobile de livraison de repas avec des consommateurs et des livreurs.

Lorsqu'un consommateur commande un repas, un livreur aléatoirement choisi doit être notifié de la commande. Ainsi, c'est ici l'action du client (au sens informatique du terme; en terme humain : du consommateur) qui entame ici l'échange de données. Le client envoie donc une requête à notre API REST pour enregistrer cette nouvelle commande. Mais, à l'enregistrement de cette commande, il nous faut diffuser l'information de cette nouvelle commande à notre livreur. On "broadcast" donc cette information.
Côté client, notre "radio" (listener), qui reçoit alors l'information, peut alors notifier le livreur de cette nouvelle commande !
Vous devez toutefois vous demander, dans cet exemple : comment peut-on être sûr que seul le livreur concerné à reçu cette information, puisque théoriquement tous les clients sont en train d'écouter les transmissions ? C'est une interrogation tout à fait pertinente : pour cela, on utilise les Channels (ou canaux en français). En réalité, notre récepteur n'écoute pas toutes les transmissions !

Lorsque vous écoutez la radio, vous êtes par exemple "branchés" à la fréquence 107.7. C'est pareil ici, on "écoute" un (ou des) channel en particulier, qui peut être public (à destination de tous les utilisateurs), semi-privé (à destination des livreurs seulement) ou privé (à destination d'un utilisateur en particulier).

Notez que nous pourrions continuer encore le scénario ici : lorsque le livreur accepte cette commande, il faudra notifier le consommateur et ainsi de suite. On pourrait également imaginer pour le canal Livreurs l'envoi d'un message broadcasté par un administrateur depuis un back-office indiquant "Attention : plan d'alerte Coronavirus en vigueur à compter de ce soir". Ou pour le canal à destination "Utilisateurs" (tous les utilisateurs de la plateforme) : "Application en maintenance à compter de 5 heures du matin".

III- Exemple de mise en place simplifiée avec Nuxt + Laravel Websocket + Laravel Echo + Pusher


Il faut savoir qu'il y a plusieurs moyens de mettre en oeuvre le temps réel avec le protocole Websocket, en y incluant le système Redis par exemple, et avec d'autres langages et technologies que celles présentées ici.
Cet article n'a pas pour vocation de présenter un panel exhaustif de manières d'implémentation et d'utilisation.
Tout d'abord, il faut évidemment communiquer avec un serveur capable de gérer le protocole Websocket ! Si vous êtes familier de PHP et que vous ne souhaitez pas utiliser Laravel, je vous conseille d'implémenter Ratchet, qui est une librairie Websocket assez simple à mettre en place.

Dans notre cas, nous allons utiliser le framework Laravel et le package laravel-websockets.
Ensuite, côté client, il nous faut une technologie capable d'utiliser ce protocole, c'est à dire capable d'établir la liaison continue avec le serveur, et d'émettre/recevoir des données. Vous pouvez implémenter un système de temps réel avec le protocole Websocket en Javascript pur.
En effet, il faut savoir que lorsque vous codez en Javascript, il existe grand nombre d'API existantes : ce sont des objets pré-conçus que vous pouvez appeler nativement quand vous développez votre application web. L'un d'eux est Websocket. Il vous permet de créer et de gérer la connexion à un serveur Websocket, ainsi que de communiquer avec ce dernier. Mais il existe de nombreuses librairies vous permettant d'intégrer le protocole websocket, telles que ws (NodeJS), socket.io (NodeJS) ou encore HumbleNet (C).

Nous nous basons ici sur la librairie JS Laravel Echo avec le driver de Pusher. Pour la technologie du client, nous allons utiliser le framework Nuxt (https://fr.nuxtjs.org/). Il s'agit ici simplement de voir, concrètement, le protocole Websocket en action. Pour cela, un exemple comprenant le client et le serveur est à disposition à cette adresse.
Cet exemple réalisé en Nuxt/Laravel reprend le principe énoncé dans la partie 2 d'un consommateur qui commande, et d'un livreur qui reçoit la commande.
On a une partie "formulaire" pour passer une commande avec un numéro, une partie "Passage de commande" côté client qui enregistre les commandes passées, et une partie "Réception de commande" qui elle est remplie avec les données reçues via le protocole Websocket. Pour des raisons pratiques (ne pas multiplier le nombre de clients -au sens informatique du terme-), vous serez à la fois le consommateur et le livreur.

Pour émuler le serveur, vous allez avoir besoin de Wamp Server. Récupérez le projet Laravel que j'ai créé pour vous ICI. Tout y est déjà fonctionnel, et tous les packages sont présents. Il vous reste seulement à installer la base de données. Pour cela, dans localhost/phpmyadmin (user: root sans mot de passe) > Nouvelle base de données > "laranuxt-realtime".Dans un terminal, allez dans votre projet et lancez :

php artisan migrate

Cette commande créera pour vous les tables nécessaires dans la base de données. A présent, lancez Wamp. Dans votre navigateur, allez sur localhost puis "Ajouter un Virtual Host" comme sur l'image.

Pour nom, mettez "laranuxt-realtime", comme chemin [Votre chemin vers wamp]/www/laranuxt-realtime/public/ (ex. : D:/wamp/www/laranuxt-realtime/public/) et enregistrez. Relancez Wamp (clic gauche sur l'icône > Redémarrer les services). Dans votre projet "laranuxt-realtime", lancez les 2 commandes suivantes (dans 2 terminaux séparés):

php artisan websockets:serve

Cette commande lance le serveur websocket.

php artisan queue:work

Cette commande permettra à votre serveur de traiter automatiquement la file d'attente des messages à diffuser.

Votre serveur est fin prêt pour traiter le protocole websocket.

Pour le client, installez Node.js, ce qui vous permettra d'avoir le gestionnaire de paquet NPM. Une fois installé, récupérez le projet Nuxt que j'ai créé pour vous ICI et lancez-le à l'aide de la commande suivante :

npm run dev

Le projet se déploie alors sur "http://localhost:3000". Vous pouvez désormais passer des commandes...et les recevoir via le protocole Websocket, en temps réel ! Pour voir le protocole web socket en oeuvre, ouvrez la console Chrome (F12 ou clic-droit > Inspecter l'élément) et allez dans Network. L'une des ressources commence par "373c14126a5cf6ef7c8d?protocol=" : c'est notre connexion websocket ! En cliquant dessus, puis dans "Messages", vous pouvez voir les différents échanges de données entre le serveur et le client !

Dernière chose : le package laravel-websockets ne permet pas seulement de rendre le projet à même d'utiliser le protocole web socket, il est bien plus complet que cela. Notamment, il fournit un tableau de bord vous permettant de configurer le Channel de diffusion, l'objet d'évènement (équivalent au namespace) et le message pour simuler des transmissions depuis http://laranuxt-realtime/laravel-websockets.

Dans un prochain article, nous verrons plus en détail la mise en place du temps réel avec les websockets, avec plus de technique et des problématiques plus complexes comme la sécurisation des channels privés.