Le protocole WebSocket, dcrit dans la spcification RFC 6455 fournit un moyen dchanger des donnes entre le navigateur et le serveur via une connexion persistante. Les donnes peuvent tre transmises dans les deux sens sous forme de paquets, sans interrompre la connexion et de requtes HTTP supplmentaires.
WebSocket est particulirement adapt aux services qui ncessitent un change de donnes continu, par exemple jeux en ligne, systmes de trading en temps rel, etc.
Un exemple simple
Pour ouvrir une connexion websocket, nous devons crer new WebSocket en utilisant le protocole spcial ws dans lurl :
let socket = new WebSocket("ws://javascript.info");
Il existe galement un protocole chiffr wss://. Cest comme HTTPS mais pour les websockets.
wss://Le protocole wss:// est non seulement chiffr, mais galement plus fiable.
Cest parce que les donnes ws:// ne sont pas chiffres, par consquent visibles pour tout intermdiaire. Les anciens serveurs proxy ne connaissent pas WebSocket, ils peuvent voir des en-ttes tranges et abandonner la connexion.
Dun autre ct, wss:// est WebSocket sur TLS, (comme HTTPS est HTTP sur TLS), la couche de scurit de transport chiffre les donnes lexpditeur et dchiffre au rcepteur. Les paquets de donnes sont donc transmis chiffrs via des proxys. Ils ne peuvent pas voir ce quil y a lintrieur et les laisser passer.
Une fois le socket cr, nous devons couter les vnements quil contient. Il y a au total 4 vnements :
openConnection tablie,messageDonne reue,errorerreur websocket,closeconnexion ferme.
Et si nous souhaitons envoyer quelque chose, alors socket.send(data) fera cela.
Voici un exemple :
let socket = new WebSocket("wss://javascript.info/article/websocket/demo/hello");
socket.onopen = function(e) {
alert("[open] Connection established");
alert("Sending to server");
socket.send("My name is John");
};
socket.onmessage = function(event) {
alert(`[message] Data received from server: ${event.data}`);
};
socket.onclose = function(event) {
if (event.wasClean) {
alert(`[close] Connection closed cleanly, code=${event.code} reason=${event.reason}`);
} else {
// par exemple : processus serveur arrt ou rseau en panne
// event.code est gnralement 1006 dans ce cas
alert('[close] Connection died');
}
};
socket.onerror = function(error) {
alert(`[error]`);
};
des fins de dmonstration, il y a un petit serveur server.js crit en Node.js, pour lexemple ci-dessus, en cours dexcution. Il rpond par Bonjour du serveur, John, puis attend 5 secondes et ferme la connexion.
Vous verrez donc les vnements open message close.
On peut dj parler de WebSocket. Cest assez simple, non ?
Approfondissont maintenant.
Ouvrir un websocket
Lorsque new WebSocket(url) est cr, il se connecte immdiatement.
Lors de la connexion, le navigateur ( laide des en-ttes) demande au serveur: Prenez-vous en charge Websocket ? Et si le serveur rpond oui, alors la conversation se poursuit dans le protocole WebSocket, qui nest pas du tout HTTP.
Voici un exemple den-ttes de navigateur pour une demande faite par new WebSocket("wss://javascript.info/chat").
GET /chat
Host: javascript.info
Origin: https://javascript.info
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Key: Iv8io/9s+lYFgZWcXczP8Q==
Sec-WebSocket-Version: 13
Originlorigine de la page client, par exemplehttps://javascript.info. Les objets WebSocket sont cross-origin par nature. Il ny a pas den-ttes spciaux ou dautres limitations. Les anciens serveurs ne sont pas en mesure de grer WebSocket de toute faon, il ny a donc pas de problme de compatibilit. Mais len-tteOriginest important, car il permet au serveur de dcider de discuter ou non en WebSocket avec ce site Web.Connection: Upgradeindique que le client souhaite modifier le protocole.Upgrade: websocketle protocole demand est websocket.Sec-WebSocket-Keyune cl gnre alatoirement par le navigateur pour la scurit.Sec-WebSocket-VersionVersion du protocole WebSocket, 13 est la version actuelle.
Nous ne pouvons pas utiliser XMLHttpRequest ou fetch pour effectuer ce type de requte HTTP, car JavaScript nest pas autoris dfinir ces en-ttes.
Si le serveur accepte de passer WebSocket, il doit envoyer le code de rponse 101 :
101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: hsBlbuDTkk24srzEOTBUlZAlC2g=
Ici Sec-WebSocket-Accept est Sec-WebSocket-Key, recod laide dun algorithme spcial. En le voyant, le navigateur comprend que le serveur prend rellement en charge le protocole WebSocket.
Ensuite, les donnes sont transfres en utilisant le protocole WebSocket, nous verrons bientt sa structure (frames). Et ce nest pas du tout HTTP.
Extensions et sous-protocoles
Il peut y avoir des en-ttes supplmentaires Sec-WebSocket-Extensions et Sec-WebSocket-Protocol qui dcrivent les extensions et les sous-protocoles.
Par exemple :
-
Sec-WebSocket-Extensions: deflate-framesignifie que le navigateur prend en charge la compression des donnes. Une extension est lie au transfert des donnes, cest une fonctionnalit qui tend le protocole WebSocket. Len-tteSec-WebSocket-Extensionsest envoy automatiquement par le navigateur, avec la liste de toutes les extensions quil prend en charge. -
Sec-WebSocket-Protocol: soap, wampsignifie que nous aimerions transfrer non seulement toutes les donnes, mais les donnes SOAP ou les protocoles WAMP (The WebSocket Application Messaging Protocol). Les sous-protocoles WebSocket sont enregistrs dans le catalogue IANA. Donc, cet en-tte dcrit les formats de donnes que nous allons utiliser.Cet en-tte facultatif est dfini laide du deuxime paramtre de
new WebSocket. Cest le tableau des sous-protocoles, par exemple si nous souhaitons utiliser SOAP ou WAMP :let socket = new WebSocket("wss://javascript.info/chat", ["soap", "wamp"]);
Le serveur doit rpondre avec une liste de protocoles et dextensions quil accepte dutiliser.
Par exemple, la requte :
GET /chat
Host: javascript.info
Upgrade: websocket
Connection: Upgrade
Origin: https://javascript.info
Sec-WebSocket-Key: Iv8io/9s+lYFgZWcXczP8Q==
Sec-WebSocket-Version: 13
Sec-WebSocket-Extensions: deflate-frame
Sec-WebSocket-Protocol: soap, wamp
Rponse :
101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: hsBlbuDTkk24srzEOTBUlZAlC2g=
Sec-WebSocket-Extensions: deflate-frame
Sec-WebSocket-Protocol: soap
Ici, le serveur rpond quil prend en charge lextension deflate-frame, et uniquement SOAP des sous-protocoles demands.
Transfert de donnes
La communication WebSocket se compose de frames des fragments de donnes, qui peuvent tre envoys de chaque ct et peuvent tre de plusieurs types :
- text frames contiennent des donnes textuelles que les parties senvoient mutuellement.
- binary data frames contiennent des donnes binaires que les parties senvoient mutuellement.
- ping/pong frames sont utiliss pour vrifier la connexion, envoye par le serveur, le navigateur y rpond automatiquement.
- il y a aussi la connection close frame et quelques autres frames de service.
Dans le navigateur, nous travaillons directement uniquement avec du texte ou des frames binaires.
La mthode WebSocket .send() peut envoyer du texte ou des donnes binaires.
Un appel socket.send(body) autorise body dans une chane de caractres ou un format binaire, y compris Blob, ArrayBuffer, etc. Aucun paramtre requis : il suffit de lenvoyer dans nimporte quel format.
Lorsque nous recevons les donnes, le texte vient toujours sous forme de chane de caractres. Et pour les donnes binaires, nous pouvons choisir entre les formats Blob et ArrayBuffer.
Cela est dfini par la proprit socket.binaryType, cest "blob" par dfaut, donc les donnes binaires viennent en tant quobjets Blob.
Blob est un objet binaire de haut niveau, il sintgre directement avec <a>, <img> et dautres balises, cest donc un dfaut sain. Mais pour le traitement binaire, pour accder aux octets de donnes individuels, nous pouvons le changer en "arraybuffer" :
socket.binaryType = "arraybuffer";
socket.onmessage = (event) => {
// event.data est soit une chane de caractres (si du texte), soit un arraybuffer (si binaire)
};
Limitation de dbit
Imaginez, notre application gnre beaucoup de donnes envoyer. Mais lutilisateur a une connexion rseau lente, peut-tre sur un Internet mobile, en dehors dune ville.
Nous pouvons appeler socket.send (data) encore et encore. Mais les donnes seront mises en mmoire tampon (stockes) en mmoire et envoyes uniquement aussi rapidement que la vitesse du rseau le permet.
La proprit socket.bufferedAmount stocke le nombre doctets qui sont mis en mmoire tampon ce moment, en attente dtre envoys sur le rseau.
Nous pouvons lexaminer pour voir si le socket est rellement disponible pour la transmission.
// toutes les 100 ms examine le socket et envoi plus de donnes
// uniquement si toutes les donnes existantes ont t envoyes
setInterval(() => {
if (socket.bufferedAmount == 0) {
socket.send(moreData());
}
}, 100);
Connexion ferme
Normalement, lorsquune partie souhaite fermer la connexion (le navigateur et le serveur ont les mmes droits), ils envoient un connection close frame avec un code numrique et une raison textuelle.
La mthode pour cela est :
socket.close([code], [reason]);
codeest un code de fermeture WebSocket spcial (facultatif)reasonest une chane de caractres qui dcrit la raison de la fermeture (facultatif)
Ensuite, lautre partie du gestionnaire dvnements close obtient le code et la raison, par exemple :
// partie fermante :
socket.close(1000, "Work complete");
// l'autre partie
socket.onclose = event => {
// event.code === 1000
// event.reason === "Work complete"
// event.wasClean === true (clean close)
};
Valeurs de code les plus courantes :
1000la closure normale par dfaut (utilise si aucuncodenest fourni),1006aucun moyen pour utiliser ce code manuellement, indique que la connexion a t perdue (pas de frame de fermeture).
Il existe dautres codes comme :
1001la partie sen va, par exemple le serveur sarrte ou un navigateur quitte la page,1009le message est trop gros pour tre trait,1011erreur inattendue sur le serveur,- etc.
La liste complte se trouve dans le RFC6455, 7.4.1.
Les codes WebSocket sont un peu comme les codes HTTP, mais diffrents. En particulier, tous les codes infrieurs 1000 sont rservs, il y aura une erreur si nous essayons de dfinir un tel code.
// en cas de rupture de connexion
socket.onclose = event => {
// event.code === 1006
// event.reason === ""
// event.wasClean === false (no closing frame)
};
tat de connexion
Pour obtenir ltat de la connexion, il existe en outre la proprit socket.readyState avec des valeurs :
0CONNECTING: la connexion na pas encore t tablie,1OPEN: communicante,2CLOSING: la connexion se ferme,3CLOSED: la connexion est ferme.
Exemple de tchat
Passons en revue un exemple de discussion laide de lAPI WebSocket du navigateur et du module Node.js WebSocket https://github.com/websockets/ws. Nous porterons notre attention principalement sur le ct client, mais le serveur est galement simple.
HTML: nous avons besoin dun <form> pour envoyer des messages et dun <div> pour les messages entrants :
<!-- message form -->
<form name="publish">
<input type="text" name="message">
<input type="submit" value="Send">
</form>
<!-- div with messages -->
<div id="messages"></div>
De JavaScript, nous voulons trois choses :
- Ouvrir la connexion.
- Lors de la soumission du formulaire
socket.send(message)pour le message. - Sur le message entrant lajouter
div#messages.
Voici le code :
let socket = new WebSocket("wss://javascript.info/article/websocket/chat/ws");
// envoyer un message depuis le formulaire
document.forms.publish.onsubmit = function() {
let outgoingMessage = this.message.value;
socket.send(outgoingMessage);
return false;
};
// message reu - affiche le message dans div#messages
socket.onmessage = function(event) {
let message = event.data;
let messageElem = document.createElement('div');
messageElem.textContent = message;
document.getElementById('messages').prepend(messageElem);
}
Le code ct serveur dpasse un peu notre porte. Ici, nous utiliserons Node.js, mais ce nest pas obligatoire. Dautres plateformes ont galement leurs moyens de travailler avec WebSocket.
Lalgorithme ct serveur sera :
- Crer
clients = new Set()un ensemble de sockets. - Pour chaque websocket accept, lajouter lensemble
clients.add (socket)et configurer lcouteur dvnementsmessagepour obtenir ses messages. - Lorsquun message est reu : parcourir les clients et lenvoyer tout le monde.
- Lorsquune connexion est ferme :
clients.delete(socket).
const ws = new require('ws');
const wss = new ws.Server({noServer: true});
const clients = new Set();
http.createServer((req, res) => {
// ici, nous ne grons que les connexions websocket
// dans un projet rel, nous aurions un autre code ici pour grer les demandes non Websocket
wss.handleUpgrade(req, req.socket, Buffer.alloc(0), onSocketConnect);
});
function onSocketConnect(ws) {
clients.add(ws);
ws.on('message', function(message) {
message = message.slice(0, 50); // la longueur maximale des messages sera de 50
for(let client of clients) {
client.send(message);
}
});
ws.on('close', function() {
clients.delete(ws);
});
}
Voici lexemple fonctionnel :
Vous pouvez galement le tlcharger (bouton en haut droite dans liframe) et lexcuter localement. Noubliez pas dinstaller Node.js et npm install ws avant lexcution.
Rsum
WebSocket est un moyen moderne davoir des connexions navigateur-serveur persistantes.
- WebSockets nont pas de limites cross-origin.
- Ils sont bien pris en charge dans les navigateurs.
- Peut envoyer/recevoir des chanes de caractres et des donnes binaires.
LAPI est simple.
Les mthodes :
socket.send(data),socket.close([code], [reason]).
Les vnements :
open,message,error,close.
WebSocket ne comprend pas lui seul la reconnexion, lauthentification et de nombreux autres mcanismes de haut niveau. Il existe donc des bibliothques client / serveur pour cela, et il est galement possible dimplmenter ces capacits manuellement.
Parfois, pour intgrer WebSocket dans un projet existant, les gens excutent le serveur WebSocket en parallle avec le serveur HTTP principal et partagent une seule base de donnes. Les requtes WebSocket utilisent wss://ws.site.com, un sous-domaine qui mne au serveur WebSocket, tandis que https://site.com va au serveur HTTP principal.
Certes, dautres modes dintgration sont galement possibles.
Commentaires
<code>, pour plusieurs lignes enveloppez-les avec la balise<pre>, pour plus de 10 lignes - utilisez une sandbox (plnkr, jsbin, codepen)