Le protocole WebSocket permet d'établir un canal de communication bidirectionnel et persistant entre un client (généralement un navigateur) et un serveur. Contrairement au HTTP classique, où le client doit initier chaque requête, les WebSockets peremttent au serveur d'envoyer des données de manière proactive en temps réel.
Préparation de l'environnement
Pour mettre en place notre serveur de communication, nous utiliserons Express pour la gestion des fichiers statiques et ws pour la couche WebSocket.
# Initialisation du projet
mkdir ws-node-app
cd ws-node-app
npm init -y
# Installation des dépendances
npm install express ws uuid
Architecture du projet
Nous allons organiser notre application avec une structure simple :
app.js: Serveur Node.js (Express + WebSocket).public/index.html: Interface utilisateur.public/main.js: Logique client.
Développement du serveur (app.js)
Le code suivant configure un serveur HTTP qui écoute les requêtes web standards et gère simultanément les mises à niveau de protocole vers WebSocket.
const express = require('express');
const http = require('http');
const WebSocket = require('ws');
const { v4: uuidv4 } = require('uuid');
const port = 3000;
const app = express();
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });
// Stockage des sessions actives
const activeUsers = new Map();
app.use(express.static('public'));
// Logique de diffusion (Broadcast)
const dispatch = (data, senderId = null) => {
const payload = JSON.stringify(data);
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
// Optionnel : ne pas renvoyer le message à l'expéditeur
const user = activeUsers.get(client);
if (user?.id !== senderId) {
client.send(payload);
}
}
});
};
wss.on('connection', (ws) => {
const userId = uuidv4();
activeUsers.set(ws, { id: userId });
// Notification de bienvenue
ws.send(JSON.stringify({
event: 'init',
data: { id: userId }
}));
// Alerter les autres utilisateurs
dispatch({
event: 'system',
message: `L'utilisateur ${userId.substring(0, 8)} a rejoint la session.`
}, userId);
ws.on('message', (buffer) => {
try {
const content = JSON.parse(buffer.toString());
if (content.type === 'msg') {
dispatch({
event: 'chat',
author: userId.substring(0, 8),
text: content.text
});
}
} catch (err) {
console.error('Erreur de parsing:', err);
}
});
ws.on('close', () => {
activeUsers.delete(ws);
dispatch({
event: 'system',
message: `Un utilisateur s'est déconnecté.`
});
});
});
server.listen(port, () => {
console.log(`Serveur actif sur http://localhost:${port}`);
});
Interface Client (public/index.html)
Une interface minimaliste pour visualiser les échanges de données.
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Node.js WebSocket Chat</title>
<style>
#terminal { height: 400px; border: 2px solid #333; overflow-y: auto; padding: 15px; background: #f9f9f9; }
.log { margin-bottom: 8px; border-bottom: 1px dashed #ccc; }
.sys { color: #888; font-style: italic; }
</style>
</head>
<body>
<div id="terminal"></div>
<div style="margin-top: 10px;">
<input type="text" id="inputMsg" placeholder="Message...">
<button onclick="pushMessage()">Envoyer</button>
</div>
<script src="main.js"></script>
</body>
</html>
Logique Frontend (public/main.js)
Ce script gère la connexion au serveur et la mise à jour dynamique du DOM lors de la réception de messagse.
const conn = new WebSocket(`ws://${window.location.host}`);
const terminal = document.getElementById('terminal');
const input = document.getElementById('inputMsg');
conn.onopen = () => {
printToScreen('Système', 'Connecté au serveur WebSocket', 'sys');
};
conn.onmessage = (raw) => {
const packet = JSON.parse(raw.data);
switch(packet.event) {
case 'init':
printToScreen('Système', `Votre ID : ${packet.data.id}`, 'sys');
break;
case 'chat':
printToScreen(packet.author, packet.text);
break;
case 'system':
printToScreen('Avis', packet.message, 'sys');
break;
}
};
function pushMessage() {
const text = input.value.trim();
if (text && conn.readyState === WebSocket.OPEN) {
conn.send(JSON.stringify({ type: 'msg', text: text }));
printToScreen('Moi', text);
input.value = '';
}
}
function printToScreen(sender, msg, className = 'log') {
const div = document.createElement('div');
div.className = className;
div.innerHTML = `<strong>${sender}:</strong> ${msg}`;
terminal.appendChild(div);
terminal.scrollTop = terminal.scrollHeight;
}
Considérations pour la mise en production
Bien que cet exemple soit fonctionnel, une application de production nécessite des optimisations supplémentaires :
- Sécurité : Utilisez le protocole
wss://(WebSocket Secure) pour chiffrer les échanges via SSL/TLS. - Authentification : Vérifiez les jetons JWT ou les sessions lors de la mise à niveau HTTP (évènement
upgrade). - Scalabilité : Si vous déployez plusieurs instances de serveurs, utilisez un adaptateur comme Redis Pub/Sub pour synchroniser les messages entre les instances.
- Gestion des erreurs : Implémentez une logique de reconnexion automatique côté client pour gérer les coupures réseau imprévues.