Jak korzystać z WebTransport

Opublikowano: 8 czerwca 2020 r.

WebTransport to interfejs API sieci, który wykorzystuje protokół HTTP/3 jako dwukierunkowy transport. Jest on przeznaczony do dwukierunkowej komunikacji między klientem internetowym a serwerem HTTP/3. Umożliwia on przesyłanie danych w sposób zawodny za pomocą interfejsów API datagramów oraz w sposób niezawodny za pomocą interfejsów API strumieni.

Datagramy idealnie nadają się do wysyłania i odbierania danych, które nie wymagają silnych gwarancji dostawy. Poszczególne pakiety danych mają ograniczony rozmiar, który zależy od maksymalnej jednostki przesyłania (MTU) połączenia bazowego. Mogą one zostać przesłane lub nie, a jeśli zostaną przesłane, mogą dotrzeć w dowolnej kolejności. Dzięki tym cechom interfejsy API datagramów idealnie nadają się do transmisji danych z małym opóźnieniem i najwyższą starannością. Datagramy można traktować jako wiadomości protokołu pakietów użytkownika (UDP), ale zaszyfrowane i z kontrolą przeciążenia.

Interfejsy API strumieni zapewniają natomiast niezawodne przesyłanie danych w określonej kolejności. Dobrze sprawdzają się w sytuacjach, w których musisz wysyłać lub odbierać co najmniej 1 strumień uporządkowanych danych. Używanie wielu strumieni WebTransport jest analogiczne do nawiązywania wielu połączeń TCP, ale ponieważ HTTP/3 używa lżejszego protokołu QUIC, można je otwierać i zamykać bez dużego narzutu.

Przypadki użycia

To krótka lista możliwych sposobów wykorzystania WebTransportu przez programistów.

  • Wysyłanie stanu gry w regularnych odstępach czasu z minimalnym opóźnieniem na serwer w małych, zawodnych i nieuporządkowanych wiadomościach.
  • Odbieranie strumieni multimediów przesyłanych z serwera z minimalnym opóźnieniem, niezależnie od innych strumieni danych.
  • otrzymywanie powiadomień wysyłanych z serwera, gdy strona jest otwarta;

Chętnie dowiemy się więcej o tym, jak planujesz korzystać z WebTransportu.

Obsługa przeglądarek

Browser Support

  • Chrome: 97.
  • Edge: 97.
  • Firefox: 114.
  • Safari: 26.4.

Source

Podobnie jak w przypadku wszystkich funkcji, które nie są obsługiwane przez wszystkie przeglądarki, zalecamy dodanie wykrywania funkcji.

Związek z innymi technologiami

Czy WebTransport zastępuje WebSockets?

Być może. W niektórych przypadkach użycia protokoły WebSockets i WebTransport mogą być odpowiednimi protokołami komunikacji.

Komunikacja za pomocą protokołu WebSocket jest oparta na pojedynczym, niezawodnym i uporządkowanym strumieniu wiadomości, co jest odpowiednie w przypadku niektórych rodzajów komunikacji. Jeśli potrzebujesz tych cech, interfejsy API strumieni WebTransport mogą je również zapewnić. Dla porównania interfejsy API datagramów WebTransport zapewniają dostarczanie z niskim opóźnieniem, ale bez gwarancji niezawodności i kolejności, więc nie są bezpośrednim zamiennikiem WebSocketów.

Gdy używasz WebTransport z interfejsami datagramów lub wieloma równoczesnymi instancjami interfejsu Streams API, nie musisz się martwić blokowaniem na początku kolejki, które może być problemem w przypadku WebSocketów. Nawiązywanie nowych połączeń jest też bardziej wydajne, ponieważ podstawowe uzgadnianie połączenia QUIC jest szybsze niż uruchamianie protokołu TCP przez protokół TLS.

WebTransport jest częścią nowego projektu specyfikacji, dlatego ekosystem WebSocket obejmujący biblioteki klienta i serwera jest znacznie bardziej rozbudowany. Jeśli potrzebujesz rozwiązania, które działa „od razu” w przypadku typowych konfiguracji serwera i jest obsługiwane przez większość klientów internetowych, lepszym wyborem będą obecnie gniazda WebSocket.

Czy WebTransport to to samo co interfejs API gniazda UDP?

Nie. WebTransport nie jest interfejsem UDP Socket API. WebTransport używa protokołu HTTP/3, który z kolei „pod maską” korzysta z UDP. WebTransport ma jednak wymagania dotyczące szyfrowania i kontroli przeciążenia, które sprawiają, że jest czymś więcej niż podstawowym interfejsem API gniazd UDP.

Czy WebTransport jest alternatywą dla kanałów danych WebRTC?

Tak, w przypadku połączeń klient-serwer. WebTransport ma wiele takich samych właściwości jak kanały danych WebRTC, chociaż protokoły bazowe są różne.

Ogólnie rzecz biorąc, uruchomienie serwera zgodnego z HTTP/3 wymaga mniej konfiguracji niż utrzymywanie serwera WebRTC, co wiąże się ze zrozumieniem wielu protokołów (ICE, DTLSSCTP), aby uzyskać działający transport. WebRTC obejmuje znacznie więcej elementów, które mogą prowadzić do nieudanych negocjacji między klientem a serwerem.

Interfejs WebTransport API został zaprojektowany z myślą o przypadkach użycia przez deweloperów internetowych i powinien przypominać pisanie nowoczesnego kodu platformy internetowej, a nie korzystanie z interfejsów kanału danych WebRTC. W przeciwieństwie do WebRTC WebTransport jest obsługiwany w Web Workers, co umożliwia komunikację między klientem a serwerem niezależnie od danej strony HTML. WebTransport udostępnia interfejs zgodny ze standardem Streams, dlatego obsługuje optymalizacje związane z ograniczaniem przepustowości.

Jeśli jednak masz już działającą konfigurację klienta/serwera WebRTC, z której jesteś zadowolony, przejście na WebTransport może nie przynieść wielu korzyści.

Eksperyment

Najlepszym sposobem na eksperymentowanie z WebTransport jest uruchomienie zgodnego serwera HTTP/3. Użyj tej strony z podstawowym klientem JavaScript, aby wypróbować komunikację między klientem a serwerem.

Dodatkowo dostępny jest serwer echo utrzymywany przez społeczność pod adresem webtransport.day.

Korzystanie z interfejsu API

WebTransport został zaprojektowany na podstawie nowoczesnych elementów platformy internetowej, takich jak Streams API. W dużej mierze opiera się na obietnicach i dobrze współpracuje z asyncawait.

Obecna implementacja WebTransport w Chromium obsługuje 3 rodzaje ruchu: datagramy oraz strumienie jednokierunkowe i dwukierunkowe.

Nawiązywanie połączenia z serwerem

Aby połączyć się z serwerem HTTP/3, utwórz instancję WebTransport. Schemat adresu URL powinien mieć postać https. Musisz wyraźnie określić numer portu.

Aby poczekać na nawiązanie połączenia, użyj obietnicy ready. Obietnica pozostaje niespełniona do czasu zakończenia konfiguracji i jest odrzucana, jeśli połączenie nie powiedzie się na etapie QUIC/TLS.

Obietnica closed zostanie spełniona, gdy połączenie zostanie zamknięte w normalny sposób, a odrzucona, jeśli zamknięcie było nieoczekiwane.

Jeśli serwer odrzuci połączenie z powodu błędu wskazania klienta (np. ścieżka adresu URL jest nieprawidłowa), spowoduje to odrzucenie przez closed, a ready pozostanie nierozwiązany.

const url = 'https://example.com:4999/foo/bar';
const transport = new WebTransport(url);

// Optionally, set up functions to respond to
// the connection closing:
transport.closed.then(() => {
  console.log(`The HTTP/3 connection to ${url} closed gracefully.`);
}).catch((error) => {
  console.error(`The HTTP/3 connection to ${url} closed due to ${error}.`);
});

// Once .ready fulfills, the connection can be used.
await transport.ready;

Interfejsy API datagramów

Gdy masz instancję WebTransport połączoną z serwerem, możesz jej używać do wysyłania i odbierania dyskretnych fragmentów danych, czyli datagramów.

Getter writeable zwraca obiekt WritableStream, którego klient internetowy może użyć do wysyłania danych na serwer. Funkcja pobierająca readable zwraca obiekt ReadableStream, który umożliwia nasłuchiwanie danych z serwera. Oba strumienie są z natury zawodne, więc istnieje możliwość, że zapisane dane nie zostaną odebrane przez serwer i odwrotnie.

Oba typy strumieni używają instancji Uint8Array do przesyłania danych.

// Send two datagrams to the server.
const writer = transport.datagrams.writable.getWriter();
const data1 = new Uint8Array([65, 66, 67]);
const data2 = new Uint8Array([68, 69, 70]);
writer.write(data1);
writer.write(data2);

// Read datagrams from the server.
const reader = transport.datagrams.readable.getReader();
while (true) {
  const {value, done} = await reader.read();
  if (done) {
    break;
  }
  // value is a Uint8Array.
  console.log(value);
}

Interfejsy Streams API

Po połączeniu z serwerem możesz też używać WebTransport do wysyłania i odbierania danych za pomocą interfejsów API Streams.

Każdy fragment wszystkich strumieni to Uint8Array. W przeciwieństwie do interfejsów Datagram API te strumienie są niezawodne. Każdy strumień jest jednak niezależny, więc kolejność danych w strumieniach nie jest gwarantowana.

WebTransportSendStream

Obiekt WebTransportSendStream jest tworzony przez klienta internetowego za pomocą metody createUnidirectionalStream() instancji WebTransport, która zwraca obietnicę dotyczącą obiektu WebTransportSendStream.

Użyj metody close() interfejsu WritableStreamDefaultWriter, aby zamknąć powiązany strumień HTTP/3. Przeglądarka próbuje wysłać wszystkie oczekujące dane, zanim zamknie powiązany strumień.

// Send two Uint8Arrays to the server.
const stream = await transport.createUnidirectionalStream();
const writer = stream.writable.getWriter();
const data1 = new Uint8Array([65, 66, 67]);
const data2 = new Uint8Array([68, 69, 70]);
writer.write(data1);
writer.write(data2);
try {
  await writer.close();
  console.log('All data has been sent.');
} catch (error) {
  console.error(`An error occurred: ${error}`);
}

Podobnie użyj metody abort() interfejsu WritableStreamDefaultWriter, aby wysłać do serwera RESET_STREAM. Podczas korzystania z abort() przeglądarka może odrzucić wszystkie oczekujące dane, które nie zostały jeszcze wysłane.

const ws = await transport.createUnidirectionalStream();
const writer = ws.getWriter();
writer.write(...);
writer.write(...);
await writer.abort();
// Not all the data may have been written.

WebTransportReceiveStream

Serwer inicjuje WebTransportReceiveStream. Uzyskanie WebTransportReceiveStream przez klienta internetowego to proces dwuetapowy. Najpierw klient wywołuje atrybut incomingUnidirectionalStreams instancji WebTransport, który zwraca wartość ReadableStream. Każdy fragment tego ReadableStream jest z kolei WebTransportReceiveStream, którego można użyć do odczytywania instancji Uint8Array wysyłanych przez serwer.

async function readFrom(receiveStream) {
  const reader = receiveStream.readable.getReader();
  while (true) {
    const {done, value} = await reader.read();
    if (done) {
      break;
    }
    // value is a Uint8Array
    console.log(value);
  }
}

const rs = transport.incomingUnidirectionalStreams;
const reader = rs.getReader();
while (true) {
  const {done, value} = await reader.read();
  if (done) {
    break;
  }
  // value is an instance of WebTransportReceiveStream
  await readFrom(value);
}

Zamknięcie strumienia możesz wykryć za pomocą obietnicy closed interfejsu ReadableStreamDefaultReader. Gdy podstawowy strumień HTTP/3 zostanie zamknięty za pomocą bitu FIN, obietnica closed zostanie spełniona po odczytaniu wszystkich danych. Gdy strumień HTTP/3 zostanie nagle zamknięty (np. przez RESET_STREAM), obietnica closed zostanie odrzucona.

// Assume an active receiveStream
const reader = receiveStream.readable.getReader();
reader.closed.then(() => {
  console.log('The receiveStream closed gracefully.');
}).catch(() => {
  console.error('The receiveStream closed abruptly.');
});

WebTransportBidirectionalStream

WebTransportBidirectionalStream może zostać utworzony przez serwer lub klienta.

Klienci internetowi mogą utworzyć taki obiekt za pomocą metody createBidirectionalStream() instancji WebTransport, która zwraca obietnicę dotyczącą obiektu WebTransportBidirectionalStream.

const stream = await transport.createBidirectionalStream();
// stream is a WebTransportBidirectionalStream
// stream.readable is a ReadableStream
// stream.writable is a WritableStream

Możesz nasłuchiwać zdarzenia WebTransportBidirectionalStream utworzonego przez serwer za pomocą atrybutu incomingBidirectionalStreams instancji WebTransport, który zwraca wartość ReadableStream. Każda część tego elementu ReadableStream jest z kolei elementem WebTransportBidirectionalStream.

const rs = transport.incomingBidirectionalStreams;
const reader = rs.getReader();
while (true) {
  const {done, value} = await reader.read();
  if (done) {
    break;
  }
  // value is a WebTransportBidirectionalStream
  // value.readable is a ReadableStream
  // value.writable is a WritableStream
}

WebTransportBidirectionalStream to po prostu połączenie WebTransportSendStreamWebTransportReceiveStream. Przykłady z dwóch poprzednich sekcji pokazują, jak używać każdej z nich.

Kod polyfill

Dostępny jest polyfill (a raczej ponyfill, który udostępnia funkcje jako samodzielny moduł, z którego możesz korzystać) o nazwie webtransport-ponyfill-websocket implementujący niektóre funkcje WebTransport. Uważnie przeczytaj ograniczenia w README projektu, aby sprawdzić, czy to rozwiązanie sprawdzi się w Twoim przypadku.

Kwestie związane z prywatnością i bezpieczeństwem

Więcej informacji znajdziesz w odpowiedniej sekcji projektu specyfikacji.

Prześlij opinię

Czy w interfejsie API jest coś, co jest niewygodne lub nie działa zgodnie z oczekiwaniami? Czy brakuje Ci elementów, które są niezbędne do realizacji Twojego pomysłu?

Twoje publiczne wsparcie pomaga zespołowi Chrome określać priorytety funkcji i pokazuje innym dostawcom przeglądarek, jak ważne jest ich obsługiwanie.

  • Napisz tweeta do @ChromiumDev, używając hashtagu #WebTransport i podając szczegóły dotyczące tego, gdzie i jak korzystasz z tej funkcji.

Dyskusja ogólna

W przypadku ogólnych pytań lub problemów, które nie pasują do żadnej z pozostałych kategorii, możesz skorzystać z grupy dyskusyjnej web-transport-dev Google.

Podziękowania

Uwzględniliśmy informacje z wyjaśnienia WebTransportprojektu specyfikacji. Dziękujemy autorom za stworzenie podstaw.