WebSocketStream: intégrer des flux à l'API WebSocket

Empêchez votre application de se noyer dans les messages WebSocket ou d'inonder un serveur WebSocket de messages en appliquant une contre-pression.

Contexte

API WebSocket

L'API WebSocket fournit une interface JavaScript au protocole WebSocket, ce qui permet d'ouvrir une session de communication interactive dans les deux sens 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 des é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 fragments de données reçus sur le réseau et les traiter comme vous le souhaitez. Dans le contexte des flux, le concept contre-pression. Il s'agit du processus par lequel un seul flux ou une chaîne régule la vitesse de lecture ou d'écriture. Lorsque le flux lui-même ou un flux ultérieur dans la chaîne du pipeline est toujours occupé et qu'elle n'est pas encore prête à accepter plus de fragments, il envoie un signal en arrière à travers la chaîne pour ralentir la livraison le cas échéant.

Le problème avec l'API WebSocket actuelle

Il est impossible d'appliquer une contre-pression aux messages reçus

Avec l'API WebSocket actuelle, la réaction à un message s'effectue dans WebSocket.onmessage, un EventHandler appelé lorsqu'un message est reçu du serveur.

Supposons que vous ayez une application qui doit effectuer des opérations intensives de traitement de données chaque fois qu'un nouveau message est reçu. Vous configurerez probablement le flux comme le code ci-dessous, Et comme vous utilisez await pour obtenir le résultat de l'appel process(), tout devrait fonctionner correctement, 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'y a aucun moyen d'appliquer une contre-pression. Lorsque les messages arrivent plus rapidement que la méthode process() ne peut les gérer, le processus de rendu remplira la mémoire en mettant en mémoire tampon ces messages, ne répondent plus à cause d'une utilisation du processeur de 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 vous devez interroger WebSocket.bufferedAmount ce qui est inefficace et non ergonomique. Cette propriété en lecture seule renvoie le nombre d'octets de données mis en file d'attente à l'aide d'appels vers WebSocket.send(), mais pas encore transmises au réseau. Cette valeur est réinitialisée une fois que toutes les données en file d’attente ont été envoyées, mais si vous appelez WebSocket.send() sans cesse, il va continuer à grimper.

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.

Cas d'utilisation suggérés pour l'API WebSocketStream

Voici quelques exemples de sites qui peuvent utiliser cette API:

  • Applications WebSocket à haut débit qui doivent conserver l'interactivité en particulier pour la vidéo et le partage d'écran.
  • De même, les applications de capture vidéo et autres qui génèrent beaucoup de données dans le navigateur à importer 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 une vidéo explicative Fin
2. Créer l'ébauche initiale de la spécification En cours
3. Recueillir des commentaires et itérer sur la conception En cours
4. Phase d'évaluation Fin
5. Lancer Non démarré

Utiliser l'API WebSocketStream

Exemple d'introduction

L'API WebSocketStream est basée sur des promesses, ce qui rend son traitement naturel dans un monde JavaScript moderne. Commencez par construire un nouveau WebSocketStream et transmettez-lui 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 enfin une ReadableStreamDefaultReader, Vous pouvez ensuite read() données jusqu'à la fin du flux, c'est-à-dire jusqu'au renvoi d'un objet au format {value: undefined, done: true}

Par conséquent, en appelant la méthode WritableStream.getWriter() vous obtenez enfin une WritableStreamDefaultWriter, Vous pouvez ensuite write() les 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 annoncée ? Comme je vous l'ai dit plus haut, vous bénéficiez d'un accès "sans frais" et ne nécessite aucune autre action de votre part. Si la fonction process() prend plus de temps, le message suivant n'est consommé que lorsque le pipeline est prêt. De même, l'étape WritableStreamDefaultWriter.write() ne poursuivra que si cela ne présente aucun risque.

Exemples avancés

Le deuxième argument de WebSocketStream est un sac d'options qui permet une extension future. Actuellement, la seule option est protocols, qui se comporte de la même manière que deuxième argument du constructeur WebSocket:

const chatWSS = new WebSocketStream(CHAT_URL, {protocols: ['chat', 'chatv2']});
const {protocol} = await chatWSS.opened;

L'élément protocol sélectionné et les extensions potentiels font partie du dictionnaire disponible via la promesse WebSocketStream.opened. Toutes les informations concernant la connexion en direct sont fournies par cette promesse. car ce 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 le WebSocket.onclose et WebSocket.onerror événements de l'API WebSocket est désormais disponible via la promesse WebSocketStream.closed. La promesse rejette en cas de clôture impropre, sinon il 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 le Liste des codes d'état CloseEvent.

const {code, reason} = await chatWSS.closed;

Fermer une connexion WebSocketStream

Un WebSocketStream peut être fermé à l'aide d'une 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 code et la raison qui sont envoyées 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 assurer 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 vous devez interroger WebSocket.bufferedAmount ce qui est inefficace et non ergonomique.

Détection de caractéristiques

Pour vérifier si l'API WebSocketStream est compatible, utilisez:

if ('WebSocketStream' in window) {
  // `WebSocketStream` is supported!
}

Démo

Sur les navigateurs compatibles, vous pouvez voir l'API WebSocketStream en action dans l'iFrame intégré, ou directement sur Glitch.

Commentaires

L'équipe Chrome souhaite connaître votre avis sur l'API WebSocketStream.

Présentez-nous la conception de l'API

Y a-t-il un aspect de l'API qui ne fonctionne pas comme prévu ? Ou s'il manque des méthodes ou des propriétés dont vous avez besoin pour mettre en œuvre 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 ajouter vos réflexions à un problème existant.

Signaler un problème d'implémentation

Avez-vous détecté un bug dans l'implémentation de Chrome ? Ou l'implémentation est-elle différente des spécifications ? 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 la zone Composants. Glitch est idéal pour partager des cas de reproduction rapide et facile.

Apportez votre soutien à l'API

Prévoyez-vous d'utiliser l'API WebSocketStream ? Votre assistance publique permet à l'équipe Chrome de 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 n'hésitez pas à nous dire où et comment vous l'utilisez.

Liens utiles

Remerciements

L'API WebSocketStream a été implémentée par Adam Rice et Yutaka Hirano Image héros de Daan Mooij sur Unsplash.