過小評価されている Server-Sent Events(SSE)技術
(igorstechnoclub.com)サーバー送信イベント(Server-Sent Events, SSE)は過小評価されている
- ほとんどの開発者は WebSockets について知っているが、SSE はよりシンプルで、しばしば見過ごされがちな代替手段である。
- SSE は、サーバーからクライアントへの一方向通信チャネルを HTTP を通じて確立する。
- WebSockets の双方向接続とは異なり、SSE はサーバーからクライアントへの更新のために開いた HTTP 接続を維持する。
SSE が過小評価される理由
- WebSocket の人気: WebSockets の全二重通信機能が、SSE のシンプルなアプローチを覆い隠している。
- 制限に対する認識: 一方向という特性は制約が大きいように見えるかもしれないが、多くのユースケースでは十分である。
SSE の主な強み
-
実装のシンプルさ
- 標準 HTTP プロトコルを活用することで、WebSocket 接続管理の複雑さを取り除く。
-
インフラ互換性
- 既存の HTTP インフラとシームレスに動作する:
- ロードバランサー
- プロキシ
- ファイアウォール
- 標準 HTTP サーバー
- 既存の HTTP インフラとシームレスに動作する:
-
リソース効率
- WebSockets と比べてリソース消費が少ない:
- 一方向という特性
- 標準 HTTP 接続の利用
- 継続的なソケット維持管理が不要
- WebSockets と比べてリソース消費が少ない:
-
自動再接続
- ブラウザーの組み込みサポート:
- 接続切断への対応
- 自動再接続の試行
- 回復力のあるリアルタイム体験
- ブラウザーの組み込みサポート:
-
明確なセマンティクス
- 一方向通信パターンによって次が保証される:
- 関心の明確な分離
- 直感的なデータフロー
- 単純化されたアプリケーションロジック
- 一方向通信パターンによって次が保証される:
実用的な応用
- リアルタイムのニュースフィードとソーシャル更新
- 株価情報と金融データ
- プログレスバーとジョブ監視
- サーバーログのストリーミング
- コラボレーション編集(更新用)
- ゲームのリーダーボード
- 位置追跡システム
実装例
サーバー側(Flask)
/streamパスが SSE 接続を処理する。generate_random_data()がフォーマット済みイベントを継続的に生成する。text/event-streamMIME タイプが SSE プロトコルを示す。stream_with_contextが Flask アプリケーションコンテキストを維持する。
クライアント側(JavaScript)
EventSourceオブジェクトが SSE 接続を管理する。onmessageハンドラーが受信したイベントを処理する。onerrorが接続の問題を処理する。- ブラウザーが自動再接続を処理する。
制限事項と考慮点
-
一方向通信
- サーバーからクライアントへのみ可能
- クライアントからサーバーへの通信には別途 HTTP リクエストが必要
-
ブラウザー対応
- 最新ブラウザーでは十分にサポートされている
- 古いブラウザーではポリフィルが必要な場合がある
-
データ形式
- 主にテキストベースのデータをサポート
- バイナリデータはエンコードが必要(例: Base64)
ベストプラクティス
-
エラー処理
eventSource.onerrorで接続エラーを処理する。
-
接続管理
- 完了時に接続をクリーンアップする。
-
再接続戦略
- 最大再試行回数を設定し、再接続ロジックを実装する。
実例: ChatGPT の実装
- 現代の大規模言語モデル(LLM)は SSE を使ってストリーミング応答を提供する。
- 主なパターン:
content-type: text/event-streamヘッダーを返す\r\n\r\nで区切られたデータブロックをストリーミングする
結論
- SSE は、リアルタイムのサーバー・クライアント通信に対するエレガントなソリューションを提供する。
- シンプルさ、効率性、既存インフラとの統合により、多くのアプリケーションに適した選択肢となる。
- WebSockets は双方向通信において依然として有用だが、SSE は一方向データストリーミングのシナリオにより特化した適切なソリューションを提供する。
5件のコメント
OpenAIをRESTで実装しながら、SSEを実際に使いました。
一方向通信が必要な場面では、ぜひ採用したいと思います。
SSE はセキュリティ機器(WAF やインテリジェントセキュリティ)にはブロックされないものの、改行文字単位でのストリーミングができないケースによく遭遇します。(オンプレミスの)途中で応答をすべて受け取ってから、一気に送ってくるような形です。
OpenAPIがSSEをサポートしていないのは本当に残念です
NAT 環境で双方向通信を構築するのに本当に良い方法ですね。
Hacker Newsのコメント
MercureはSSEベースのオープンプロトコルで、WebSocketsベースのソリューションの代替として使われている。Mercureは、クライアントとの持続的なSSE接続を維持する独立したハブを中心に動作し、サーバーアプリとクライアントの両方が利用できるシンプルなHTTP APIを提供する。Mercureは、JWTベースの認証メカニズム、複数トピックを単一接続で購読する機能、イベント履歴、ネットワーク障害発生時の自動状態調整などの機能を追加している
SSEの大きな欠点は、HTTP/2でない場合に最大接続数の制限があること。これはブラウザごとの制限が低いため、複数のタブを開いたときに問題になることがある
DopplerのCLIでは、SSEを使って自動再起動機能を実装した。SSEを通じてサーバーからイベントを受信し、最新のシークレット情報を取得してアプリケーションプロセスに注入する。WebSocketsではなくSSEを選んだ理由は、Golangアプリケーションに追加の依存関係を持ち込みたくなかったため。HTTPタイムアウトの問題を解決するために、断続的な"ping"イベントを送信する必要があった
SSEの単方向という性質は制限があるように見えるかもしれないが、多くの場合それで十分。SSEの主な制約は、テキスト専用であることと、HTTP/1.1におけるブラウザの接続制限。HTTP/2以降を使えば、接続制限は問題にならない。パフォーマンスが重要な場合は、fetchとReadableStreamを使って、より柔軟でオーバーヘッドの少ないソリューションを選べる
SSEはシンプルであるがゆえに、多くの開発者が適切な実装を使わず、データチャンクを正規表現でパースしてしまうことが多い。SSEはストリーム内でコメントをサポートしているため、これは問題になり得る
Data-star.devは、SSEを通じてハイパーメディア応答をストリーミングすることに重点を置いたフロントエンドライブラリ。GoとNATSをバックエンド技術として使用して開発されており、あらゆるSSE実装と互換性がある
SSEは過小評価されているわけではない。実際、Open AIでストリーミング完了に使われている。ReactJSのコードベースでSSEを実装するのは難しく、当時Axiosがこれをサポートしていなかったため、ネイティブのfetchを使わなければならなかった
WebプロジェクトでSSEを実装したとき、6個を超えるタブを開くとWebサイトが動作を停止した。FirefoxはSSE接続をホストあたり最大6接続という制限に含めるため、追加のリクエストがブロックされる
SSEは、うまく動作するときには過小評価されがち。現在取り組んでいるプロジェクトでは、認証の問題やトンネルのkeep-aliveの問題のために苦労している。これはプロトコル自体の問題ではなく、解決策を見つけるのが難しい