WebSocketStream:ストリームをWebSocket APIと統合する
アプリがWebSocketメッセージに溺れるのを防ぎます または、バックプレッシャを適用してWebSocketサーバーをメッセージで溢れさせます。
バックグラウンド
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アプリケーション、特にビデオと画面共有。
- 同様に、サーバーにアップロードする必要があるブラウザで大量のデータを生成するビデオキャプチャやその他のアプリケーション。背圧を使用することで、クライアントはデータをメモリに蓄積するのではなく、データの生成を停止することができます。
現在のステータス
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.onclose
とWebSocket.onerror
から入手できた情報は、WebSocketStream.closed
promiseから入手できるようになりました。不潔なクローズの場合、約束はリークジェクトします。それ以外の場合、サーバーから送信されたコードと理由で解決されます。
考えられるすべてのステータスコードとその意味はCloseEvent
ステータスコードのリストで説明されています。
const {code, reason} = await chatWSS.closed;
WebSocketStream接続を閉じる
AbortController
でWebSocketStreamを閉じることができます。したがって、 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を実装する唯一のブラウザです。クラシックの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
をお使いください。
参考リンク
- 公開説明者
- WebSocketStreamAPIデモ| WebSocketStreamAPIデモソース
- バグの追跡
- ChromeStatus.comエントリ
- 点滅コンポーネント:
Blink>Network>WebSockets
謝辞
WebSocketStream APIはAdam RiceとYutaka Hiranoによって実装されました。ヒーローイメージはDaan MooijにUnsplashでアップロードされました。