WebSocketStream:將串流與 WebSocket API 整合

運用背壓,防止應用程式因 WebSocket 訊息而消失,或為 WebSocket 伺服器大量傳播訊息。

背景

WebSocket API

WebSocket API 提供 WebSocket 通訊協定的 JavaScript 介面。 這樣就能開啟雙向互動工作階段 使用者的瀏覽器和伺服器之間 你可以透過這個 API 傳送訊息至伺服器,並接收事件導向的回應 而不必輪詢伺服器來回應

Streams API

Streams API 可讓 JavaScript 以程式輔助的方式存取透過網路接收的資料串流串流 並依需求處理 串流的另一個重要概念 。 也就是單一串流或管道鏈 可調節讀取或寫入速度 串流本身或管線鏈中的串流作業仍為忙碌時 目前還不想接受更多分段 該指令碼會將信號反向傳送,並適時減緩傳送速度。

目前使用的 WebSocket API 問題

無法對收到的訊息套用背壓

目前使用的 WebSocket API 會 WebSocket.onmessage、 收到伺服器傳來的訊息時呼叫的 EventHandler

假設您已有一個應用程式需要執行繁重的資料處理作業 自動傳送新訊。 您可能會採用類似下方的程式碼設定流程。 既然你已經await process() 呼叫的結果,對吧?

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

答錯了!目前的 WebSocket API 問題是,無法套用背壓。 訊息送達時,如果訊息超出 process() 方法的處理速度, 轉譯程序會緩衝這些訊息來填滿記憶體 由於 CPU 使用率達到 100% (或兩者皆有),因此沒有回應。

為已傳送的郵件套用背壓並非符合人體工學

可以為送出的訊息套用背壓,不過會涉及輪詢 WebSocket.bufferedAmount敬上 因此效率較低且非人體工學 這項唯讀屬性會傳回已排入佇列的資料位元組數 呼叫 WebSocket.send()、 但尚未傳輸至網路 傳送所有排入佇列的資料之後,這個值會重設為 0。 但如果你繼續呼叫 WebSocket.send(), 它會繼續攀爬

什麼是 WebSocketStream API?

WebSocketStream API 可處理不存在或非人體工學背壓的問題 整合串流與 WebSocket API 這表示背壓可以「免費」使用,不必支付任何額外費用。

WebSocketStream API 建議用途

以下列舉可使用這個 API 的網站:

  • 需要維持互動性的高頻寬 WebSocket 應用程式, 特別是影片和螢幕分享
  • 同樣地,影片擷取以及其他會在瀏覽器中產生大量資料的應用程式 需要上傳到伺服器 透過背壓,用戶端可以停止產生資料,而不是在記憶體中累積資料。

目前狀態

步驟 狀態
1. 建立說明 完成
2. 建立規格的初始草稿 進行中
3. 收集意見回饋與在設計上反覆測試 進行中
4. 來源試用 完成
5. 啟動 尚未開始

如何使用 WebSocketStream API

入門範例

WebSocketStream API 依承諾而定,因此可以放心處理 現代的 JavaScript 世界 首先,請建構新的 WebSocketStream,並向其傳送 WebSocket 伺服器的網址。 接著,請等待連線狀態為 opened, 結果 ReadableStream 和/或 WritableStream

透過呼叫 ReadableStream.getReader()敬上 方法後,您最後就會取得 ReadableStreamDefaultReader, 然後read() 資料,也就是在串流完成之前, {value: undefined, done: true}

因此,只要呼叫 WritableStream.getWriter()敬上 方法後,您最後就會取得 WritableStreamDefaultWriter, 然後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);
  }

背壓

那我們承諾的背壓功能怎麼樣? 如同我所說的,您可以使用「免費」使用,不必進行額外步驟。 如果 process() 花費額外的時間,則下一則訊息只會在管道準備就緒後使用。 同樣地,WritableStreamDefaultWriter.write() 步驟 只會在安全的情況下繼續進行。

進階範例

WebSocketStream 的第二個引數是選項包,允許日後擴充。 目前唯一的選項是 protocols。 這個詞的運作方式與 WebSocket 建構函式的第二個引數

const chatWSS = new WebSocketStream(CHAT_URL, {protocols: ['chat', 'chatv2']});
const {protocol} = await chatWSS.opened;

所選 protocol 和可能的 extensions 是字典的一部分 可透過 WebSocketStream.opened 承諾使用。 所有與即時連線相關的資訊均由上述承諾提供。 因為如果連線失敗也無所謂

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

關閉的 WebSocketStream 連線相關資訊

WebSocket.oncloseWebSocket.onerror 事件 透過 WebSocketStream.closed 承諾提供。 承諾會在不乾淨的收尾情況下拒收 否則該指令就會解析為伺服器發送的代碼與原因。

如需瞭解所有可能的狀態碼及其意義,請參閱 CloseEvent 狀態碼清單

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

關閉 WebSocketStream 連線

WebSocketStream 支援 AbortController。 因此,請傳遞 AbortSignalWebSocketStream 建構函式中。

const controller = new AbortController();
const wss = new WebSocketStream(URL, {signal: controller.signal});
setTimeout(() => controller.abort(), 1000);

或者,您也可以使用 WebSocketStream.close() 方法 ( 但主要用途是允許將 程式碼 以及傳送到伺服器的原因

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

漸進增強與互通性

Chrome 是目前唯一實作 WebSocketStream API 的瀏覽器。 為了與傳統 WebSocket API 互通, 無法正壓制接收郵件。 可以為送出的訊息套用背壓,不過會涉及輪詢 WebSocket.bufferedAmount敬上 因此效率較低且非人體工學

特徵偵測

如要檢查 WebSocketStream API 是否支援,請使用:

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

示範

支援瀏覽器時,您可以在嵌入式 iframe 中實際看到 WebSocketStream API 或直接使用 Glitch

意見回饋

Chrome 團隊想與我們分享您使用 WebSocketStream API 的體驗。

請與我們分享 API 設計

您覺得這個 API 有什麼不如預期的運作方式? 或是你還需要實現創意的方法或屬性嗎? 對安全性模型有任何疑問或意見嗎? 在對應的 GitHub 存放區上提出規格問題, 或為現有問題補充意見

回報導入問題

您發現 Chrome 實作錯誤嗎? 還是採用與規格不同? 前往 new.crbug.com 回報錯誤。 請盡可能提供詳盡的重現資訊。 並在「元件」方塊中輸入 Blink>Network>WebSocketsGlitch 時間為分享簡單快速的重現案件時,可以派上用場。

顯示對 API 的支援

您是否打算使用 WebSocketStream API? 您的公開支援可協助 Chrome 團隊優先開發功能 ,讓其他瀏覽器廠商瞭解支援這些廠商有多重要。

使用主題標記將推文傳送至 @ChromiumDev #WebSocketStream敬上 ,並說明你使用這項服務的位置和方式。

實用連結

特別銘謝

WebSocketStream API 是由 Adam Rice 實作, Yutaka Hirano。 主頁橫幅由 Daan Mooij 提供 Unsplash