Le problème : connexions client-serveur et serveur-client à faible latence
Le Web a été largement construit autour du paradigme requête/réponse de HTTP. Un client charge une page Web, puis rien ne se passe jusqu'à ce que l'utilisateur clique sur la page suivante. Vers 2005, AJAX a commencé à rendre le Web plus dynamique. Toutefois, toutes les communications HTTP étaient gérées par le client, ce qui nécessitait une interaction de l'utilisateur ou une interrogation périodique pour charger de nouvelles données depuis le serveur.
Les technologies permettant au serveur d'envoyer des données au client dès qu'il sait que de nouvelles données sont disponibles existent depuis un certain temps. Ils sont appelés "Push" ou "Comet". L'une des techniques les plus courantes pour créer l'illusion d'une connexion initiée par le serveur est appelée "interrogation longue". Avec l'interrogation longue, le client ouvre une connexion HTTP vers le serveur, qui la maintient ouverte jusqu'à l'envoi de la réponse. Chaque fois que le serveur dispose de nouvelles données, il envoie la réponse (les autres techniques impliquent Flash, les requêtes XHR multipart et les fichiers HTML). L'interrogation longue et les autres techniques fonctionnent plutôt bien. Vous les utilisez tous les jours dans des applications telles que le chat Gmail.
Cependant, toutes ces solutions de contournement partagent un problème : elles entraînent une surcharge HTTP, ce qui ne les rend pas adaptées aux applications à faible latence. Pensez aux jeux de tir à la première personne multijoueurs dans le navigateur ou à tout autre jeu en ligne avec un composant en temps réel.
Présentation de WebSocket : intégrer les sockets au Web
La spécification WebSocket définit une API établissant des connexions "socket" entre un navigateur Web et un serveur. En d'autres termes, il existe une connexion persistante entre le client et le serveur, et les deux parties peuvent commencer à envoyer des données à tout moment.
Premiers pas
Pour ouvrir une connexion WebSocket, il vous suffit d'appeler le constructeur WebSocket :
var connection = new WebSocket('ws://html5rocks.websocket.org/echo', ['soap', 'xmpp']);
Remarquez bien ws:. Il s'agit du nouveau schéma d'URL pour les connexions WebSocket. Il existe également wss: pour une connexion WebSocket sécurisée, de la même manière que https: est utilisé pour les connexions HTTP sécurisées.
En associant immédiatement certains gestionnaires d'événements à la connexion, vous pouvez savoir quand la connexion est ouverte, quand des messages entrants sont reçus ou s'il y a une erreur.
Le deuxième argument accepte des sous-protocoles facultatifs. Il peut s'agir d'une chaîne ou d'un tableau de chaînes. Chaque chaîne doit représenter un nom de sous-protocole, et le serveur n'accepte qu'un seul des sous-protocoles transmis dans le tableau. Le sous-protocole accepté peut être déterminé en accédant à la propriété protocol de l'objet WebSocket.
Les noms des sous-protocoles doivent correspondre à l'un des noms de sous-protocoles enregistrés dans le registre IANA. En février 2012, un seul nom de sous-protocole (soap) était enregistré.
// When the connection is open, send some data to the server
connection.onopen = function () {
connection.send('Ping'); // Send the message 'Ping' to the server
};
// Log errors
connection.onerror = function (error) {
console.log('WebSocket Error ' + error);
};
// Log messages from the server
connection.onmessage = function (e) {
console.log('Server: ' + e.data);
};
Communication avec le serveur…
Dès que nous sommes connectés au serveur (lorsque l'événement open est déclenché), nous pouvons commencer à lui envoyer des données à l'aide de la méthode send('your message') sur l'objet de connexion. Auparavant, il ne prenait en charge que les chaînes, mais dans la dernière spécification, il peut désormais également envoyer des messages binaires. Pour envoyer des données binaires, vous pouvez utiliser un objet Blob ou ArrayBuffer.
// Sending String
connection.send('your message');
// Sending canvas ImageData as ArrayBuffer
var img = canvas_context.getImageData(0, 0, 400, 320);
var binary = new Uint8Array(img.data.length);
for (var i = 0; i < img.data.length; i++) {
binary[i] = img.data[i];
}
connection.send(binary.buffer);
// Sending file as Blob
var file = document.querySelector('input[type="file"]').files[0];
connection.send(file);
De même, le serveur peut nous envoyer des messages à tout moment. Chaque fois que cela se produit, le rappel onmessage se déclenche. Le rappel reçoit un objet d'événement, et le message réel est accessible via la propriété data.
WebSocket peut également recevoir des messages binaires dans la dernière spécification. Les frames binaires peuvent être reçus au format Blob ou ArrayBuffer. Pour spécifier le format du binaire reçu, définissez la propriété binaryType de l'objet WebSocket sur "blob" ou "arraybuffer". Le format par défaut est "blob". (Vous n'avez pas besoin d'aligner le paramètre binaryType lors de l'envoi.)
// Setting binaryType to accept received binary as either 'blob' or 'arraybuffer'
connection.binaryType = 'arraybuffer';
connection.onmessage = function(e) {
console.log(e.data.byteLength); // ArrayBuffer object if binary
};
Les extensions sont une autre fonctionnalité récemment ajoutée à WebSocket. Les extensions permettent d'envoyer des frames compressés, multiplexés, etc. Vous pouvez trouver les extensions acceptées par le serveur en examinant la propriété "extensions" de l'objet WebSocket après l'événement d'ouverture. En février 2012, aucune spécification officielle n'avait encore été publiée pour les extensions.
// Determining accepted extensions
console.log(connection.extensions);
Communication interorigine
En tant que protocole moderne, la communication interorigine est intégrée directement à WebSocket. Bien que vous deviez toujours vous assurer de ne communiquer qu'avec des clients et des serveurs de confiance, WebSocket permet la communication entre des parties sur n'importe quel domaine. Le serveur décide de rendre son service disponible pour tous les clients ou uniquement pour ceux qui résident sur un ensemble de domaines bien définis.
Serveurs proxy
Chaque nouvelle technologie s'accompagne de nouveaux problèmes. Dans le cas de WebSocket, il s'agit de la compatibilité avec les serveurs proxy qui gèrent les connexions HTTP dans la plupart des réseaux d'entreprise. Le protocole WebSocket utilise le système de mise à niveau HTTP (qui est normalement utilisé pour HTTP/SSL) pour "mettre à niveau" une connexion HTTP vers une connexion WebSocket. Certains serveurs proxy n'acceptent pas ce comportement et interrompent la connexion. Ainsi, même si un client donné utilise le protocole WebSocket, il n'est pas toujours possible d'établir une connexion. La section suivante est donc d'autant plus importante :)
Utiliser WebSockets dès aujourd'hui
WebSocket est une technologie encore jeune qui n'est pas entièrement implémentée dans tous les navigateurs. Cependant, vous pouvez utiliser WebSocket dès aujourd'hui avec des bibliothèques qui utilisent l'une des solutions de remplacement mentionnées ci-dessus lorsque WebSocket n'est pas disponible. La bibliothèque socket.io est devenue très populaire dans ce domaine. Elle est fournie avec une implémentation client et serveur du protocole, et inclut des solutions de remplacement (socket.io n'est pas encore compatible avec la messagerie binaire en février 2012). Il existe également des solutions commerciales telles que PusherApp, qui peuvent être facilement intégrées à n'importe quel environnement Web en fournissant une API HTTP pour envoyer des messages WebSocket aux clients. En raison de la requête HTTP supplémentaire, il y aura toujours une surcharge par rapport à WebSocket pur.
Côté serveur
L'utilisation de WebSocket crée un tout nouveau modèle d'utilisation pour les applications côté serveur. Bien que les piles de serveurs traditionnelles telles que LAMP soient conçues autour du cycle de requête/réponse HTTP, elles ne gèrent souvent pas bien un grand nombre de connexions WebSocket ouvertes. Pour maintenir un grand nombre de connexions ouvertes en même temps, il faut une architecture qui accepte une simultanéité élevée à un faible coût de performances. Ces architectures sont généralement conçues autour du threading ou des E/S non bloquantes.
Implémentations côté serveur
- Node.js
- Java
- Ruby
- Python
- Erlang
- C++
- .NET
Versions du protocole
Le protocole de communication (un handshake et le transfert de données entre le client et le serveur) pour WebSocket est désormais RFC6455. Les dernières versions de Chrome et de Chrome pour Android sont entièrement compatibles avec la norme RFC6455, y compris la messagerie binaire. De plus, Firefox sera compatible avec la version 11 et Internet Explorer avec la version 10. Vous pouvez toujours utiliser des versions de protocole plus anciennes, mais cela n'est pas recommandé, car elles sont connues pour être vulnérables. Si vous disposez d'implémentations de serveur pour des versions plus anciennes du protocole WebSocket, nous vous recommandons de passer à la dernière version.
Cas d'utilisation
Utilisez WebSocket chaque fois que vous avez besoin d'une connexion à très faible latence, en temps quasi réel, entre le client et le serveur. N'oubliez pas que cela peut impliquer de repenser la façon dont vous créez vos applications côté serveur, en mettant l'accent sur des technologies telles que les files d'attente d'événements. Voici quelques exemples de cas d'utilisation :
- Jeux en ligne multijoueurs
- Applications de chat
- Bandeau d'actualités sportives en direct
- Flux de réseaux sociaux mis à jour en temps réel
Démonstrations
- Plink
- Peins avec moi
- Pixelatr
- Tirets
- Mots croisés en ligne massivement multijoueurs
- Serveur de ping (utilisé dans les exemples ci-dessus)
- Exemple HTML5demos