WebSocketStream: integracja strumieni z interfejsem API WebSocket

Stosuj wsteczne obciążenie, aby zapobiec zasypaniu aplikacji komunikatami WebSocket przez zasypanie jej wiadomościami przez serwer WebSocket.

Tło

Interfejs WebSocket API udostępnia interfejs JavaScriptu do protokołu WebSocket, który umożliwia prowadzenie dwukierunkowej interaktywnej sesji komunikacji między przeglądarką użytkownika a serwerem. Dzięki temu interfejsowi API możesz wysyłać wiadomości do serwera i otrzymywać odpowiedzi wywoływane zdarzeniami bez sprawdzania serwera w celu uzyskania odpowiedzi.

Streams API

Interfejs Streams API umożliwia programowi JavaScript uzyskiwanie dostępu do strumieni danych otrzymanych przez sieć i przetwarzanie ich zgodnie z potrzebami. Ważnym pojęciem w kontekście strumieni danych jest ciśnienie zwrotne. Jest to proces, w ramach którego pojedynczy strumień lub łańcuch potoku reguluje szybkość czytania lub pisania. Gdy strumień lub strumień w dalszej części łańcucha rurociągu jest nadal zajęty i nie jest jeszcze gotowy do przyjęcia kolejnych fragmentów, wysyła sygnał wstecz przez łańcuch, aby w razie potrzeby spowolnić przesyłanie.

Problem z obecnym interfejsem WebSocket API

Nie można zastosować wstecznego obciążenia do odebranych wiadomości

W obecnym interfejsie WebSocket API reagowanie na wiadomość odbywa się w WebSocket.onmessage, czyli w wywołanym EventHandler po otrzymaniu wiadomości z serwera.

Załóżmy, że masz aplikację, która musi wykonywać intensywne operacje przetwarzania danych za każdym razem, gdy otrzyma nową wiadomość. Skonfigurujesz prawdopodobnie proces podobny do tego poniżej. Skoro await wynik wywołania process() to nie problem, prawda?

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

Nieprawda. Problem z obecnym interfejsem WebSocket API polega na tym, że nie można zastosować obciążenia wstecznego. Gdy wiadomości docierają szybciej niż metoda process() może je obsłużyć, proces renderowania zapełnia pamięć, buforując te wiadomości, przestanie odpowiadać z powodu wykorzystania procesora w 100% lub w obu przypadkach.

Stosowanie ciśnienia wstecznego w przypadku wysłanych wiadomości jest nieergonomiczne

Zastosowanie ciśnienia zwrotnego do wysłanych wiadomości jest możliwe, ale wymaga odpytywania właściwości WebSocket.bufferedAmount, co jest niewydajne i nieergonomiczne. Ta właściwość tylko do odczytu zwraca liczbę bajtów danych, które zostały umieszczone w kolejce za pomocą wywołań funkcji WebSocket.send(), ale nie zostały jeszcze przesłane do sieci. Po wysłaniu wszystkich danych znajdujących się w kolejce ta wartość jest resetowana do zera, ale jeśli będziesz wywoływać funkcję WebSocket.send(), nadal będzie rosnąć.

Co to jest interfejs WebSocketStream API?

Interfejs WebSocketStream API rozwiązuje problem nieistniejącego lub nieergonomicznego sprzężenia zwrotnego poprzez integrację strumieni z interfejsem WebSocket API. Oznacza to, że odwrotny przepływ danych może być stosowany „bezpłatnie”, bez dodatkowych kosztów.

Sugerowane przypadki użycia interfejsu WebSocketStream API

Przykłady witryn, które mogą korzystać z tego interfejsu API:

  • Aplikacje WebSocket o dużej przepustowości, które muszą zachować interaktywność, w szczególności w przypadku udostępniania ekranu i filmów.
  • Podobnie jest w przypadku aplikacji do nagrywania filmów i innych aplikacji, które generują dużo danych w przeglądarce i muszą przesłać je na serwer. Dzięki temu klient może przestać generować dane zamiast gromadzić je w pamięci.

Obecny stan,

Krok Stan
1. Utwórz wyjaśnienie Zakończono
2. Utwórz początkową wersję roboczą specyfikacji W toku
3. Zbieraj opinie i ulepszaj projekt W toku
4. Wersja próbna origin Zakończono
5. Uruchom Nie rozpoczęto

Jak używać interfejsu WebSocketStream API

Interfejs WebSocketStream API opiera się na obietnicach, dzięki czemu radzenie sobie z nim we współczesnym świecie języka JavaScript wydaje się naturalne. Najpierw tworzysz nowy obiekt WebSocketStream i przekazujesz mu adres URL serwera WebSocket. Następnie czekasz, aż połączenie będzie opened, co spowoduje, że ReadableStream lub WritableStream.

Wywołując metodę ReadableStream.getReader(), otrzymujesz w końcu ReadableStreamDefaultReader, z którymi możesz read() korzystać do momentu zakończenia strumienia, czyli do momentu zwrócenia obiektu formularza{value: undefined, done: true}.

W związku z tym wywołanie metody WritableStream.getWriter() powoduje uzyskanie wartości WritableStreamDefaultWriter, którą można następnie przekazać do 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);
  }

Ciśnienie wsteczne

A co z obiecaną funkcją sprężenia wstecznego? Otrzymasz je „za darmo”, bez konieczności wykonywania dodatkowych czynności. Jeśli process() zajmuje więcej czasu, następna wiadomość jest przetwarzana dopiero po przygotowaniu potoku. Podobnie krok WritableStreamDefaultWriter.write() jest wykonywany tylko wtedy, gdy jest to bezpieczne.

Przykłady zaawansowane

Drugi argument funkcji WebSocketStream to pakiet opcji umożliwiający rozszerzenie w przyszłości. Jedyną opcją jest protocols, który działa tak samo jak drugi argument konstruktora WebSocket:

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

Wybrane protocol oraz potencjalne extensions są częścią słownika dostępnego w ramach WebSocketStream.opened. Wszystkie informacje o bezpośrednim połączeniu są zawarte w tej deklaracji, ponieważ w przypadku niepowodzenia połączenia nie są one istotne.

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

Informacje o zamkniętym połączeniu WebSocketStream

Informacje, które były dostępne z użyciem zdarzeń WebSocket.oncloseWebSocket.onerror w interfejsie WebSocket API, są teraz dostępne za pomocą obietnicy WebSocketStream.closed. Obietnica zostanie odrzucona w przypadku brudnego zamknięcia konta, a w przeciwnym razie będzie uwzględniać kod i przyczynę przesłane przez serwer.

Wszystkie możliwe kody stanu i ich znaczenie znajdziesz na liście kodów stanu CloseEvent.

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

Zamknięcie połączenia WebSocketStream

Strumień WebSocket można zamknąć za pomocąAbortController. W związku z tym przekaż AbortSignal do konstruktora WebSocketStream.

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

Możesz też użyć metody WebSocketStream.close(), której głównym celem jest umożliwienie określenia kodu i powodu, który jest wysyłany na serwer.

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

Stopniowe ulepszanie i interoperacyjność

Obecnie Chrome jest jedyną przeglądarką, w której zaimplementowano interfejs WebSocketStream API. Ze względu na interoperacyjność z klasycznym interfejsem WebSocket API nie można stosować ciśnienia zwrotnego do otrzymanych wiadomości. Zastosowanie ciśnienia zwrotnego do wysłanych wiadomości jest możliwe, ale wymaga odpytywania właściwości WebSocket.bufferedAmount, co jest niewydajne i nieergonomiczne.

Wykrywanie cech

Aby sprawdzić, czy interfejs WebSocketStream API jest obsługiwany, użyj:

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

Prezentacja

W obsługiwanych przeglądarkach interfejs WebSocketStream API możesz zobaczyć w działaniu w osadzonym elemencie iframe lub bezpośrednio w Glitch.

Prześlij opinię

Zespół Chrome chce poznać Twoje wrażenia związane z interfejsem WebSocketStream API.

Opowiedz nam o konstrukcji interfejsu API

Czy jest coś, co nie działa w interfejsie API zgodnie z oczekiwaniami? A może brakuje metod lub właściwości, których potrzebujesz do wdrożenia swojego pomysłu? Masz pytanie lub komentarz na temat modelu zabezpieczeń? Zgłoś problem ze specyfikacją w odpowiednim repozytorium GitHub lub dodaj swoje uwagi do istniejącego problemu.

Zgłaszanie problemów z implementacją

Czy znalazłeś/znalazłaś błąd w implementacji Chrome? Czy implementacja różni się od specyfikacji? Zgłoś błąd na stronie new.crbug.com. Pamiętaj, aby podać jak najwięcej szczegółów i proste instrukcje odtwarzania błędu. W polu Składniki wpisz Blink>Network>WebSockets. Glitch doskonale sprawdza się w przypadku szybkiego i łatwego udostępniania zgłoszeń.

Pokaż wsparcie dla interfejsu API

Zamierzasz używać interfejsu WebSocketStream API? Twoje publiczne wsparcie pomaga zespołowi Chrome ustalać priorytety funkcji i pokazuje innym dostawcom przeglądarek, jak ważne jest ich wsparcie.

Wyślij tweeta na adres @ChromiumDev, używając hashtagu #WebSocketStream, i daj nam znać, gdzie i jak go używasz.

Przydatne linki

Podziękowania

Interfejs WebSocketStream API został zaimplementowany przez Adama Rice i Yutaka Hirano.