WebSocketStream: Streams mit der WebSocket API integrieren

Verhindern Sie, dass Ihre App in WebSocket-Nachrichten ertrinken oder einen WebSocket-Server mit Nachrichten überflutet, indem Sie Gegendruck ausüben.

Hintergrund

Das WebSocket API

Die WebSocket API bietet eine JavaScript-Schnittstelle für das WebSocket-Protokoll, Dadurch wird es möglich, eine wechselseitige Kommunikationssitzung zu öffnen, zwischen dem Browser des Nutzers und einem Server. Mit dieser API können Sie Nachrichten an einen Server senden und ereignisgesteuerte Antworten erhalten ohne den Server nach einer Antwort fragen zu müssen.

Streams API

Die Streams API ermöglicht JavaScript, programmatisch auf Streams von Datenblöcken zuzugreifen, die über das Netzwerk empfangen wurden und wie gewünscht verarbeiten. Ein wichtiges Konzept im Zusammenhang mit Streams ist Rückstand. Dies ist der Prozess, bei dem ein einzelner Stream oder eine Pipe-Kette reguliert die Lese- oder Schreibgeschwindigkeit. Wenn der Stream selbst oder ein Stream später in der Pipe-Kette noch überlastet ist und noch nicht bereit ist, weitere Blöcke zu akzeptieren, wird ein Signal rückwärts durch die Kette gesendet, um die Zustellung entsprechend zu verlangsamen.

Das Problem mit der aktuellen WebSocket API

Es ist nicht möglich, empfangene Nachrichten zu entlasten.

Bei der aktuellen WebSocket API erfolgt die Reaktion auf eine Nachricht WebSocket.onmessage, Eine EventHandler wird aufgerufen, wenn eine Nachricht vom Server empfangen wird.

Nehmen wir an, Sie haben eine Anwendung, die umfangreiche Verarbeitungsvorgänge für Daten ausführen muss. wenn eine neue Nachricht eingeht. Den Ablauf würden Sie wahrscheinlich wie mit dem folgenden Code einrichten, Und da Sie das Ergebnis des process()-Aufrufs await, sollten Sie gut sein, richtig?

// 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);
};

Leider falsch! Das Problem mit der aktuellen WebSocket API ist, dass es keine Möglichkeit gibt, einen Rückstau zu erzeugen. Wenn Nachrichten schneller eingehen, als die Methode process() sie verarbeiten kann, füllt der Renderingprozess entweder den Arbeitsspeicher durch das Zwischenspeichern dieser Nachrichten, reagieren nicht mehr, weil die CPU zu 100% oder beides verwendet wird.

Das Anwenden von Gegendruck auf gesendete Nachrichten ist nicht ergonomisch

Es ist möglich, eine Gegendruck auf gesendete Nachrichten anzuwenden. Dazu müssen die WebSocket.bufferedAmount was ineffizient und nicht-ergonomisch ist. Dieses schreibgeschützte Attribut gibt die Anzahl der Datenbyte zurück, die in die Warteschlange gestellt wurden. mit Aufrufen an WebSocket.send(), aber noch nicht an das Netzwerk übertragen. Dieser Wert wird auf null zurückgesetzt, sobald alle Daten in der Warteschlange gesendet wurden. Wenn Sie jedoch weiterhin WebSocket.send() aufrufen, steigt er weiter an.

Was ist das WebSocketStream-API?

Die WebSocketStream API befasst sich mit dem Problem eines nicht vorhandenen oder nichtergonomischen Rückstaus indem Sie Streams in die WebSocket API integrieren. Das bedeutet, dass Rückdruck „kostenlos“ angewendet werden kann, ohne dass zusätzliche Kosten entstehen.

Empfohlene Anwendungsfälle für die WebSocketStream API

Beispiele für Websites, die diese API verwenden können:

  • WebSocket-Anwendungen mit hoher Bandbreite, die Interaktivität aufrechterhalten, insbesondere bei Videos und Bildschirmfreigaben.
  • Ähnlich verhält es sich mit Videoaufnahme und anderen Anwendungen, die viele Daten im Browser generieren. das auf den Server hochgeladen werden muss. Durch eine Überlastung kann der Client aufhören, Daten zu erzeugen, anstatt Daten im Speicher anzusammeln.

Aktueller Status

Schritt Status
1. Erklärende Mitteilung erstellen Abschließen
2. Ersten Entwurf der Spezifikation erstellen In Bearbeitung
3. Feedback einholen und Design iterieren In Bearbeitung
4. Ursprungstest Abschließen
5. Starten Nicht gestartet

WebSocketStream API verwenden

Einleitendes Beispiel

Die WebSocketStream API ist versprechenbasiert, wodurch sich der Umgang damit ganz normal anfühlt in einer modernen JavaScript-Welt. Sie beginnen mit dem Erstellen eines neuen WebSocketStream und übergeben ihm die URL des WebSocket-Servers. Als Nächstes warten Sie, bis die Verbindung opened ist, Das führt zu einer ReadableStream und/oder WritableStream

Durch Aufrufen der Methode ReadableStream.getReader() erhalten Sie schließlich eine ReadableStreamDefaultReader, Sie können dann read() Daten bis zum Abschluss des Streams, d. h. bis er ein Objekt der folgenden Form zurückgibt: {value: undefined, done: true}

Entsprechend kann durch Aufrufen der Methode WritableStream.getWriter() erhalten Sie schließlich eine WritableStreamDefaultWriter, Sie können dann write() an die Daten gesendet werden.

  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);
  }

Rückdruck

Wie sieht es mit der versprochenen Gegendruckfunktion aus? Wie bereits erwähnt, erhalten Sie die App „kostenlos“, es sind keine zusätzlichen Schritte erforderlich. Wenn process() zusätzliche Zeit in Anspruch nimmt, wird die nächste Nachricht erst verarbeitet, wenn die Pipeline bereit ist. Gleichermaßen der Schritt WritableStreamDefaultWriter.write() wird nur fortgesetzt, wenn dies sicher ist.

Erweiterte Beispiele

Das zweite Argument für WebSocketStream ist ein Options-Paket, das eine zukünftige Erweiterung ermöglicht. Derzeit ist die einzige Option protocols, das sich genauso verhält wie der zweites Argument für den WebSocket-Konstruktor:

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

Das ausgewählte protocol sowie das potenzielle extensions sind Teil des Wörterbuchs verfügbar über das WebSocketStream.opened-Promise. Alle Informationen über die Live-Verbindung werden durch dieses Promise bereitgestellt, da es nicht relevant ist, wenn die Verbindung fehlschlägt.

const {readable, writable, protocol, extensions} = await chatWSS.opened;

Informationen über eine geschlossene WebSocketStream-Verbindung

Die Informationen, die im WebSocket.onclose und WebSocket.onerror Termine im WebSocket API ist jetzt über das WebSocketStream.closed-Promise verfügbar. Das Versprechen wird im Falle eines nicht bereinigten Schließvorgangs abgelehnt. Andernfalls wird sie in den vom Server gesendeten Code und die Ursache aufgelöst.

Alle möglichen Statuscodes und ihre Bedeutung werden in der Liste der CloseEvent-Statuscodes.

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

WebSocketStream-Verbindung schließen

Ein WebSocketStream kann mit einem AbortController Übergeben Sie daher ein AbortSignal- WebSocketStream-Konstruktor übergeben.

const controller = new AbortController();
const wss = new WebSocketStream(URL, {signal: controller.signal});
setTimeout(() => controller.abort(), 1000);

Alternativ können Sie auch die Methode WebSocketStream.close() verwenden. aber sein Hauptzweck besteht darin, die Angabe des Attributs Code und der Grund, der an den Server gesendet wird.

wss.close({code: 4000, reason: 'Game over'});

Progressive Verbesserung und Interoperabilität

Chrome ist derzeit der einzige Browser, in dem die WebSocketStream API implementiert wird. Für die Interoperabilität mit der klassischen WebSocket API Es ist nicht möglich, empfangene Nachrichten zu entlasten. Es ist möglich, eine Gegendruck auf gesendete Nachrichten anzuwenden. Dazu müssen die WebSocket.bufferedAmount was ineffizient und nicht-ergonomisch ist.

Funktionserkennung

So überprüfen Sie, ob die WebSocketStream API unterstützt wird:

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

Demo

In unterstützten Browsern können Sie das WebSocketStream-API in Aktion im eingebetteten iFrame sehen, oder direkt über Glitch.

Feedback

Das Chrome-Team möchte mehr über Ihre Erfahrungen mit der WebSocketStream API erfahren.

Informationen zum API-Design

Funktioniert die API nicht wie erwartet? Oder fehlen Methoden oder Eigenschaften, die du zur Umsetzung deiner Idee benötigst? Haben Sie eine Frage oder einen Kommentar zum Sicherheitsmodell? Reichen Sie ein Spezifikationsproblem im entsprechenden GitHub-Repository ein. oder fügen Sie Ihre Gedanken zu einem vorhandenen Problem hinzu.

Problem mit der Implementierung melden

Haben Sie bei der Implementierung von Chrome einen Fehler gefunden? Oder weicht die Implementierung von der Spezifikation ab? Melde einen Fehler unter new.crbug.com. Gib so viele Details wie möglich, einfache Anweisungen zum Reproduzieren und geben Sie Blink>Network>WebSockets in das Feld Components ein. Glitch eignet sich hervorragend, um schnell und einfach Reproduktionsfälle zu teilen.

Unterstützung für die API anzeigen

Möchten Sie die WebSocketStream API verwenden? Ihr öffentlicher Support hilft dem Chrome-Team bei der Priorisierung von Funktionen und zeigt anderen Browseranbietern, wie wichtig es ist, sie zu unterstützen.

Sende einen Tweet mit dem Hashtag an @ChromiumDev #WebSocketStream und teilen Sie uns mit, wo und wie Sie sie nutzen.

Nützliche Links

Danksagungen

Die WebSocketStream API wurde von Adam Rice implementiert und Yutaka Hirano Hero-Image von Daan Mooij auf Unsplash (Unsplash).