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 API WebSocket

Interfejs WebSocket API udostępnia interfejs JavaScript dla protokołu WebSocket, co pozwala rozpocząć dwukierunkową interaktywną sesję komunikacji między przeglądarką użytkownika a serwerem. Za pomocą tego interfejsu API możesz wysyłać wiadomości do serwera i otrzymywać odpowiedzi oparte na zdarzeniach bez odpytywania serwera w poszukiwaniu odpowiedzi.

Streams API

Streams API umożliwia programowi JavaScript dostęp do strumieni danych odbieranych przez sieć i przetwarzać je zgodnie z potrzebami. Ważną kwestią w kontekście strumieni backpressure. Jest to proces, w którym pojedynczy strumień lub łańcuch rur reguluje szybkość czytania lub pisania. Gdy strumień lub strumień później w łańcuchu potoków jest nadal zajęty i nie jest gotowy na przyjmowanie kolejnych fragmentów, wysyła sygnał wstecz przez łańcuch, aby odpowiednio spowolnić dostarczanie.

Problem z bieżącym interfejsem WebSocket API

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

W obecnym interfejsie WebSocket API reagowanie na wiadomość odbywa się WebSocket.onmessage funkcja EventHandler jest wywoływana po odebraniu wiadomości z serwera.

Załóżmy, że mamy aplikację, która musi wykonywać intensywne operacje crunchingu danych za każdym razem, gdy odebrana jest nowa wiadomość. Skonfigurujesz proces podobny do poniższego kodu, a ponieważ await wynik rozmowy w usłudze 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);
};

Błąd! Problem z obecnym interfejsem WebSocket API polega na tym, że nie można zastosować obciążenia wstecznego. Gdy wiadomości przychodzą szybciej, niż metoda process() jest w stanie je obsłużyć, proces renderowania zapełnia pamięć, buforując te wiadomości. przestały reagować z powodu wykorzystania procesora w 100% lub obu tych przypadków.

Stosowanie prepresji wstecznej do wysyłanych wiadomości nie jest ergonomiczne

Zastosowanie wstecznego obciążenia do wysyłanych wiadomości jest możliwe, ale obejmuje odpytywanie WebSocket.bufferedAmount która jest niewydajna i nieergonomiczna. Ta usługa tylko do odczytu zwraca liczbę bajtów danych, które zostały umieszczone w kolejce używa połączeń z WebSocket.send() ale jeszcze nie są przesyłane do sieci. Po przesłaniu wszystkich danych znajdujących się w kolejce ta wartość jest resetowana do zera. ale jeśli będziesz nadal dzwonić pod numer WebSocket.send(), ale nadal będzie się wspinać.

Co to jest interfejs API WebSocketStream?

Interfejs WebSocketStream API rozwiązuje problem nieistniejącego lub nieergonomicznego wstecznego obciążenia przez integrację strumieni z interfejsem WebSocket API. Oznacza to, że możesz je zastosować „bezpłatnie” bez dodatkowych kosztów.

Sugerowane przypadki użycia interfejsu WebSocketStream API

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

  • Aplikacje WebSocket o dużej przepustowości, które muszą zachowywać interaktywność, w konkretnym filmie i przy udostępnianiu ekranu.
  • Podobnie jest w przypadku nagrywania filmów i innych aplikacji, które generują dużo danych w przeglądarce. który musi zostać przesłany na serwer. Dzięki ciśnieniu wstecznemu klient może przestać generować dane, zamiast zbierać 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 iterować projekt W toku
4. Wersja próbna origin Zakończono
5. Uruchom Nie rozpoczęto

Jak używać interfejsu WebSocketStream API

Przykład wprowadzający

Interfejs WebSocketStream API opiera się na obietnicach, dzięki czemu radzenie sobie z nim jest naturalne. we współczesnym świecie JavaScriptu. Zacznij od utworzenia nowego WebSocketStream i przekaż mu adres URL serwera WebSocket. Następnie zaczekaj, aż połączenie będzie opened, co daje ReadableStream. lub WritableStream

Wywołując funkcję ReadableStream.getReader() otrzymasz w końcu ReadableStreamDefaultReader, co możesz następnie read() danych od chwili zakończenia strumienia, czyli do momentu zwrócenia obiektu {value: undefined, done: true}.

W związku z tym, wywołując WritableStream.getWriter() otrzymasz w końcu WritableStreamDefaultWriter, co możesz następnie 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? Jak wspomnieliśmy powyżej, dostęp jest udostępniany „bezpłatnie”, bez konieczności wykonywania dodatkowych czynności. Jeśli process() potrzebuje więcej czasu, następna wiadomość zostanie wykorzystana dopiero wtedy, gdy potok będzie gotowy. Podobnie WritableStreamDefaultWriter.write() zostanie przeprowadzony tylko wtedy, gdy można to bezpiecznie zrobić.

Przykłady zaawansowane

Drugi argument w WebSocketStream to pakiet opcji umożliwiających przedłużenie dostępu w przyszłości. Obecnie 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 słowa kluczowe protocol oraz potencjalne extensions są częścią słownika dostępne w ramach obietnicy WebSocketStream.opened. Wszystkie informacje o transmisji na żywo pochodzą z tego obietnicy, ponieważ nie ma znaczenia w przypadku niepowodzenia połączenia.

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

Informacje o zamkniętym połączeniu WebSocketStream

Informacje, które były dostępne w WebSocket.onclose i WebSocket.onerror wydarzenia w interfejsie WebSocket API jest teraz dostępna w ramach obietnicy WebSocketStream.closed. Obietnica jest odrzucana w przypadku brudnego zamknięcia transakcji, w przeciwnym razie przechodzi do kodu i przyczyny wysłanych przez serwer.

Wszystkie możliwe kody stanu i ich znaczenie zostały opisane w lista kodów stanu CloseEvent.

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

Zamykanie połączenia WebSocketStream

Strumień WebSocketStream można zamknąć za pomocą AbortController Dlatego musisz przekazać 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(), ale jego głównym celem jest określenie kod i wyjaśnienie, które jest przesyłane 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. Aby zapewnić interoperacyjność z klasycznym interfejsem WebSocket API, nie można zastosować obciążenia wstecznego do odebranych wiadomości. Zastosowanie wstecznego obciążenia do wysyłanych wiadomości jest możliwe, ale obejmuje odpytywanie WebSocket.bufferedAmount która jest niewydajna i nieergonomiczna.

Wykrywanie cech

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

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

Prezentacja

W obsługiwanych przeglądarkach interfejs WebSocketStream API można zobaczyć w umieszczonym elemencie iframe, lub bezpośrednio w usłudze Glitch.

Prześlij opinię

Zespół Chrome chce poznać Twoją opinię na temat interfejsu 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 Ci metod lub właściwości, które pozwolą Ci zrealizować Twój pomysł? Masz pytanie lub komentarz na temat modelu zabezpieczeń? Zgłoś problem ze specyfikacją w odpowiednim repozytorium GitHub. lub opisz swój problem.

Zgłoś problem z implementacją

Czy wystąpił błąd z implementacją Chrome? Czy implementacja różni się od specyfikacji? Zgłoś błąd na new.crbug.com. Postaraj się podać jak najwięcej szczegółów, proste instrukcje dotyczące odtwarzania, i w polu Komponenty wpisz Blink>Network>WebSockets. Glitch doskonale sprawdza się w przypadku szybkiego i łatwego udostępniania zgłoszeń.

Pokaż wsparcie dla interfejsu API

Czy planujesz użycie interfejsu API WebSocketStream? 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ł wdrożony przez Adam Rice i Yutaka Hirano Baner powitalny: Daan Mooij, w: Odchylenie.