Spec-Zone.ru › Web APIs

Использование 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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API