WebSocketStream: integrowanie strumieni z interfejsem WebSocket API

Zapobieganie zasypywaniu aplikacji wiadomościami WebSocket lub zalewaniu serwera WebSocket wiadomościami przez zastosowanie odwrotnego ciśnienia.

Interfejs WebSocket API udostępnia interfejs JavaScriptu dla 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 na serwer i otrzymywać odpowiedzi wywoływane zdarzeniami bez sprawdzania serwera w celu uzyskania odpowiedzi.

Interfejs Streams API

Interfejs Streams API umożliwia programowi JavaScript dostęp do strumieni danych otrzymanych przez sieć i ich przetwarzanie w wybrany sposób. Ważnym pojęciem w kontekście strumieni jest ciśnienie zwrotne. Jest to proces, w którym pojedynczy strumień lub łańcuch potoku reguluje szybkość odczytu lub zapisu. Gdy strumień lub strumień w dalszej części łańcucha jest nadal zajęty i nie jest jeszcze gotowy do przyjmowania 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 stosować ciśnienia wstecznego do otrzymanych wiadomości.

W przypadku obecnego interfejsu WebSocket API reakcja na wiadomość występuje w WebSocket.onmessage,EventHandler wywoływanym po otrzymaniu wiadomości od serwera.

Załóżmy, że masz aplikację, która musi wykonywać intensywne operacje przetwarzania danych za każdym razem, gdy otrzyma nową wiadomość. Prawdopodobnie skonfigurujesz przepływ danych podobny do kodu poniżej, a ponieważ await jest wynikiem wywołania process(), wszystko powinno być w porządku, 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 ma możliwości zastosowania odwrotnego ciśnienia. Gdy wiadomości docierają szybciej, niż metoda process() może je przetworzyć, proces renderowania albo zajmie pamięć przez buforowanie tych wiadomości, albo przestanie odpowiadać z powodu 100% wykorzystania procesora, albo jedno i drugie.

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

Zastosowanie sprzężenia zwrotnego w przypadku 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. Ta wartość wraca do 0 po wysłaniu wszystkich danych z kolejki, ale jeśli nadal wywołujesz funkcję WebSocket.send(), będzie ona dalej rosnąć.

Czym 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 nacisk może być stosowany „bezpłatnie”, bez dodatkowych kosztów.

Sugerowane zastosowania 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ą być przesyłane na serwer. Dzięki temu klient może przestać generować dane zamiast gromadzić je w pamięci.

Obecny stan,

Krok Stan
1. Tworzenie wyjaśnienia Zakończono
2. Tworzenie wstępnej wersji specyfikacji W toku
3. Zbieraj opinie i ulepszaj projekt W toku
4. Wersja próbna origin Zakończono
5. Uruchom Nie rozpoczęto

Jak korzystać z interfejsu WebSocketStream API

Interfejs WebSocketStream API jest oparty na obietnicach, co sprawia, że korzystanie z niego jest naturalne w świecie nowoczesnego JavaScriptu. 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.

Po wywołaniu metody ReadableStream.getReader() otrzymasz obiekt ReadableStreamDefaultReader, z którego możesz pobierać dane do momentu zakończenia strumienia, czyli do czasu, aż zwróci obiekt o formie {value: undefined, done: true}.read()

W związku z tym wywołanie metody WritableStream.getWriter() daje w efekcie zmienną WritableStreamDefaultWriter, którą można następnie write()przekazać.

  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

Co z obiecaną funkcją backpressure? Otrzymasz je „bezpłatnie”, bez konieczności wykonywania dodatkowych czynności. Jeśli process() zajmuje więcej czasu, następny komunikat jest wykorzystywany dopiero po przygotowaniu potoku. Podobnie krok WritableStreamDefaultWriter.write() jest wykonywany tylko wtedy, gdy jest to bezpieczne.

Zaawansowane przykłady

Drugi argument funkcji WebSocketStream to pakiet opcji umożliwiający rozszerzenie w przyszłości. Jedyną opcją jest protocols, która 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 jest odrzucana w przypadku nieprawidłowego zamknięcia. W przeciwnym razie jest przekształcana w kod i uzasadnienie wysł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. Dlatego do konstruktora WebSocketStream prześlij parametr AbortSignal.

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

Progresywne ulepszanie i interoperacyjność

Chrome jest obecnie jedyną przeglądarką, która implementuje 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.

Prześlij informacje o projektowaniu interfejsu API

Czy coś w interfejsie API nie działa zgodnie z oczekiwaniami? A może brakuje metod lub właściwości, których potrzebujesz do wdrożenia swojego pomysłu? Masz pytania lub uwagi dotyczące 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? A może implementacja różni się od specyfikacji? Zgłoś błąd na stronie new.crbug.com. Podaj jak najwięcej szczegółów i proste instrukcje odtwarzania błędu. W polu Składniki wpisz Blink>Network>WebSockets. Glitch świetnie sprawdza się do szybkiego i łatwego udostępniania przypadków odtwarzania.

Pokaż pomoc dotyczącą 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 do @ChromiumDev, używając hashtaga #WebSocketStream, i podaj, gdzie i jak go używasz.

Przydatne linki

Podziękowania

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