Pour éviter que votre application ne soit submergée de messages WebSocket ou n'inonde un serveur WebSocket, appliquez une contre-pression.
Contexte
L'API WebSocket fournit une interface JavaScript au protocole WebSocket, ce qui permet d'ouvrir une session de communication interactive bidirectionnelle entre le navigateur de l'utilisateur et un serveur. Avec cette API, vous pouvez envoyer des messages à un serveur et recevoir des réponses basées sur les événements sans interroger le serveur pour obtenir une réponse.
API Streams
L'API Streams permet à JavaScript d'accéder de manière programmatique aux flux de blocs de données reçus sur le réseau et de les traiter comme souhaité. Un concept important dans le contexte des flux est la pression de retour. Il s'agit du processus par lequel un seul flux ou une chaîne de canaux régule la vitesse de lecture ou d'écriture. Lorsque le flux lui-même ou un flux plus tard dans la chaîne de canalisation est toujours occupé et n'est pas encore prêt à accepter d'autres segments, il envoie un signal à rebours dans la chaîne pour ralentir la diffusion, le cas échéant.
Problème avec l'API WebSocket actuelle
Il est impossible d'appliquer une pression arrière aux messages reçus.
Avec l'API WebSocket actuelle, la réaction à un message se produit dans WebSocket.onmessage
, un EventHandler
appelé lorsqu'un message est reçu du serveur.
Supposons qu'une application doit effectuer des opérations de traitement de données lourdes chaque fois qu'un nouveau message est reçu.
Vous configureriez probablement le flux de manière similaire au code ci-dessous. Étant donné que vous await
le résultat de l'appel process()
, vous devriez être bon, n'est-ce pas ?
// A heavy data crunching operation.
const process = async (data) => {
return new Promise((resolve) => {
window.setTimeout(() => {
console.log('WebSocket message processed:', data);
return resolve('done');
}, 1000);
});
};
webSocket.onmessage = async (event) => {
const data = event.data;
// Await the result of the processing step in the message handler.
await process(data);
};
Faux ! Le problème avec l'API WebSocket actuelle est qu'il n'existe aucun moyen d'appliquer une pression en arrière.
Lorsque les messages arrivent plus rapidement que la méthode process()
ne peut les gérer, le processus de rendu remplit la mémoire en mettant en mémoire tampon ces messages, devient inactif en raison d'une utilisation du processeur à 100 %, ou les deux.
L'application d'une contre-pression aux messages envoyés n'est pas ergonomique
Il est possible d'appliquer une contre-pression aux messages envoyés, mais cela implique d'interroger la propriété WebSocket.bufferedAmount
, ce qui est inefficace et non ergonomique.
Cette propriété en lecture seule renvoie le nombre d'octets de données qui ont été mis en file d'attente à l'aide d'appels à WebSocket.send()
, mais qui n'ont pas encore été transmis au réseau.
Cette valeur est réinitialisée à zéro une fois que toutes les données mises en file d'attente ont été envoyées, mais si vous continuez à appeler WebSocket.send()
, elle continuera d'augmenter.
Qu'est-ce que l'API WebSocketStream ?
L'API WebSocketStream résout le problème de la contre-pression inexistante ou non ergonomique en intégrant des flux à l'API WebSocket. Cela signifie que la contre-pression peut être appliquée "sans frais", sans frais supplémentaires.
Suggestions de cas d'utilisation pour l'API WebSocketStream
Voici quelques exemples de sites pouvant utiliser cette API:
- Applications WebSocket à haut débit qui doivent conserver l'interactivité, en particulier la vidéo et le partage d'écran
- De même, la capture vidéo et d'autres applications qui génèrent beaucoup de données dans le navigateur doivent être importées sur le serveur. Avec la contre-pression, le client peut arrêter de produire des données au lieu de les accumuler en mémoire.
État actuel
Étape | État |
---|---|
1. Créer un message d'explication | Fin |
2. Créer une première ébauche de la spécification | En cours |
3. Recueillir des commentaires et itérer sur la conception | En cours |
4. Essai Origin | Fin |
5. Lancer | Non démarrée |
Utiliser l'API WebSocketStream
L'API WebSocketStream est basée sur des promesses, ce qui la rend naturelle dans un monde JavaScript moderne.
Commencez par créer un WebSocketStream
et lui transmettre l'URL du serveur WebSocket.
Ensuite, vous attendez que la connexion soit opened
, ce qui entraîne un ReadableStream
et/ou un WritableStream
.
En appelant la méthode ReadableStream.getReader()
, vous obtenez finalement un ReadableStreamDefaultReader
, à partir duquel vous pouvez read()
des données jusqu'à ce que le flux soit terminé, c'est-à-dire jusqu'à ce qu'il renvoie un objet de type {value: undefined, done: true}
.
Par conséquent, en appelant la méthode WritableStream.getWriter()
, vous obtenez finalement un WritableStreamDefaultWriter
, auquel vous pouvez ensuite write()
des données.
const wss = new WebSocketStream(WSS_URL);
const {readable, writable} = await wss.opened;
const reader = readable.getReader();
const writer = writable.getWriter();
while (true) {
const {value, done} = await reader.read();
if (done) {
break;
}
const result = await process(value);
await writer.write(result);
}
Contre-pression
Qu'en est-il de la fonctionnalité de contre-pression promise ?
Vous l'obtenez "sans frais", sans aucune étape supplémentaire.
Si process()
prend plus de temps, le message suivant n'est consommé qu'une fois le pipeline prêt.
De même, l'étape WritableStreamDefaultWriter.write()
ne se poursuit que si elle peut être effectuée en toute sécurité.
Exemples avancés
Le deuxième argument de WebSocketStream est un sac d'options permettant une extension future.
La seule option est protocols
, qui se comporte de la même manière que le deuxième argument du constructeur WebSocket:
const chatWSS = new WebSocketStream(CHAT_URL, {protocols: ['chat', 'chatv2']});
const {protocol} = await chatWSS.opened;
Le protocol
sélectionné ainsi que le extensions
potentiel font partie du dictionnaire disponible via la promesse WebSocketStream.opened
.
Toutes les informations sur la connexion en direct sont fournies par cette promesse, car il n'est pas pertinent si la connexion échoue.
const {readable, writable, protocol, extensions} = await chatWSS.opened;
Informations sur la connexion WebSocketStream fermée
Les informations disponibles dans les événements WebSocket.onclose
et WebSocket.onerror
de l'API WebSocket sont désormais disponibles via la promesse WebSocketStream.closed
.
La promesse est rejetée en cas de fermeture non propre, sinon elle se résout au code et au motif envoyés par le serveur.
Tous les codes d'état possibles et leur signification sont expliqués dans la liste des codes d'état CloseEvent
.
const {code, reason} = await chatWSS.closed;
Fermer une connexion WebSocketStream
Un WebSocketStream peut être fermé avec un AbortController
.
Par conséquent, transmettez un AbortSignal
au constructeur WebSocketStream
.
const controller = new AbortController();
const wss = new WebSocketStream(URL, {signal: controller.signal});
setTimeout(() => controller.abort(), 1000);
Vous pouvez également utiliser la méthode WebSocketStream.close()
, mais son objectif principal est de permettre de spécifier le code et le motif envoyés au serveur.
wss.close({code: 4000, reason: 'Game over'});
Amélioration progressive et interopérabilité
Chrome est actuellement le seul navigateur à implémenter l'API WebSocketStream.
Pour l'interopérabilité avec l'API WebSocket classique, il n'est pas possible d'appliquer une contre-pression aux messages reçus.
Il est possible d'appliquer une contre-pression aux messages envoyés, mais cela implique d'interroger la propriété WebSocket.bufferedAmount
, ce qui est inefficace et non ergonomique.
Détection de fonctionnalités
Pour vérifier si l'API WebSocketStream est compatible, utilisez:
if ('WebSocketStream' in window) {
// `WebSocketStream` is supported!
}
Démo
Dans les navigateurs compatibles, vous pouvez voir l'API WebSocketStream en action dans l'iframe intégrée ou directement sur Glitch.
Commentaires
L'équipe Chrome souhaite connaître votre expérience avec l'API WebSocketStream.
Parlez-nous de la conception de l'API
L'API ne fonctionne-t-elle pas comme prévu ? Ou manque-t-il des méthodes ou des propriétés dont vous avez besoin pour implémenter votre idée ? Vous avez une question ou un commentaire sur le modèle de sécurité ? Signalez un problème de spécification dans le dépôt GitHub correspondant ou ajoutez vos commentaires à un problème existant.
Signaler un problème d'implémentation
Avez-vous trouvé un bug dans l'implémentation de Chrome ?
Ou l'implémentation est-elle différente de la spécification ?
Signalez un bug sur new.crbug.com. Veillez à inclure autant de détails que possible, des instructions simples pour reproduire le problème et saisissez Blink>Network>WebSockets
dans le champ Composants.
Glitch est idéal pour partager des cas de reproduction rapidement et facilement.
Afficher la compatibilité avec l'API
Prévoyez-vous d'utiliser l'API WebSocketStream ? Votre soutien public aide l'équipe Chrome à hiérarchiser les fonctionnalités et montre aux autres fournisseurs de navigateurs à quel point il est essentiel de les prendre en charge.
Envoyez un tweet à @ChromiumDev en utilisant le hashtag #WebSocketStream
et indiquez-nous où et comment vous l'utilisez.
Liens utiles
- Explication publique
- Démonstration de l'API WebSocketStream | Source de la démonstration de l'API WebSocketStream
- Bug de suivi
- Enregistrement sur ChromeStatus.com
- Composant clignotement:
Blink>Network>WebSockets
Remerciements
L'API WebSocketStream a été implémentée par Adam Rice et Yutaka Hirano.