Использование WebSocketStream для создания клиента
Нестандартный: Эта функция нестандартна и не соответствует стандартам. Не используйте её на производственных сайтах, обращённых к веб-приложению: она не будет работать для всех пользователей. Также могут быть большие несовместимости между реализациями, и поведение может измениться в будущем.
API WebSocketStream — это основанная на Promise альтернатива WebSocket для создания и использования клиентских WebSocket-соединений на стороне клиента. WebSocketStream использует API потоков для обработки получения и отправки сообщений, что означает, что подключения к сокету могут автоматически использовать поток обратную связь (никаких дополнительных действий со стороны разработчика не требуется), регулируя скорость чтения или записи для предотвращения узких мест в приложении.
В этой статье объясняется, как использовать API WebSocketStream для создания клиента WebSocket.
Обнаружение функциональности
Чтобы проверить, поддерживается ли API WebSocketStream, можно использовать следующее:
if ("WebSocketStream" in self) {
// WebSocketStream is supported
}
Создание объекта WebSocketStream
Для создания клиента WebSocket необходимо сначала создать новый экземпляр WebSocketStream с помощью конструктора WebSocketStream(). В самом простом виде он принимает URL веб-сокет-сервера в качестве аргумента:
const wss = new WebSocketStream("wss://example.com/wss");
Он также может принимать объект с параметрами, содержащий пользовательские протоколы и/или AbortSignal (см. Закрытие соединения):
const controller = new AbortController();
const queueWSS = new WebSocketStream("wss://example.com/queue", {
protocols: ["amqp", "mqtt"],
signal: controller.signal,
});
Отправка и получение данных
Экземпляр WebSocketStream имеет свойство opened — это возвращает промис, который выполняется объектом, содержащим ReadableStream и WritableStream экземпляры после успешного открытия WebSocket-соединения:
const { readable, writable } = await wss.opened;
Вызов getReader() и getWriter() для этих объектов предоставляет нам ReadableStreamDefaultReader и WritableStreamDefaultWriter соответственно, которые могут использоваться для чтения и записи в подключение к сокету:
const reader = readable.getReader(); const writer = writable.getWriter();
Для записи данных в сокет можно использовать WritableStreamDefaultWriter.write():
writer.write("My message");
Для чтения данных из сокета можно непрерывно вызывать ReadableStreamDefaultReader.read(), пока поток не завершится, что указывается тем, что done является true:
while (true) {
const { value, done } = await reader.read();
if (done) {
break;
}
// Process value in some way
}
Браузер автоматически контролирует скорость, с которой клиент получает и отправляет данные, применяя обратную связь при необходимости. Если данные поступают быстрее, чем клиент может их read(), базовый API потоков применяет обратную связь к серверу. Кроме того, write() операции будут выполняться только в том случае, если это безопасно.
Закрытие соединения
С помощью WebSocketStream, информация, ранее доступная через WebSocket close и error события, теперь доступна через свойство closed — это возвращает промис, который выполняется объектом, содержащим код закрытия (см. полный список CloseEvent кодов статуса) и причину закрытия соединения сервером:
const { code, reason } = await wss.closed;
Как уже упоминалось, WebSocket-соединение можно закрыть с помощью AbortController. Необходимый AbortSignal передаётся в конструктор WebSocketStream при создании, а AbortController.abort() можно вызвать при необходимости:
const controller = new AbortController();
const wss = new WebSocketStream("wss://example.com/wss", {
signal: controller.signal,
});
// some time later
controller.abort();
В качестве альтернативы можно использовать метод WebSocketStream.close() для закрытия соединения. Это используется в основном в случае, если вы хотите указать пользовательский код и/или причину:
wss.close({
code: 4000,
reason: "Night draws to a close",
});
Примечание: В зависимости от настройки сервера и используемого кода состояния сервер может игнорировать пользовательский код в пользу корректного кода, соответствующего причине закрытия.
Полный пример клиента
Чтобы продемонстрировать основное использование WebSocketStream, мы создали пример клиента. Полный список можно увидеть внизу статьи, а ниже приведена соответствующая информация.
Примечание: Для работы примера вам также понадобится компонент сервера. Мы написали наш клиент для работы с сервером Deno, описанным в Написание WebSocket-сервера на JavaScript (Deno), но подойдет любой совместимый сервер.
HTML для демонстрации выглядит следующим образом. Он включает информационные <h2> и <p> элементы, <button> для закрытия WebSocket-соединения, который изначально отключён, и <div> для вывода сообщений.
<h2>WebSocketStream Test</h2> <p>Sends a ping every five seconds</p> <button id="close" disabled>Close socket connection</button> <div id="output"></div>
Теперь перейдём к JavaScript. Сначала мы получим ссылки на вывод <div> и кнопку закрытия <button>, а также определим вспомогательную функцию, которая записывает сообщения в <div>:
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 ... else для обнаружения WebSocketStream и вывода информационного сообщения в браузерах, которые не поддерживают эту функцию:
if (!("WebSocketStream" in self)) {
writeToScreen("Your browser does not support WebSocketStream");
} else {
// supporting code path
}
В поддерживаемом коде мы начнём с определения переменной, содержащей URL WebSocket-сервера, и создадим новый экземпляр WebSocketServer:
const wsURL = "ws://127.0.0.1/"; const wss = new WebSocketStream(wsURL);
Примечание: Лучшей практикой является использование защищённых WebSocket (wss://) в производственных приложениях. Однако в этой демонстрации мы подключаемся к localhost, поэтому нам необходимо использовать протокол WebSocket без защиты (ws://) для работы примера.
Основной объём нашего кода находится в функции start() , которую мы определяем и сразу же вызываем. Мы ждём промис opened, а затем, после его выполнения, записываем сообщение, чтобы сообщить читателю об успешном подключении, и создаём экземпляры ReadableStreamDefaultReader и WritableStreamDefaultWriter из возвращённых свойств readable и writable.
Далее мы создаём функцию start(), которая отправляет сообщения "ping" на сервер и получает сообщения "pong" обратно, и вызываем её. В теле функции мы ждём промис wss.opened и создаём читателя и писателя из его значений выполнения. После открытия сокета мы сообщаем об этом пользователю и активируем кнопку закрытия. Затем мы write() значение "ping" в сокет и сообщаем об этом пользователю. В этот момент сервер ответит сообщением "pong" . Мы ждём read() ответа, сообщаем его пользователю, затем пишем ещё одно "ping" в сервер после таймаута в 5 секунд. Это продолжается бесконечно "ping"/"pong" цикл.
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();
Примечание: Функция setTimeout() оборачивает вызов write() в блок try...catch, чтобы обработать любые ошибки, которые могут возникнуть, если приложение попытается записать в поток после его закрытия.
Теперь мы добавим раздел кода с использованием промисов, чтобы проинформировать пользователя о коде и причине, если WebSocket-соединение закрыто, как сигнализирует выполнение промиса closed:
wss.closed.then((result) => {
writeToScreen(
`DISCONNECTED: code ${result.closeCode}, message "${result.reason}"`,
);
console.log("Socket closed", result.closeCode, result.reason);
});
Наконец, мы добавим обработчик событий на кнопку закрытия, который закроет соединение с помощью метода close() с кодом и пользовательской причиной. Функция также отключает кнопку закрытия — мы не хотим, чтобы пользователи нажимали её, если соединение уже закрыто.
closeBtn.addEventListener("click", () => {
wss.close({
code: 1000,
reason: "That's all folks",
});
closeBtn.disabled = true;
});
Полный список
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>WebSocketStream Test</title>
</head>
<body>
<h2>WebSocketStream Test</h2>
<p>Sends a ping every five seconds</p>
<button id="close" disabled>Close socket connection</button>
<div id="output"></div>
<script>
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;
});
}
</script>
</body>
</html>
© 2005–2024 MDN contributors.
Licensed under the Creative Commons Attribution-ShareAlike License v2.5 or later.
https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Using_WebSocketStream