Verwendung von WebSocketStream zur Erstellung eines Clients
Nicht standardisiert: Diese Funktion ist nicht standardisiert. Wir raten davon ab, nicht-standardisierte Funktionen auf produktiven Webseiten zu verwenden, da sie nur von bestimmten Browsern unterstützt werden und sich in Zukunft ändern oder entfernt werden können. Unter Umständen kann sie jedoch eine geeignete Option sein, wenn es keine standardisierte Alternative gibt.
Die WebSocketStream
API ist eine auf Promise
basierende Alternative zu WebSocket
, um Client-seitige WebSocket-Verbindungen zu erstellen und zu nutzen. WebSocketStream
verwendet die Streams API, um Nachrichten zu empfangen und zu senden, was bedeutet, dass Socket-Verbindungen automatisch von Stream-Backpressure profitieren (keine zusätzlichen Aktionen des Entwicklers erforderlich), um die Geschwindigkeit des Lesens oder Schreibens zu regulieren und so Engpässe in der Anwendung zu vermeiden.
Dieser Artikel erklärt, wie Sie die WebSocketStream
API verwenden, um einen WebSocket-Client zu erstellen.
Funktionserkennung
Um zu prüfen, ob die WebSocketStream
API unterstützt wird, können Sie Folgendes verwenden:
if ("WebSocketStream" in self) {
// WebSocketStream is supported
}
Erstellen eines WebSocketStream-Objekts
Um einen WebSocket-Client zu erstellen, müssen Sie zuerst eine neue WebSocketStream
-Instanz mithilfe des WebSocketStream()
Konstruktors erstellen. In seiner einfachsten Form nimmt es die URL des WebSocket-Servers als Argument:
const wss = new WebSocketStream("wss://example.com/wss");
Es kann auch ein Optionsobjekt mit benutzerdefinierten Protokollen und/oder einem AbortSignal
enthalten (siehe Schließen der Verbindung):
const controller = new AbortController();
const queueWSS = new WebSocketStream("wss://example.com/queue", {
protocols: ["amqp", "mqtt"],
signal: controller.signal,
});
Senden und Empfangen von Daten
Die WebSocketStream
-Instanz hat eine opened
Eigenschaft — diese gibt ein Promise zurück, das mit einem Objekt erfüllt wird, das eine ReadableStream
und eine WritableStream
Instanz enthält, sobald die WebSocket-Verbindung erfolgreich geöffnet wurde:
const { readable, writable } = await wss.opened;
Das Aufrufen von getReader()
und getWriter()
an diesen Objekten liefert uns einen ReadableStreamDefaultReader
und einen WritableStreamDefaultWriter
bzw., die verwendet werden können, um von der Socket-Verbindung zu lesen und in diese zu schreiben:
const reader = readable.getReader();
const writer = writable.getWriter();
Um Daten an die Socket-Verbindung zu senden, können Sie WritableStreamDefaultWriter.write()
verwenden:
writer.write("My message");
Um Daten von der Socket-Verbindung zu lesen, können Sie kontinuierlich ReadableStreamDefaultReader.read()
aufrufen, bis der Stream abgeschlossen ist, was durch done
als true
angezeigt wird:
while (true) {
const { value, done } = await reader.read();
if (done) {
break;
}
// Process value in some way
}
Der Browser steuert automatisch die Geschwindigkeit, mit der der Client Daten empfängt und sendet, indem er bei Bedarf Backpressure anwendet. Wenn Daten schneller ankommen, als der Client sie lesen()
kann, übt die zugrundeliegende Streams API Backpressure auf den Server aus. Darüber hinaus werden write()
-Operationen nur fortgesetzt, wenn es sicher ist, dies zu tun.
Schließen der Verbindung
Mit WebSocketStream
sind die Informationen, die zuvor über die WebSocket
-Ereignisse close
und error
verfügbar waren, nun über die closed
Eigenschaft verfügbar - diese gibt ein Promise zurück, das mit einem Objekt erfüllt wird, das den Schließcode (siehe die vollständige Liste der CloseEvent
-Statuscodes) und einen Grund enthält, warum der Server die Verbindung geschlossen hat:
const { code, reason } = await wss.closed;
Wie bereits erwähnt, kann die WebSocket-Verbindung mithilfe eines AbortController
geschlossen werden. Das notwendige AbortSignal
wird dem WebSocketStream
-Konstruktor bei der Erstellung übergeben, und AbortController.abort()
kann dann bei Bedarf aufgerufen werden:
const controller = new AbortController();
const wss = new WebSocketStream("wss://example.com/wss", {
signal: controller.signal,
});
// some time later
controller.abort();
Alternativ können Sie die Methode WebSocketStream.close()
verwenden, um eine Verbindung zu schließen. Dies wird hauptsächlich verwendet, wenn Sie einen benutzerdefinierten Code und/oder Grund angeben möchten:
wss.close({
code: 4000,
reason: "Night draws to a close",
});
Hinweis: Abhängig von der Serverkonfiguration und dem verwendeten Statuscode kann es sein, dass der Server einen benutzerdefinierten Code zugunsten eines gültigen Codes ignoriert, der für den Schließgrund korrekt ist.
Ein vollständiger Beispiel-Client
Um die grundlegende Verwendung von WebSocketStream
zu demonstrieren, haben wir einen Beispiel-Client erstellt. Sie können die vollständige Liste am Ende des Artikels einsehen und der folgenden Erklärung folgen.
Hinweis: Damit das Beispiel funktioniert, benötigen Sie auch eine Serverkomponente. Wir haben unseren Client so geschrieben, dass er mit dem in Writing a WebSocket server in JavaScript (Deno) erklärten Deno-Server zusammenarbeitet, aber jeder kompatible Server ist geeignet.
Das HTML für die Demo sieht folgendermaßen aus. Es enthält informative <h2>
und <p>
Elemente, einen <button>
zum Schließen der WebSocket-Verbindung, der initial deaktiviert ist, und ein <div>
für uns, um Ausgabemeldungen hineinzuschreiben.
<h2>WebSocketStream Test</h2>
<p>Sends a ping every five seconds</p>
<button id="close" disabled>Close socket connection</button>
<div id="output"></div>
Nun zum JavaScript. Zuerst holen wir uns Referenzen auf das Ausgabe-<div>
und den Schließen-<button>
, und definieren eine Hilfsfunktion, die Nachrichten in das <div>
schreibt:
const output = document.querySelector("#output");
const closeBtn = document.querySelector("#close");
function writeToScreen(message) {
const pElem = document.createElement("p");
pElem.textContent = message;
output.appendChild(pElem);
}
Als Nächstes erstellen wir eine if...else
-Struktur, um die Unterstützung von WebSocketStream
zu erkennen und eine informative Nachricht in nicht unterstützenden Browsern auszugeben:
if (!("WebSocketStream" in self)) {
writeToScreen("Your browser does not support WebSocketStream");
} else {
// supporting code path
}
Im unterstützenden Codepfad beginnen wir mit der Definition einer Variable, die die URL des WebSocket-Servers enthält, und konstruieren eine neue WebSocketServer
-Instanz:
const wsURL = "ws://127.0.0.1/";
const wss = new WebSocketStream(wsURL);
Hinweis:
Beste Praxis ist es, sichere WebSockets (wss://
) in Produktions-Apps zu verwenden. In diesem Demo verbinden wir uns jedoch mit localhost, daher müssen wir das unsichere WebSocket-Protokoll (ws://
) verwenden, damit das Beispiel funktioniert.
Der Hauptteil unseres Codes ist in der start()
-Funktion enthalten, die wir definieren und dann sofort aufrufen. Wir warten auf das opened
Promise, dann, nachdem es erfüllt ist, schreiben wir eine Nachricht, um den Leser wissen zu lassen, dass die Verbindung erfolgreich ist, und erstellen ReadableStreamDefaultReader
und WritableStreamDefaultWriter
Instanzen von den zurückgegebenen readable
und writable
Eigenschaften.
Als Nächstes erstellen wir eine start()
-Funktion, die "ping"-Nachrichten an den Server sendet und "pong"-Nachrichten zurückerhält, und rufen sie auf. Im Funktionskörper warten wir auf das wss.opened
Promise und erstellen einen Leser und Schreiber von seinen Erfüllungswerten. Sobald die Socket-Verbindung geöffnet ist, teilen wir dies dem Benutzer mit und aktivieren den Schließen-Button. Als Nächstes write()
wir einen "ping"
-Wert an die Socket-Verbindung und teilen dies dem Benutzer mit. Zu diesem Zeitpunkt wird der Server mit einer "pong"
-Nachricht antworten. Wir warten auf das read()
der Antwort, kommunizieren sie dem Benutzer, dann schreiben wir nach einer Pause von 5 Sekunden ein weiteres "ping"
an den Server. Dies setzt den "ping"
/"pong"
-Loop auf unbestimmte Zeit fort:
async function start() {
const { readable, writable } = await wss.opened;
writeToScreen("CONNECTED");
closeBtn.disabled = false;
const reader = readable.getReader();
const writer = writable.getWriter();
writer.write("ping");
writeToScreen("SENT: ping");
while (true) {
const { value, done } = await reader.read();
writeToScreen(`RECEIVED: ${value}`);
if (done) {
break;
}
setTimeout(async () => {
try {
await writer.write("ping");
writeToScreen("SENT: ping");
} catch (e) {
writeToScreen(`Error writing to socket: ${e.message}`);
}
}, 5000);
}
}
start();
Hinweis:
Die Funktion setTimeout()
umschließt den write()
-Aufruf in einem try...catch
-Block, um etwaige Fehler zu handhaben, die auftreten können, wenn die Anwendung versucht, in den Stream zu schreiben, nachdem er geschlossen wurde.
Wir schließen nun einen Promise-basierten Codeabschnitt ein, um den Benutzer über den Code und den Grund zu informieren, wenn die WebSocket-Verbindung geschlossen wird, was durch die Erfüllung des closed
Promises signalisiert wird:
wss.closed.then((result) => {
writeToScreen(
`DISCONNECTED: code ${result.closeCode}, message "${result.reason}"`,
);
console.log("Socket closed", result.closeCode, result.reason);
});
Schließlich fügen wir einen Ereignis-Listener zum Schließen-Button hinzu, der die Verbindung mithilfe der close()
-Methode schließt, mit einem Code und einem benutzerdefinierten Grund. Die Funktion deaktiviert auch den Schließen-Button — wir möchten nicht, dass Benutzer ihn drücken, wenn die Verbindung bereits geschlossen ist.
closeBtn.addEventListener("click", () => {
wss.close({
code: 1000,
reason: "That's all folks",
});
closeBtn.disabled = true;
});
Vollständige Auflistung
const output = document.querySelector("#output");
const closeBtn = document.querySelector("#close");
function writeToScreen(message) {
const pElem = document.createElement("p");
pElem.textContent = message;
output.appendChild(pElem);
}
if (!("WebSocketStream" in self)) {
writeToScreen("Your browser does not support WebSocketStream");
} else {
const wsURL = "ws://127.0.0.1/";
const wss = new WebSocketStream(wsURL);
console.log(wss.url);
async function start() {
const { readable, writable, extensions, protocol } = await wss.opened;
writeToScreen("CONNECTED");
closeBtn.disabled = false;
const reader = readable.getReader();
const writer = writable.getWriter();
writer.write("ping");
writeToScreen("SENT: ping");
while (true) {
const { value, done } = await reader.read();
writeToScreen(`RECEIVED: ${value}`);
if (done) {
break;
}
setTimeout(() => {
writer.write("ping");
writeToScreen("SENT: ping");
}, 5000);
}
}
start();
wss.closed.then((result) => {
writeToScreen(
`DISCONNECTED: code ${result.closeCode}, message "${result.reason}"`,
);
console.log("Socket closed", result.closeCode, result.reason);
});
closeBtn.addEventListener("click", () => {
wss.close({
code: 1000,
reason: "That's all folks",
});
closeBtn.disabled = true;
});
}