WebSocketStream: integracja strumieni z interfejsem API WebSocket

Stosuj wsteczne działanie, aby Twoja aplikacja nie utknęła w wiadomościach WebSocket ani nie zalała serwera WebSocket wiadomościami.

Wprowadzenie

WebSocket API

Interfejs WebSocket API udostępnia interfejs JavaScript dla protokołu WebSocket, który umożliwia otwarcie dwukierunkowej interaktywnej sesji komunikacji między przeglądarką użytkownika a serwerem. Umożliwia on wysyłanie wiadomości na serwer i odbieranie odpowiedzi zależnych od zdarzeń bez konieczności zwracania się do serwera w celu uzyskania odpowiedzi.

Streams API

Interfejs Streams API umożliwia JavaScriptowi programowy dostęp do strumieni danych otrzymanych w sieci i przetwarzanie ich zgodnie z potrzebami. Ważną koncepcją w kontekście strumieni jest obciążenie wsteczne. Jest to proces, w którym pojedynczy strumień lub łańcuch potoku reguluje szybkość czytania lub zapisu. Gdy strumień lub strumień na dalszych etapach łańcucha jest nadal zajęty i nie jest jeszcze gotowy na przyjęcie kolejnych fragmentów, wysyła sygnał wstecz przez łańcuch, co pozwala odpowiednio spowolnić przesyłanie.

Problem z aktualnym interfejsem API WebSocket

Stosowanie nacisku wstecznego na odbierane wiadomości jest niemożliwe

Przy obecnym interfejsie WebSocket API reakcja na wiadomość odbywa się w WebSocket.onmessage, czyli w obiekcie EventHandler wywoływanym po odebraniu wiadomości z serwera.

Załóżmy, że masz aplikację, która po otrzymaniu nowej wiadomości musi wykonywać intensywne operacje ćwiczenia danych. Procedura została prawdopodobnie skonfigurowana w podobny sposób jak w tym kodzie, a skoro skoro await wynik 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);
};

Błąd! Problem z obecnym interfejsem WebSocket API polega na tym, że nie można zastosować wstecznego obciążenia. Gdy wiadomości przychodzą szybciej, niż pozwala na to metoda process(), proces renderowania może wypełnić pamięć przez buforowanie tych wiadomości, przestać odpowiadać z powodu 100% wykorzystania procesora lub skorzystać z obu tych rozwiązań naraz.

Stosowanie obciążenia wstecznego do wysyłanych wiadomości nie jest ergonomiczne

Stosowanie obciążenia wstecznego do wysyłanych wiadomości jest możliwe, ale wymaga odpytywania właściwości WebSocket.bufferedAmount, co jest nieefektywne 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ść jest resetowana do zera, gdy wszystkie dane w kolejce zostaną wysłane. Jeśli jednak będziesz nadal wywoływać metodę WebSocket.send(), będzie ona rosnąć.

Co to jest interfejs API WebSocketStream?

Interfejs WebSocketStream API eliminuje problem nieistniejącego lub nieergonomicznego obciążenia wstecznego dzięki integracji strumieni z interfejsem WebSocket API. Oznacza to, że obciążenie wsteczne może być stosowane „bezpłatnie” bez dodatkowych kosztów.

Sugerowane przypadki użycia interfejsu WebSocketStream API

Przykłady witryn, w których można używać tego interfejsu API:

  • Aplikacje WebSocket wysokiej przepustowości, które muszą zachowywać interaktywność, w szczególności udostępnianie filmów i ekranu.
  • Podobnie dzieje się w przypadku przechwytywania wideo i innych aplikacji, które generują w przeglądarce dużo danych, które muszą zostać przesłane na serwer. Dzięki temu klient może przestać wytwarzać dane zamiast gromadzić je w pamięci.

Obecny stan,

| Krok | Stan | | ------------------------------------------ | ---------------------------- | | 1. Napisz wyjaśnienie | [Complete][explainer] | | 2. Utwórz wstępną wersję roboczą specyfikacji | [W toku][specyfikacja] | | 3. Zbieraj opinie i ponownie ulepszaj projekt | [W toku](#feedback) | | 4. Testowanie origin | [Complete][ot] | | 5. Uruchomienie | Nie rozpoczęto |

Jak używać interfejsu WebSocketStream API

Przykład wprowadzający

Interfejs WebSocketStream API jest oparty na obietnicach, dlatego we współczesnym świecie JavaScriptu postępowanie z nim jest naturalne. Zacznij od utworzenia nowego obiektu WebSocketStream i przekazania do niego adresu URL serwera WebSocket. Następnie poczekaj na połączenie opened, co spowoduje utworzenie ReadableStream lub WritableStream.

Wywołując w końcu metodę ReadableStream.getReader(), uzyskujesz w końcu obiekt ReadableStreamDefaultReader, który możesz potem read() przesyłać do momentu zakończenia strumienia, czyli do momentu, gdy zwróci obiekt w postaci obiektu{value: undefined, done: true}.

W związku z tym, wywołując metodę WritableStream.getWriter(), uzyskujesz w końcu WritableStreamDefaultWriter, do którego można następnie write() uzyskać dane.

  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ą wsteczną? Tak jak wspomnieliśmy powyżej, korzystanie z niej jest „bezpłatne”, bez konieczności wykonywania dodatkowych czynności. Jeśli process() zajmie więcej czasu, następna wiadomość zostanie wykorzystana dopiero wtedy, gdy potok będzie gotowy. Podobnie krok WritableStreamDefaultWriter.write() zostanie wykonany tylko wtedy, gdy jest to bezpieczne.

Przykłady zaawansowane

Drugi argument funkcji WebSocketStream to torba z opcją umożliwiającą dodanie rozszerzenia w przyszłości. Obecnie 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, a także potencjalne extensions, są częścią słownika dostępnego za pośrednictwem obietnicy WebSocketStream.opened. Obietnica zapewnia wszystkie informacje o aktywnym połączeniu, ponieważ nie są one istotne, jeśli połączenie się nie powiedzie.

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

Informacje o zamkniętym połączeniu WebSocketStream

Informacje, które były dostępne na podstawie zdarzeń WebSocket.onclose i WebSocket.onerror w interfejsie WebSocket API, są teraz dostępne za pomocą obietnicy WebSocketStream.closed. Obietnica odrzuca w przypadku nieoczywistego zamknięcia. W przeciwnym razie odnosi się do kodu i przyczyny wysłanych przez serwer.

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

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

Zamykanie połączenia WebSocketStream

Komponent WebSocketStream można zamknąć za pomocą AbortController. Dlatego 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(), ale jej głównym celem jest umożliwienie określenia kodu i przyczyny wysyłanej do serwera.

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

Stopniowe ulepszanie i współdziałanie

Chrome jest obecnie jedyną przeglądarką, która obsługuje interfejs API WebSocketStream. W przypadku interoperacyjności z klasycznym interfejsem WebSocket API nie można stosować wstecznie do odbieranych wiadomości. Stosowanie obciążenia wstecznego do wysyłanych wiadomości jest możliwe, ale wymaga odpytywania właściwości WebSocket.bufferedAmount, co jest nieefektywne i nieergonomiczne.

Wykrywanie funkcji

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

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

Demonstracyjny

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

Prześlij opinię

Zespół Chrome chce poznać Twoją opinię na temat korzystania z interfejsu API WebSocketStream.

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ó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 uwagi o istniejącym problemie.

Zgłoś problem z implementacją

Czy występuje błąd w implementacji przeglądarki 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, proste instrukcje odtworzenia i w polu Komponenty wpisz Blink>Network>WebSockets. Funkcja Glitch doskonale nadaje się do szybkiego i łatwego udostępniania przypadków odtworzenia.

Pokaż obsługę interfejsu API

Czy planujesz użycie interfejsu API WebSocketStream? Twoja publiczna pomoc pomaga zespołowi Chrome w określaniu priorytetów funkcji i pokazuje innym dostawcom przeglądarek, jak ważne jest ich wsparcie.

Wyślij tweeta do @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 Adama Ricea i Yutakę Hirano. Baner powitalny od Daana Mooij w filmie Unsplash.