使用 WebTransport

WebTransport 是提供低延遲、雙向用戶端-伺服器訊息傳遞的 API。進一步瞭解這項功能的用途,以及如何針對未來的導入方式提供意見回饋。

背景

什麼是 WebTransport?

WebTransport 是一種網路 API,會使用 HTTP/3 通訊協定做為雙向傳輸。這項通訊協定適用於網路用戶端和 HTTP/3 伺服器之間的雙向通訊。它支援透過資料包 API串流 API 傳送資料,前者不穩定,後者則可靠。

Datagram 非常適合用於傳送和接收不需要強大傳送保證的資料。個別資料封包的大小受限於基礎連線的最大傳輸單位 (MTU),且可能無法順利傳送,且如果傳送成功,可能會以任意順序抵達。這些特性讓資料包 API 成為低延遲、盡力傳輸資料的理想選擇。您可以將資料元視為 用戶資料元協定 (UDP) 訊息,但經過加密和擁塞控制。

相較之下,串流 API 則提供可靠的排序資料傳輸功能。在需要傳送或接收一或多個有序資料串流的情況下,非常適合使用這類串流。使用多個 WebTransport 串流,就好比建立多個 TCP 連線,但由於 HTTP/3 會在幕後使用較輕量化的 QUIC 通訊協定,因此可以開啟和關閉這些連線,而不會造成太多額外負擔。

用途

以下列出開發人員可能會使用 WebTransport 的幾種方式。

  • 透過小型、不可靠且不按順序傳送的訊息,以定期間隔傳送遊戲狀態,並將延遲時間降到最低。
  • 接收伺服器推送的媒體串流,延遲時間最短,不受其他資料串流影響。
  • 在網頁開啟時,接收來自伺服器的推播通知。

我們想進一步瞭解您打算如何使用 WebTransport。

瀏覽器支援

Browser Support

  • Chrome: 97.
  • Edge: 97.
  • Firefox: 114.
  • Safari: not supported.

Source

對於所有不受通用瀏覽器支援的功能,透過功能偵測進行防禦式編碼是最佳做法。

目前狀態

步驟 狀態
1. 建立說明 完成
2. 建立規格初稿 完成
3. 收集意見回饋並重複設計 完成
4. 來源試用 完成
5. 發布 Chromium 97

WebTransport 與其他技術的關係

WebTransport 是否可取代 WebSocket?

不一定。在某些用途中,WebSockets 或 WebTransport 可能都是可用的通訊協定。

WebSocket 通訊是以單一可靠的訊息串流為基礎,並按照順序排列,這類通訊方式可滿足某些通訊需求。如果您需要這些特性,WebTransport 的串流 API 也可以提供。相較之下,WebTransport 的資料包 API 可提供低延遲的傳送服務,但無法保證可靠性或順序,因此無法直接取代 WebSocket。

透過資料報頭 API 或多個並行 Streams API 執行個體使用 WebTransport,表示您不必擔心 頭阻斷問題,這可能是 WebSocket 的問題。此外,建立新連線時,由於基礎的 QUIC 握手比透過 TLS 啟動 TCP 更快,因此可提升效能。

WebTransport 是新規格草稿的一部分,因此目前的用戶端和伺服器程式庫 WebSocket 生態系統更加健全。如果您需要「開箱即用」的解決方案,且可搭配常見的伺服器設定使用,並支援廣泛的網路用戶端,WebSockets 是目前較佳的選擇。

WebTransport 與 UDP Socket API 是否相同?

否。WebTransport 並非 UDP Socket API。雖然 WebTransport 使用 HTTP/3,而 HTTP/3 又會「在幕後」使用 UDP,但 WebTransport 有加密和壅塞控制方面的需求,因此不只是基本的 UDP 網路介面 API。

WebTransport 是否是 WebRTC 資料管道的替代方案?

是的,用戶端與伺服器連線。WebTransport 與 WebRTC 資料管道共用許多相同的屬性,但基礎通訊協定不同。

一般來說,執行與 HTTP/3 相容的伺服器所需的設定和配置,比維護 WebRTC 伺服器所需的設定和配置少,因為後者需要瞭解多個通訊協定 (ICEDTLSSCTP),才能取得可運作的傳輸機制。WebRTC 包含許多可導致用戶端/伺服器協商失敗的動態元素。

WebTransport API 的設計著重於網頁程式開發人員的使用情境,因此使用者應該會覺得這更像是編寫新式網頁平台程式碼,而不是使用 WebRTC 的資料通道介面。與 WebRTC 不同,WebTransport 支援 Web Workers,可讓您在與特定 HTML 頁面無關的情況下,執行用戶端與伺服器之間的通訊。由於 WebTransport 會公開符合 Streams 的介面,因此可支援回壓方面的最佳化。

不過,如果您已經有運作良好的 WebRTC 用戶端/伺服器設定,轉換至 WebTransport 可能不會帶來太多優勢。

立即試用

如要測試 WebTransport,最好的方法是啟動相容的 HTTP/3 伺服器。接著,您可以使用這個頁面搭配基本 JavaScript 用戶端,試用用戶端/伺服器通訊。

此外,您也可以前往 webtransport.day 使用社群維護的回音伺服器。

使用 API

WebTransport 是根據新型網站平台基本元素 (例如 Streams API) 設計而成。它極度仰賴承諾,且可與 asyncawait 搭配使用。

Chromium 目前的 WebTransport 實作功能支援三種不同類型的流量:資料包,以及單向和雙向串流。

連線至伺服器

您可以建立 WebTransport 執行個體,連線至 HTTP/3 伺服器。網址的通訊協定應為 https。您必須明確指定通訊埠號碼。

您應使用 ready Promise 等待連線建立。設定完成前,這個應許不會履行,如果在 QUIC/TLS 階段連線失敗,則會遭到拒絕。

當連線正常關閉時,closed 應許會完成,如果關閉方式非預期,則會遭到拒絕。

如果伺服器因用戶端指示錯誤 (例如網址路徑無效) 而拒絕連線,就會導致 closed 拒絕,而 ready 仍未解決。

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;

Datagram API

有了連線至伺服器的 WebTransport 例項後,您就可以使用該例項傳送及接收個別位元組的資料,稱為 資料包

writeable getter 會傳回 WritableStream,網路用戶端可使用該值將資料傳送至伺服器。readable getter 會傳回 ReadableStream,讓您監聽伺服器的資料。這兩個串流本質上都不可靠,因此伺服器可能不會收到您寫入的資料,反之亦然。

這兩種串流都會使用 Uint8Array 例項進行資料傳輸。

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

Streams API

連線至伺服器後,您也可以使用 WebTransport 透過其 Streams API 傳送及接收資料。

所有串流的每個區塊都是 Uint8Array。與 Datagram API 不同,這些串流可靠。不過,每個串流都是獨立的,因此無法保證串流間的資料順序。

WebTransportSendStream

網路用戶端會使用 WebTransport 例項的 createUnidirectionalStream() 方法建立 WebTransportSendStream,並傳回 WebTransportSendStream 的 promise。

請使用 WritableStreamDefaultWriterclose() 方法關閉相關聯的 HTTP/3 串流。瀏覽器會在實際關閉相關串流之前,嘗試傳送所有待處理資料。

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

同樣地,請使用 WritableStreamDefaultWriterabort() 方法,將 RESET_STREAM 傳送至伺服器。使用 abort() 時,瀏覽器可能會捨棄所有尚未傳送的待處理資料。

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

伺服器會啟動 WebTransportReceiveStream。取得 WebTransportReceiveStream 的程序分為兩個步驟,首先,它會呼叫 WebTransport 例項的 incomingUnidirectionalStreams 屬性,該屬性會傳回 ReadableStreamReadableStream 的每個區塊都是 WebTransportReceiveStream,可用於讀取伺服器傳送的 Uint8Array 例項。

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

您可以使用 ReadableStreamDefaultReaderclosed 應許,偵測串流關閉。當底層 HTTP/3 串流使用 FIN 位元關閉時,系統會在讀取所有資料後履行 closed 承諾。如果 HTTP/3 串流突然關閉 (例如由 RESET_STREAM 關閉),closed 應許就會遭到拒絕。

// 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 可能由伺服器或用戶端建立。

網路用戶端可以使用 WebTransport 例項的 createBidirectionalStream() 方法建立一個,該方法會傳回 WebTransportBidirectionalStream 的承諾。

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

您可以監聽伺服器使用 WebTransport 例項的 incomingBidirectionalStreams 屬性建立的 WebTransportBidirectionalStream,該屬性會傳回 ReadableStreamReadableStream 的每個部分都是 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 只是 WebTransportSendStreamWebTransportReceiveStream 的組合。前兩節的範例說明瞭如何使用每個方法。

其他示例

WebTransport 草稿規格包含許多額外的內嵌範例,以及所有方法和屬性的完整文件。

Chrome 開發人員工具中的 WebTransport

很抱歉,Chrome 的開發人員工具目前不支援 WebTransport。你可以「加星」這個 Chrome 問題,以便在開發人員工具介面上收到更新通知。

聚酯纖維

您可以使用名為 webtransport-ponyfill-websocket 的 polyfill (或稱為 ponyfill,可提供可用作獨立模組的功能),實作 WebTransport 的部分功能。請仔細閱讀專案 README 中的限制條件,判斷這個解決方案是否適用於您的用途。

隱私權和安全性考量

如需權威指南,請參閱規格草稿的相應章節

意見回饋

Chrome 團隊希望聽聽你對這個 API 的想法和使用體驗。

針對 API 設計提供意見回饋

API 是否有任何不便之處,或無法正常運作?或者,您是否缺少實現想法的必要元素?

Web Transport GitHub 存放區中回報問題,或在現有問題中提供意見。

導入時發生問題?

你是否發現 Chrome 實作項目有錯誤?

請前往 https://new.crbug.com 提交錯誤。請盡可能提供詳細資訊,並附上重現問題的簡單操作說明。

打算使用 API 嗎?

你的公開支持有助於 Chrome 決定功能的優先順序,並向其他瀏覽器供應商顯示支援這些功能的重要性。

  • 使用主題標記 #WebTransport,並詳細說明你使用該功能的位置和方式,然後發送推文給 @ChromiumDev

一般討論

如果您有一般問題或問題不屬於其他類別,可以使用 web-transport-dev Google 群組

特別銘謝

本文參考了 WebTransport 說明文件規格草稿相關設計文件中的資訊。感謝各位作者提供這項基礎資訊。

這篇文章的主頁橫幅圖片出自 Unsplash 上的 Robin Pierre