Maîtriser les WebSockets et les Architectures Temps Réel pour des Applications Web Dynamiques
Maîtriser les WebSockets et les Architectures Temps Réel pour des Applications Web Dynamiques

Mise en place d'un serveur WebSocket simple et connexion client

Introduction au Monde des WebSockets

Bonjour et bienvenue dans ce module de "Maîtriser les WebSockets et les Architectures Temps Réel pour des Applications Web Dynamiques" ! Aujourd'hui, nous allons plonger au cœur de la technologie WebSocket en mettant concrètement en place un serveur et un client simples. Cette leçon est fondamentale car elle pose les bases pratiques de toute application temps réel construite sur cette puissante spécification.

Notre objectif est de comprendre le fonctionnement bidirectionnel et persistant des WebSockets, en partant de zéro pour construire une petite application de chat qui illustrera parfaitement la communication en temps réel entre plusieurs clients et un serveur.

Qu'est-ce qu'un WebSocket ?

Avant de manipuler le code, faisons un rapide rappel. Les WebSockets sont une technologie de communication avancée qui permet une communication bidirectionnelle (full-duplex) sur une seule connexion TCP. Contrairement au protocole HTTP, qui est stateless (sans état) et fonctionne sur un modèle requête-réponse, les WebSockets maintiennent une connexion persistante entre le client et le serveur une fois établie.

Imaginez une conversation téléphonique : une fois l'appel établi, vous pouvez parler et entendre l'autre personne simultanément, sans avoir à raccrocher et rappeler pour chaque échange. C'est l'essence même du WebSocket.

Pourquoi les WebSockets ? Les Avantages Clés

Les WebSockets offrent des avantages significatifs par rapport aux approches de simulation du temps réel (comme le long polling ou le server-sent events pour la communication unidirectionnelle) :

  • Communication Bidirectionnelle Complète (Full-Duplex) : Le client et le serveur peuvent envoyer des messages à tout moment, indépendamment l'un de l'autre, sans avoir besoin d'une nouvelle requête.
  • Faible Latence : Une fois la connexion établie, les en-têtes sont beaucoup plus légers qu'en HTTP, ce qui réduit la latence et rend les échanges quasi instantanés.
  • Efficacité et Économie de Ressources : Une seule connexion TCP est maintenue, ce qui réduit la surcharge liée à l'établissement et à la fermeture de multiples connexions HTTP.
  • Idéal pour les Applications Temps Réel : Parfaitement adapté aux :
    • Applications de chat
    • Jeux en ligne multijoueurs
    • Tableaux de bord en temps réel
    • Notifications push
    • Outils de collaboration

Prérequis

Pour suivre cette leçon et mettre en place notre exemple, vous aurez besoin de :

  • Node.js et npm (Node Package Manager) : Installés sur votre machine. Nous utiliserons Node.js pour créer notre serveur WebSocket.
  • Un éditeur de texte : Comme VS Code, Sublime Text, etc.
  • Un navigateur web moderne : (Chrome, Firefox, Edge, Safari) pour le client WebSocket.
  • Connaissances de base en JavaScript et HTML : Pour comprendre le code client et serveur.

Mise en place du serveur WebSocket

Nous allons utiliser Node.js pour notre serveur et la bibliothèque ws, qui est une implémentation WebSocket rapide et simple.

Choix de la technologie : Node.js et ws

Node.js est particulièrement bien adapté aux applications temps réel grâce à son modèle d'E/S non bloquant. La bibliothèque ws est l'une des implémentations de serveur et de client WebSocket les plus populaires pour Node.js, connue pour sa performance et sa simplicité.

Installation des dépendances

  1. Créez un nouveau dossier pour votre projet, par exemple websocket-chat.
  2. Naviguez dans ce dossier via votre terminal.
  3. Initialisez un nouveau projet Node.js :
    mkdir websocket-chat
    cd websocket-chat
    npm init -y
    
  4. Installez la bibliothèque ws :
    npm install ws
    

Code du serveur (server.js)

Créez un fichier nommé server.js dans votre dossier de projet et ajoutez le code suivant :

// server.js
const { WebSocketServer } = require('ws');

// Crée un serveur WebSocket sur le port 8080
const wss = new WebSocketServer({ port: 8080 });

console.log('Serveur WebSocket démarré sur le port 8080');

// Événement déclenché lorsqu'un client se connecte
wss.on('connection', ws => {
    console.log('Un nouveau client est connecté !');

    // Événement déclenché lorsqu'un message est reçu du client
    ws.on('message', message => {
        const decodedMessage = message.toString(); // Les messages sont des Buffers par défaut
        console.log(`Message reçu du client : ${decodedMessage}`);

        // Diffuser le message à TOUS les clients connectés
        wss.clients.forEach(client => {
            if (client.readyState === ws.OPEN) { // Vérifie si la connexion est ouverte
                client.send(decodedMessage);
            }
        });
    });

    // Événement déclenché lorsque la connexion est fermée par le client
    ws.on('close', () => {
        console.log('Un client s\'est déconnecté.');
    });

    // Événement déclenché en cas d'erreur de connexion
    ws.on('error', error => {
        console.error('Erreur WebSocket : ', error);
    });

    // Envoie un message de bienvenue au client juste après la connexion
    ws.send('Bienvenue sur le serveur WebSocket !');
});

Explication du code serveur

  • const { WebSocketServer } = require('ws'); : Importe la classe WebSocketServer de la bibliothèque ws. C'est elle qui nous permettra de créer et gérer le serveur.
  • const wss = new WebSocketServer({ port: 8080 }); : Instancie un nouveau serveur WebSocket qui écoutera les connexions entrantes sur le port 8080.
  • wss.on('connection', ws => { ... }); : C'est l'événement le plus important. Il est déclenché chaque fois qu'un nouveau client établit une connexion WebSocket avec le serveur. La variable ws dans le callback représente l'objet WebSocket de la connexion spécifique de ce client.
    • ws.on('message', message => { ... }); : Cet événement est déclenché lorsque le serveur reçoit un message d'un client. Le message est un Buffer par défaut, d'où l'utilisation de message.toString() pour le convertir en chaîne de caractères.
    • wss.clients.forEach(client => { ... }); : Pour notre application de chat, nous voulons que chaque message envoyé par un client soit reçu par tous les autres clients (et lui-même). wss.clients est un Set contenant tous les objets WebSocket des clients actuellement connectés. Nous itérons sur ce Set et utilisons client.send(decodedMessage) pour envoyer le message à chaque client. Nous vérifions client.readyState === ws.OPEN pour s'assurer que la connexion est toujours active.
    • ws.on('close', () => { ... }); : Cet événement est déclenché lorsque la connexion d'un client est fermée (soit par le client, soit en cas de problème réseau).
    • ws.on('error', error => { ... }); : Gère les erreurs spécifiques à la connexion du client.
    • ws.send('Bienvenue...'); : Ceci envoie un message uniquement au client qui vient de se connecter, juste après l'établissement de la connexion.

Mise en place du client WebSocket

Notre client sera une simple page HTML avec du JavaScript pour interagir avec le serveur.

Création de la page HTML (index.html)

Créez un fichier nommé index.html dans le même dossier que server.js et ajoutez le code suivant :

<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Client WebSocket Simple</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        #messages { border: 1px solid #ccc; padding: 10px; min-height: 200px; max-height: 400px; overflow-y: scroll; margin-bottom: 10px; }
        .message { margin-bottom: 5px; }
        .server { color: gray; font-style: italic; }
        .client { color: blue; }
        input[type="text"] { width: 300px; padding: 8px; margin-right: 5px; }
        button { padding: 8px 15px; cursor: pointer; }
    </style>
</head>
<body>
    <h1>Chat WebSocket Simple</h1>

    <div id="messages"></div>

    <input type="text" id="messageInput" placeholder="Tapez votre message ici...">
    <button id="sendButton">Envoyer</button>

    <script>
        const messagesDiv = document.getElementById('messages');
        const messageInput = document.getElementById('messageInput');
        const sendButton = document.getElementById('sendButton');

        // Création de la connexion WebSocket
        // L'URL 'ws://localhost:8080' doit correspondre à l'adresse et au port de votre serveur
        const socket = new WebSocket('ws://localhost:8080');

        // Événement déclenché lorsque la connexion est établie
        socket.onopen = function(event) {
            appendMessage('Connexion WebSocket établie !', 'server');
            console.log('Connexion ouverte:', event);
        };

        // Événement déclenché lorsqu'un message est reçu du serveur
        socket.onmessage = function(event) {
            appendMessage(`Message reçu: ${event.data}`, 'client');
            console.log('Message reçu:', event.data);
        };

        // Événement déclenché lorsque la connexion est fermée
        socket.onclose = function(event) {
            appendMessage('Connexion WebSocket fermée.', 'server');
            console.log('Connexion fermée:', event);
        };

        // Événement déclenché en cas d'erreur
        socket.onerror = function(error) {
            appendMessage(`Erreur WebSocket: ${error.message}`, 'server');
            console.error('Erreur WebSocket:', error);
        };

        // Fonction pour envoyer un message au serveur
        sendButton.onclick = function() {
            const message = messageInput.value;
            if (message) {
                socket.send(message); // Envoie le message au serveur
                messageInput.value = ''; // Efface l'input après l'envoi
            }
        };

        // Permet d'envoyer le message en appuyant sur 'Entrée'
        messageInput.addEventListener('keypress', function(e) {
            if (e.key === 'Enter') {
                sendButton.click();
            }
        });

        // Fonction utilitaire pour ajouter des messages au DOM
        function appendMessage(text, type) {
            const messageElement = document.createElement('div');
            messageElement.classList.add('message', type);
            messageElement.textContent = text;
            messagesDiv.appendChild(messageElement);
            messagesDiv.scrollTop = messagesDiv.scrollHeight; // Scroll vers le bas
        }
    </script>
</body>
</html>

Explication du code client

  • <style> : Un peu de CSS pour rendre l'interface plus agréable.
  • <div id="messages"></div> : C'est ici que tous les messages (envoyés et reçus) seront affichés.
  • <input type="text" id="messageInput"> et <button id="sendButton"> : L'interface utilisateur pour taper et envoyer des messages.
  • const socket = new WebSocket('ws://localhost:8080'); : C'est la ligne clé. Elle crée une nouvelle instance de WebSocket et tente de se connecter au serveur WebSocket sur ws://localhost:8080. L'URL doit utiliser le préfixe ws:// (ou wss:// pour une connexion sécurisée).
  • socket.onopen = function(event) { ... }; : Cet événement est déclenché une fois que la connexion WebSocket avec le serveur est établie avec succès.
  • socket.onmessage = function(event) { ... }; : Déclenché lorsque le client reçoit un message du serveur. Le message est contenu dans event.data.
  • socket.onclose = function(event) { ... }; : Déclenché lorsque la connexion est fermée, soit par le serveur, soit par le client, soit en cas de problème.
  • socket.onerror = function(error) { ... }; : Gère les erreurs survenant pendant la durée de vie de la connexion WebSocket.
  • sendButton.onclick = function() { ... }; : Lorsque le bouton "Envoyer" est cliqué, il prend le texte de l'input et l'envoie au serveur via socket.send(message).
  • appendMessage(text, type) : Une fonction utilitaire pour ajouter les messages dans la div d'affichage, avec une classe pour les différencier (serveur ou client).

Comment ça marche : Le Handshake WebSocket

La mise en place d'une connexion WebSocket ne se fait pas directement. Elle commence par un handshake (poignée de main) HTTP.

  1. Requête d'Upgrade du Client : Le client envoie une requête HTTP standard au serveur, mais avec des en-têtes spéciaux :

    GET /chat HTTP/1.1
    Host: server.example.com
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
    Origin: http://example.com
    Sec-WebSocket-Version: 13
    
    • Upgrade: websocket et Connection: Upgrade : Indiquent au serveur que le client souhaite "upgrader" (passer à) le protocole de communication vers WebSocket.
    • Sec-WebSocket-Key : Une clé utilisée par le serveur pour prouver qu'il comprend le protocole WebSocket.
    • Sec-WebSocket-Version : La version du protocole WebSocket que le client souhaite utiliser.
  2. Réponse d'Upgrade du Serveur : Si le serveur supporte WebSockets et accepte la requête, il répond avec une réponse HTTP spécifique :

    HTTP/1.1 101 Switching Protocols
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Accept: s3pPLMBiTxaQ9KuZygjjmkjfdDk=
    
    • 101 Switching Protocols : Ce code de statut indique que le serveur est prêt à changer de protocole.
    • Sec-WebSocket-Accept : Le serveur calcule cette valeur en utilisant la Sec-WebSocket-Key du client et une chaîne magique, puis la renvoie. Cela confirme au client que le serveur parle bien WebSocket.

Une fois que cette "poignée de main" HTTP est terminée avec succès, la connexion HTTP est "détournée" et transformée en une connexion WebSocket persistante et bidirectionnelle. À partir de ce moment, tous les messages échangés sont encadrés par le protocole WebSocket et ne contiennent plus les en-têtes HTTP lourds.

Test de l'application

Suivez ces étapes pour tester votre serveur et client WebSocket :

  1. Démarrez le serveur : Ouvrez votre terminal, naviguez vers le dossier websocket-chat et exécutez :

    node server.js
    

    Vous devriez voir le message Serveur WebSocket démarré sur le port 8080. Laissez ce terminal ouvert.

  2. Ouvrez le client dans votre navigateur : Ouvrez le fichier index.html directement dans votre navigateur web. Vous pouvez le faire en double-cliquant dessus ou en faisant un clic droit -> "Ouvrir avec..." votre navigateur préféré.

  3. Interagissez :

    • Dès que index.html est chargé, le client tentera de se connecter au serveur. Vous devriez voir un message Un nouveau client est connecté ! dans votre terminal et Connexion WebSocket établie ! suivi de Message reçu: Bienvenue sur le serveur WebSocket ! dans la page web.
    • Tapez un message dans la zone de texte et cliquez sur "Envoyer" (ou appuyez sur Entrée). Le message devrait apparaître dans la section des messages de la page web.
    • Testez la diffusion : Ouvrez index.html dans un deuxième onglet ou un autre navigateur. Connectez-vous également. Envoyez un message depuis le premier onglet, et vous verrez qu'il apparaît instantanément dans le deuxième onglet, et vice-versa ! Cela démontre la capacité de diffusion de notre serveur WebSocket.

Félicitations ! Vous avez mis en place votre premier serveur et client WebSocket fonctionnels.

Conclusion et Prochaines Étapes

Dans cette leçon, nous avons appris à :

  • Comprendre les fondamentaux des WebSockets et leurs avantages pour les applications temps réel.
  • Mettre en place un serveur WebSocket simple avec Node.js et la bibliothèque ws.
  • Créer un client WebSocket en HTML et JavaScript pour interagir avec le serveur.
  • Saisir le concept du handshake WebSocket.
  • Tester une application de chat temps réel simple.

C'est un jalon important dans votre parcours pour maîtriser les architectures temps réel. La simplicité et la puissance de cette approche ouvrent la porte à une multitude d'applications dynamiques.

Pour aller plus loin, nous explorerons dans les prochaines leçons des sujets comme :

  • Sécurisation des WebSockets (WSS) : Utilisation de SSL/TLS pour des connexions sécurisées.
  • Gestion des états et des utilisateurs : Comment gérer les sessions, l'authentification et les données spécifiques aux utilisateurs.
  • Utilisation de frameworks avancés : Comme Socket.IO, qui offre des fonctionnalités de reconnexion automatique, de fallback sur d'autres transports, et de gestion de "rooms" (salons de discussion).
  • Déploiement d'applications WebSocket : Comment mettre en production votre application temps réel.

Préparez-vous à construire des expériences utilisateur encore plus riches et interactives !