WebSocketStream: интеграция потоков с API WebSocket,WebSocketStream: интеграция потоков с API WebSocket

Предотвратите затопление вашего приложения сообщениями WebSocket или затопление сервера WebSocket сообщениями, применяя противодавление.

Фон

API веб-сокетов

API WebSocket предоставляет интерфейс JavaScript для протокола WebSocket , который позволяет открыть сеанс двусторонней интерактивной связи между браузером пользователя и сервером. С помощью этого API вы можете отправлять сообщения на сервер и получать ответы на основе событий, не опрашивая сервер на предмет ответа.

API потоков

API Streams позволяет JavaScript программно получать доступ к потокам фрагментов данных, полученных по сети, и обрабатывать их по желанию. Важным понятием в контексте потоков является противодавление . Это процесс, с помощью которого один поток или цепочка каналов регулирует скорость чтения или записи. Когда сам поток или поток, стоящий позже в цепочке каналов, все еще занят и еще не готов принять дополнительные фрагменты, он отправляет сигнал обратно по цепочке, чтобы замедлить доставку, если это необходимо.

Проблема с текущим API WebSocket

Применение противодавления к полученным сообщениям невозможно.

В текущем API WebSocket реакция на сообщение происходит в WebSocket.onmessageEventHandler , вызываемом при получении сообщения от сервера.

Предположим, у вас есть приложение, которому необходимо выполнять тяжелые операции обработки данных при каждом получении нового сообщения. Вы, вероятно, настроили бы поток, аналогичный приведенному ниже коду, и, поскольку вы await результата вызова process() , у вас все будет хорошо, не так ли?

// 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);
};

Неправильный! Проблема с текущим API WebSocket заключается в том, что нет возможности применить противодавление. Когда сообщения поступают быстрее, чем process() может их обработать, процесс рендеринга либо заполнит память, буферизуя эти сообщения, либо перестанет отвечать на запросы из-за 100% загрузки ЦП, либо и то, и другое.

Применение противодавления к отправленным сообщениям неэргономично.

Применение обратного давления к отправленным сообщениям возможно, но требует опроса свойства WebSocket.bufferedAmount , что неэффективно и неэргономично. Это свойство, доступное только для чтения, возвращает количество байтов данных, которые были поставлены в очередь с помощью вызовов WebSocket.send() , но еще не переданы в сеть. Это значение сбрасывается до нуля после отправки всех данных в очереди, но если вы продолжите вызывать WebSocket.send() , оно будет продолжать расти.

Что такое API WebSocketStream?

API WebSocketStream решает проблему несуществующего или неэргономичного противодавления путем интеграции потоков с API WebSocket. Это означает, что противодавление можно применять «бесплатно», без каких-либо дополнительных затрат.

Предлагаемые варианты использования API WebSocketStream

Примеры сайтов, которые могут использовать этот API:

  • Приложения WebSocket с высокой пропускной способностью, которым необходимо сохранять интерактивность, в частности видео и совместное использование экрана.
  • Аналогично, видеозахват и другие приложения, генерирующие в браузере много данных, которые необходимо загрузить на сервер. Благодаря противодавлению клиент может прекратить создавать данные, а не накапливать их в памяти.

Текущее состояние

Шаг Положение дел
1. Создайте объяснитель Полный
2. Создайте первоначальный проект спецификации. В ходе выполнения
3. Соберите отзывы и доработайте дизайн В ходе выполнения
4. Пробная версия происхождения Полный
5. Запуск Не начался

Как использовать API WebSocketStream

Вводный пример

API WebSocketStream основан на обещаниях, что делает работу с ним естественной в современном мире JavaScript. Вы начинаете с создания нового WebSocketStream и передачи ему URL-адреса сервера WebSocket. Затем вы ждете opened соединения, в результате чего создается ReadableStream и/или WritableStream .

Вызвав метод ReadableStream.getReader() , вы, наконец, получаете ReadableStreamDefaultReader , из которого затем можно read() до тех пор, пока поток не будет завершен, то есть до тех пор, пока он не вернет объект формы {value: undefined, done: true} .

Соответственно, вызывая метод WritableStream.getWriter() , вы наконец получаете WritableStreamDefaultWriter , в который затем можно write() данные.

  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);
  }

Обратное давление

А как насчет обещанной функции противодавления? Как я писал выше, вы получаете его «бесплатно», никаких дополнительных действий не требуется. Если process() требует дополнительного времени, следующее сообщение будет использовано только после того, как конвейер будет готов. Аналогично, шаг WritableStreamDefaultWriter.write() будет выполнен только в том случае, если это безопасно.

Расширенные примеры

Второй аргумент WebSocketStream — это пакет опций, позволяющий будущее расширение. В настоящее время единственным вариантом является protocols , который ведет себя так же, как второй аргумент конструктора WebSocket :

const chatWSS = new WebSocketStream(CHAT_URL, {protocols: ['chat', 'chatv2']});
const {protocol} = await chatWSS.opened;

Выбранный protocol , а также потенциальные extensions являются частью словаря, доступного через обещание WebSocketStream.opened . Вся информация о реальном соединении предоставляется этим обещанием, поскольку она не имеет значения в случае сбоя соединения.

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

Информация о закрытом соединении WebSocketStream.

Информация, которая была доступна из событий WebSocket.onclose и WebSocket.onerror в API WebSocket, теперь доступна через обещание WebSocketStream.closed . Промис отклоняется в случае некорректного закрытия, в противном случае он преобразуется в код и причину, отправленные сервером.

Все возможные коды состояния и их значение объяснены в списке кодов состояния CloseEvent .

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

Закрытие соединения WebSocketStream

WebSocketStream можно закрыть с помощью AbortController . Поэтому передайте AbortSignal конструктору WebSocketStream .

const controller = new AbortController();
const wss = new WebSocketStream(URL, {signal: controller.signal});
setTimeout(() => controller.abort(), 1000);

В качестве альтернативы вы также можете использовать метод WebSocketStream.close() , но его основная цель — разрешить указать код и причину, которая отправляется на сервер.

wss.close({code: 4000, reason: 'Game over'});

Прогрессивное улучшение и совместимость

Chrome в настоящее время является единственным браузером, реализующим API WebSocketStream. Для совместимости с классическим API WebSocket применение обратного давления к полученным сообщениям невозможно. Применение обратного давления к отправленным сообщениям возможно, но требует опроса свойства WebSocket.bufferedAmount , что неэффективно и неэргономично.

Обнаружение функций

Чтобы проверить, поддерживается ли API WebSocketStream, используйте:

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

Демо

В поддерживаемых браузерах вы можете увидеть API WebSocketStream в действии во встроенном iframe или непосредственно в Glitch .

Обратная связь

Команда Chrome хочет услышать о вашем опыте работы с API WebSocketStream.

Расскажите нам о дизайне API

Что-то в API работает не так, как вы ожидали? Или вам не хватает методов или свойств, необходимых для реализации вашей идеи? У вас есть вопрос или комментарий по модели безопасности? Сообщите о проблеме спецификации в соответствующем репозитории GitHub или добавьте свои мысли к существующей проблеме.

Сообщить о проблеме с реализацией

Вы нашли ошибку в реализации Chrome? Или реализация отличается от спецификации? Сообщите об ошибке на сайте new.crbug.com . Обязательно укажите как можно больше подробностей, простые инструкции по воспроизведению и введите Blink>Network>WebSockets в поле «Компоненты» . Glitch отлично подходит для быстрого и простого обмена кейсами.

Показать поддержку API

Планируете ли вы использовать API WebSocketStream? Ваша публичная поддержка помогает команде Chrome расставлять приоритеты для функций и показывает другим поставщикам браузеров, насколько важно их поддерживать.

Отправьте твит @ChromiumDev , используя хэштег #WebSocketStream , и сообщите нам, где и как вы его используете.

Полезные ссылки

Благодарности

API WebSocketStream был реализован Адамом Райсом и Ютакой Хирано . Изображение героя, созданное Дааном Муиджем на Unsplash .