WebSocketStream: Streams mit der WebSocket API integrieren

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

Hintergrund

Die WebSocket API bietet eine JavaScript-Schnittstelle für das WebSocket-Protokoll, mit der eine wechselseitige interaktive Kommunikationssitzung zwischen dem Browser des Nutzers und einem Server geöffnet werden kann. 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

Mit der Streams API kann JavaScript programmatisch auf Streams von Datenblöcken zugreifen, die über das Netzwerk empfangen wurden, und diese wie gewünscht verarbeiten. Ein wichtiges Konzept im Zusammenhang mit Streams ist Rückstand. Dabei wird die Lese- oder Schreibgeschwindigkeit durch einen einzelnen Stream oder eine Pipe-Kette geregelt. Wenn der Stream selbst oder ein Stream später in der Pipe-Kette noch ausgelastet ist und noch nicht bereit ist, weitere Blöcke zu akzeptieren, sendet er ein Signal rückwärts durch die Kette, um die Zustellung entsprechend zu verlangsamen.

Das Problem mit der aktuellen WebSocket API

Es ist nicht möglich, Backpressure auf empfangene Nachrichten anzuwenden.

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

Angenommen, Sie haben eine Anwendung, die bei jedem Eingang einer neuen Nachricht umfangreiche Verarbeitungsvorgänge für Daten ausführen muss. Wahrscheinlich würden Sie den Ablauf ähnlich wie im folgenden Code einrichten, und da Sie await das Ergebnis des process()-Aufrufs ausführen, sollte alles in Ordnung sein.

// 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 besteht darin, dass kein Backpressure angewendet werden kann. Wenn Nachrichten schneller eingehen, als die Methode process() verarbeiten kann, füllt der Renderingprozess entweder den Arbeitsspeicher durch Zwischenspeichern dieser Nachrichten oder reagiert aufgrund einer CPU-Auslastung zu 100% nicht mehr oder beides.

Das Anwenden von Backpressure auf gesendete Nachrichten ist nicht ergonomisch

Es ist möglich, eine Gegendruck auf gesendete Nachrichten anzuwenden. Dazu muss das Attribut WebSocket.bufferedAmount abgefragt werden, das ineffizient und nicht ergonomisch ist. Diese schreibgeschützte Eigenschaft gibt die Anzahl der Datenbyte zurück, die über Aufrufe von WebSocket.send() in die Warteschlange gestellt, aber noch nicht an das Netzwerk übertragen wurden. Dieser Wert wird auf null zurückgesetzt, sobald alle Daten in der Warteschlange gesendet wurden. Wenn du WebSocket.send() jedoch weiterhin aufrufst, steigt er weiter an.

Was ist das WebSocketStream-API?

Die WebSocketStream API behebt das Problem des nicht vorhandenen oder nicht ergonomischen Backpressure, indem Streams in die WebSocket API eingebunden werden. Das bedeutet, dass Backpressure „kostenlos“ angewendet werden kann, ohne zusätzliche Kosten.

Empfohlene Anwendungsfälle für die WebSocketStream API

Beispiele für Websites, auf denen diese API verwendet werden kann:

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

Aktueller Status

Schritt Status
1. Erläuternde 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

Die WebSocketStream API basiert auf Promises, was die Verwendung in einer modernen JavaScript-Umgebung natürlich macht. Erstellen Sie zuerst eine neue WebSocketStream und übergeben Sie ihr die URL des WebSocket-Servers. Als Nächstes warten Sie, bis die Verbindung opened ist, was zu einer ReadableStream und/oder einer WritableStream führt.

Durch Aufrufen der Methode ReadableStream.getReader() erhalten Sie schließlich ein ReadableStreamDefaultReader, aus dem Sie dann read()-Daten abrufen können, bis der Stream beendet ist, d. h. bis ein Objekt vom Typ {value: undefined, done: true} zurückgegeben wird.

Wenn Sie also die Methode WritableStream.getWriter() aufrufen, erhalten Sie eine WritableStreamDefaultWriter, auf die Sie dann Daten write() schreiben können.

  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

Was ist mit der versprochenen Gegendruckfunktion? Sie erhalten ihn „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. Ebenso wird der Schritt WritableStreamDefaultWriter.write() 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. Die einzige Option ist protocols, die sich genauso verhält wie das zweite 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, das über das Promise WebSocketStream.opened verfügbar ist. Alle Informationen zur Live-Verbindung werden über dieses Versprechen bereitgestellt, da es nicht relevant ist, ob die Verbindung fehlschlägt.

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

Informationen über eine geschlossene WebSocketStream-Verbindung

Die Informationen aus den Ereignissen WebSocket.onclose und WebSocket.onerror in der WebSocket API sind jetzt über das Promise WebSocketStream.closed verfügbar. Das Versprechen wird bei einem nicht korrekten Schließen abgelehnt. Andernfalls wird der vom Server gesendete Code und Grund zurückgegeben.

Alle möglichen Statuscodes und ihre Bedeutung werden in der Liste der CloseEvent Statuscodes erläutert.

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

WebSocketStream-Verbindung schließen

Ein WebSocketStream kann mit einem AbortController geschlossen werden. Übergeben Sie daher ein AbortSignal an den WebSocketStream-Konstruktor.

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. Der Hauptzweck besteht jedoch darin, den Code und den Grund anzugeben, 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 ist es nicht möglich, empfangene Nachrichten rückwirkend zu verarbeiten. Es ist möglich, Backpressure auf gesendete Nachrichten anzuwenden. Dazu muss jedoch das Attribut WebSocket.bufferedAmount abgefragt werden, was ineffizient und nicht ergonomisch ist.

Funktionserkennung

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

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

Demo

In unterstützten Browsern kannst du die WebSocketStream API in Aktion im eingebetteten iFrame oder direkt in Glitch sehen.

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 Sie für die Implementierung Ihrer Idee benötigen? Haben Sie eine Frage oder einen Kommentar zum Sicherheitsmodell? Reichen Sie ein Problem mit der Spezifikation im entsprechenden GitHub-Repository ein oder kommentieren Sie ein vorhandenes Problem.

Problem mit der Implementierung melden

Haben Sie bei der Implementierung von Chrome einen Fehler gefunden? Oder unterscheidet sich die Implementierung von der Spezifikation? Melden Sie den Fehler unter new.crbug.com. Geben Sie dabei so viele Details wie möglich an, eine einfache Anleitung zur Reproduktion und geben Sie Blink>Network>WebSockets in das Feld Components ein. Glitch eignet sich hervorragend, um schnell und einfach reproduzierbare Fälle zu teilen.

Unterstützung für die API anzeigen

Beabsichtigen Sie, die WebSocketStream API zu verwenden? Ihre öffentliche Unterstützung hilft dem Chrome-Team, Funktionen zu priorisieren, und zeigt anderen Browseranbietern, wie wichtig es ist, sie zu unterstützen.

Sende einen Tweet mit dem Hashtag #WebSocketStream an @ChromiumDev und teile uns mit, wo und wie du ihn verwendest.

Nützliche Links

Danksagungen

Die WebSocketStream API wurde von Adam Rice und Yutaka Hirano implementiert.