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
- Öffentliche Erläuterung
- WebSocketStream API-Demo | WebSocketStream API-Demoquelle
- Tracking-Fehler
- ChromeStatus.com-Eintrag
- Blink-Komponente:
Blink>Network>WebSockets
Danksagungen
Die WebSocketStream API wurde von Adam Rice und Yutaka Hirano implementiert.