WebSocketStream: ストリームと WebSocket API の統合

バックプレッシャーを適用して、アプリが WebSocket メッセージで圧迫されたり、WebSocket サーバーにメッセージがフラッディングされたりすることを防ぎます。

背景

WebSocket API

WebSocket API WebSocket プロトコルへの JavaScript インターフェースを提供します。 双方向の対話型のコミュニケーションセッションを ブラウザとサーバーの間で通信が行われます。 この API を使用すると、サーバーにメッセージを送信して、イベント ドリブンのレスポンスを受信できます。 サーバーをポーリングして応答をポーリングすることなく、

Streams API

Streams API ネットワーク経由で受信したデータチャンクのストリームに、JavaScript からプログラムでアクセスできるようにする 必要に応じて処理できます ストリームに関する重要なコンセプトとして、 バックプレッシャー。 これは、単一のストリームまたはパイプ チェーンが 読み取りや書き込みの速度を調節します。 ストリーム自体またはパイプライン チェーンの後続ステップのストリームがまだビジー状態である場合 より多くのチャンクを受け入れる準備ができていない場合 必要に応じてチェーンを介してシグナルを逆方向に送信し、配信を遅らせます。

現在の WebSocket API の問題

受信したメッセージにバックプレッシャーを適用することは不可能

現在の WebSocket API では、メッセージへの応答は WebSocket.onmessage サーバーからメッセージを受信したときに呼び出される EventHandler

負荷の高いデータ クランチ オペレーションを実行する必要があるアプリケーションがあるとします。 新しいメッセージを受け取るたびに通知されるようになります。 おそらく次のコードのようなフローをセットアップします。 process() 呼び出しの結果を await しているため、問題ないはずです。

// 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 は Promise ベースであるため、自然に処理できます。 使用できます。 まず、新しい WebSocketStream を作成し、WebSocket サーバーの URL を渡します。 次に、接続が 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 の 2 番目の引数は、将来の拡張を可能にするオプション バッグです。 現在選択できるオプションは protocols のみです。 これは UDM イベントと同じように WebSocket コンストラクタの 2 番目の引数:

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

選択したprotocolと潜在的なextensionsは辞書の一部です WebSocketStream.opened Promise 経由で使用できます。 ライブ接続に関するすべての情報は、この Promise によって提供されます。 なぜなら、接続に失敗した場合は関係ないからです。

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

閉じた WebSocketStream 接続に関する情報

取得された情報は、 WebSocket.oncloseWebSocket.onerror 件のイベント WebSocket が、WebSocketStream.closed Promise を介して使用できるようになりました。 正常に終了しなかった場合、Promise は拒否されます。 それ以外の場合は、サーバーから送信されたコードと理由に解決されます。

発生する可能性のあるすべてのステータス コードとその意味については、 CloseEvent ステータス コードのリスト

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

WebSocketStream 接続の終了

WebSocketStream は AbortController。 そのため、AbortSignal を渡します。 WebSocketStream コンストラクタに渡します。

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>WebSockets」と入力します。 Glitch は、すばやく簡単に再現可能なケースを共有するのに最適です。

API のサポートを表示する

WebSocketStream API を使用する予定はありますか? 皆様の公開サポートは、Chrome チームが機能の優先順位付けを行う際に役立てられます 他のブラウザ ベンダーをサポートすることがいかに重要であるかを示しています。

ハッシュタグを使用して @ChromiumDev にツイートしてください #WebSocketStream どこで、どのように使用されているかをお知らせください。

関連情報

謝辞

WebSocketStream API は Adam Rice によって実装され、 平野豊 ヒーロー画像作成者: Daan Mooij スプラッシュを解除