水が滴り落ちる消火ホース。

WebSocketStream:ストリームをWebSocket APIと統合する

アプリがWebSocketメッセージに溺れるのを防ぎます または、バックプレッシャを適用してWebSocketサーバーをメッセージで溢れさせます。

公開日 更新日

翻訳先言語: English, Português, 한국어, 中文, Pусский

バックグラウンド

WebSocket API

WebSocket APIは、WebSocketプロトコルへJavaScriptインターフェースを提供します。これにより、ユーザーのブラウザーとサーバーの間で双方向の対話型通信セッションを開くことができます。このAPIを使用すると、サーバーにメッセージを送信し、応答のためにサーバーをポーリングしなくても、イベント駆動型の応答を受信できます。

Streams API

Streams APIをを使用することで、JavaScriptは、ネットワーク経由で受信したデータチャンクのストリームにプログラムでアクセスし、それらを適切に処理できます。ストリームのコンテキストでの重要な概念は背圧です。これは、単一のストリームまたはパイプチェーンが読み取りまたは書き込みの速度を調整するプロセスです。ストリーム自体またはパイプチェーンの次のストリームがまだビジーであり、さらにチャンクを受け入れる準備ができていない場合、必要に応じて配信を遅くするためにチェーンを介して信号を逆方向に送信します。

現在のWebSocketAPIの問題

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

現在のWebSocketAPIでは、メッセージへの反応は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()メソッドが処理できるよりも速くメッセージが到着すると、レンダリングプロセスはそれらのメッセージをバッファリングすることによってメモリをいっぱいにするか、100%のCPU使用率のせいで応答しなくなるか、またはその両方が発生します。

送信されたメッセージに背圧を適用することは非人間工学的です

送信されたメッセージにバックプレッシャを適用することは可能ですが、 WebSocket.bufferedAmountプロパティをポーリングすることに係わって、これは非効率的と非人間工学的です。WebSocket.send()呼び出しを使用してキューに入れられたが、ネットワークにまだ送信されていないデータのバイト数を返します。キューに入れられたすべてのデータが送信されると、この値はゼロにリセットされますが、 WebSocket.send()呼び出しを続けると、上昇し続けます。

WebSocketStream APIとは何ですか?

WebSocketStream APIは、ストリームをWebSocket APIと統合することにより、存在しない、または非人間工学的な背圧の問題に対処します。これは、追加費用なしで「無料」で背圧が適用させられることを意味します。

WebSocketStream APIの推奨されるユースケース

このAPIを使用できるサイトの例は次のとおりです。

  • 対話性を維持する必要がある高帯域幅のWebSocketアプリケーション、特にビデオと画面共有。
  • 同様に、サーバーにアップロードする必要があるブラウザで大量のデータを生成するビデオキャプチャやその他のアプリケーション。背圧を使用することで、クライアントはデータをメモリに蓄積するのではなく、データの生成を停止することができます。

現在のステータス

ステップ状態
1.説明者を作成します完了
2.仕様の初期ドラフトを作成します進行中
3.フィードバックを収集し、設計を繰り返します進行中
4.Origin trial完了
5.起動未開始

WebSocketStream APIの使用方法

導入例

WebSocketStream APIはpromiseベースであるため、最新のJavaScript界で自然に処理できます。まず、新しいWebSocketStreamを作成し、それにWebSocketサーバーのURLを渡します。次にconnectionが確立されるのを待ちます。これにより、ReadableStream及び/又はWritableStreamが生成されます。

ReadableStream.getReader() メソッドを呼び出すことで、最終的にReadableStreamDefaultReaderを取得します。これにより、ストリームが完了するまで、つまり、{value: undefined, done: true}の形式のオブジェクトが返されるまで、データをread()できます。

したがって、 WritableStream.getWriter()メソッドを呼び出すことにより、最終的にWritableStreamDefaultWriter取得し、これにデータをwrite()ことができます。

  const wss = new WebSocketStream(WSS_URL);
const {readable, writable} = await wss.connection;
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 です。これは、WebSocketコンストラクターの2番目の引数と同じように動作します。

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

選択されたprotocolと潜在的なextensions WebSocketStream.connectionを介して利用可能な辞書の一部です。ライブ接続に関するすべての情報は、接続が失敗した場合には関係がないため、このPromiseによって提供されます。

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

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

WebSocket APIのWebSocket.oncloseWebSocket.onerrorから入手できた情報は、WebSocketStream.closed promiseから入手できるようになりました。不潔なクローズの場合、約束はリークジェクトします。それ以外の場合、サーバーから送信されたコードと理由で解決されます。

考えられるすべてのステータスコードとその意味はCloseEventステータスコードのリストで説明されています。

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

WebSocketStream接続を閉じる

AbortControllerでWebSocketStreamを閉じることができます。したがって、 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を実装する唯一のブラウザです。クラシックのWebSocketAPIとの相互運用性のために、受信したメッセージに背圧を適用することはできません。送信されたメッセージに背圧を適用することは可能ですが、  WebSocket.bufferedAmountプロパティをポーリングすることに係わって、これは非効率的と非人間工学的です。

特徴の検出

WebSocketStream APIがサポートされているかどうかを確認するには、次を使用しましょう。

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

デモ

サポートされているブラウザでは、埋め込みiframeで、またはGlitchで直接WebSocketStreamAPIの動作を確認できます。

フィードバック

Chromeチームは、WebSocketStreamAPIの使用経験について聞きたいと思っています。

APIの設計について教えてください

期待どおりに機能しないAPIについて何かありますか?または、アイデアを実装するために必要なメソッドやプロパティが不足していますか?セキュリティモデルについて質問やコメントがありますか?該当の対応するGitHubリポジトリに仕様の問題をファイルするか、既存の問題に考えを追加してください。

実装に関する問題を報告する

Chromeの実装にバグを見つけましたか?それとも、実装は仕様とは異なりますか? new.crbug.comででバグを報告してください。できる限り詳細な説明と複製の簡単な手順を必ず含めて、コンポーネントボックスにコンポーネントBlink>Network>WebSocketsと入力してください。グリッチは、すばやく簡単に共有できる複製ケースに最適です。

APIのサポートを表示します

WebSocketStream APIを使用する予定がありますか?パブリックサポートは、Chromeチームが機能に優先順位を付けることに役立ち、他のブラウザベンダーにそれらをサポートすることがいかに重要であるかを示します。

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

参考リンク

謝辞

WebSocketStream APIはAdam RiceYutaka Hiranoによって実装されました。ヒーローイメージはDaan MooijUnsplashでアップロードされました。

更新日 記事を改善する

We serve cookies on this site to analyze traffic, remember your preferences, and optimize your experience.