34 ポイント 投稿者 GN⁺ 2024-12-26 | 5件のコメント | WhatsAppで共有

サーバー送信イベント(Server-Sent Events, SSE)は過小評価されている

  • ほとんどの開発者は WebSockets について知っているが、SSE はよりシンプルで、しばしば見過ごされがちな代替手段である。
  • SSE は、サーバーからクライアントへの一方向通信チャネルを HTTP を通じて確立する。
  • WebSockets の双方向接続とは異なり、SSE はサーバーからクライアントへの更新のために開いた HTTP 接続を維持する。

SSE が過小評価される理由

  • WebSocket の人気: WebSockets の全二重通信機能が、SSE のシンプルなアプローチを覆い隠している。
  • 制限に対する認識: 一方向という特性は制約が大きいように見えるかもしれないが、多くのユースケースでは十分である。

SSE の主な強み

  1. 実装のシンプルさ

    • 標準 HTTP プロトコルを活用することで、WebSocket 接続管理の複雑さを取り除く。
  2. インフラ互換性

    • 既存の HTTP インフラとシームレスに動作する:
      • ロードバランサー
      • プロキシ
      • ファイアウォール
      • 標準 HTTP サーバー
  3. リソース効率

    • WebSockets と比べてリソース消費が少ない:
      • 一方向という特性
      • 標準 HTTP 接続の利用
      • 継続的なソケット維持管理が不要
  4. 自動再接続

    • ブラウザーの組み込みサポート:
      • 接続切断への対応
      • 自動再接続の試行
      • 回復力のあるリアルタイム体験
  5. 明確なセマンティクス

    • 一方向通信パターンによって次が保証される:
      • 関心の明確な分離
      • 直感的なデータフロー
      • 単純化されたアプリケーションロジック

実用的な応用

  • リアルタイムのニュースフィードとソーシャル更新
  • 株価情報と金融データ
  • プログレスバーとジョブ監視
  • サーバーログのストリーミング
  • コラボレーション編集(更新用)
  • ゲームのリーダーボード
  • 位置追跡システム

実装例

サーバー側(Flask)
  • /stream パスが SSE 接続を処理する。
  • generate_random_data() がフォーマット済みイベントを継続的に生成する。
  • text/event-stream MIME タイプが SSE プロトコルを示す。
  • stream_with_context が Flask アプリケーションコンテキストを維持する。
クライアント側(JavaScript)
  • EventSource オブジェクトが SSE 接続を管理する。
  • onmessage ハンドラーが受信したイベントを処理する。
  • onerror が接続の問題を処理する。
  • ブラウザーが自動再接続を処理する。

制限事項と考慮点

  1. 一方向通信

    • サーバーからクライアントへのみ可能
    • クライアントからサーバーへの通信には別途 HTTP リクエストが必要
  2. ブラウザー対応

    • 最新ブラウザーでは十分にサポートされている
    • 古いブラウザーではポリフィルが必要な場合がある
  3. データ形式

    • 主にテキストベースのデータをサポート
    • バイナリデータはエンコードが必要(例: Base64)

ベストプラクティス

  • エラー処理

    • eventSource.onerror で接続エラーを処理する。
  • 接続管理

    • 完了時に接続をクリーンアップする。
  • 再接続戦略

    • 最大再試行回数を設定し、再接続ロジックを実装する。

実例: ChatGPT の実装

  • 現代の大規模言語モデル(LLM)は SSE を使ってストリーミング応答を提供する。
  • 主なパターン:
    • content-type: text/event-stream ヘッダーを返す
    • \r\n\r\n で区切られたデータブロックをストリーミングする

結論

  • SSE は、リアルタイムのサーバー・クライアント通信に対するエレガントなソリューションを提供する。
  • シンプルさ、効率性、既存インフラとの統合により、多くのアプリケーションに適した選択肢となる。
  • WebSockets は双方向通信において依然として有用だが、SSE は一方向データストリーミングのシナリオにより特化した適切なソリューションを提供する。

5件のコメント

 
eususu 2024-12-27

OpenAIをRESTで実装しながら、SSEを実際に使いました。
一方向通信が必要な場面では、ぜひ採用したいと思います。

 
galadbran 2024-12-27

SSE はセキュリティ機器(WAF やインテリジェントセキュリティ)にはブロックされないものの、改行文字単位でのストリーミングができないケースによく遭遇します。(オンプレミスの)途中で応答をすべて受け取ってから、一気に送ってくるような形です。

 
savvykang 2024-12-27

OpenAPIがSSEをサポートしていないのは本当に残念です

 
alska1039 2024-12-26

NAT 環境で双方向通信を構築するのに本当に良い方法ですね。

 
GN⁺ 2024-12-26
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の問題のために苦労している。これはプロトコル自体の問題ではなく、解決策を見つけるのが難しい