SSEとは?Server-Sent Events の概要・仕組み・実装ガイドと運用・比較ポイント

SSE とは — 概要

SSE(Server-Sent Events、サーバー送信イベント)は、サーバーからクライアントへ一方向にリアルタイムにデータを送るための仕組みです。ブラウザ標準のAPIである EventSource を通じて実装され、HTTP(通常はテキストストリーム)を使ってイベントを継続的に送り続けます。チャットや株価更新、ログのストリーミング、通知、進捗表示など、サーバーからの「プッシュ」用途でよく使われます。

仕組みの基本(プロトコルとフォーマット)

SSE は HTTP のレスポンスを長時間オープンにして、サーバー側が逐次テキストを送り続ける方式です。サーバーはレスポンスヘッダで Content-Type: text/event-stream; charset=UTF-8 を指定し、ボディには所定のフォーマットでイベントを流します。イベントの基本フォーマットは以下のようになります(改行で区切られ、空行で1つのイベントが確定します)。

id: 12345
event: update
retry: 5000
data: 1行目のデータ
data: 2行目のデータ

  • id: イベントID(再接続時にサーバーが再送を制御するための識別子)
  • event: イベント名(ブラウザ側で addEventListener('update', ...) のように受け取れる)
  • retry: 再接続の待ち時間(ミリ秒)を指定
  • data: 実際のペイロード。複数行あれば各行 data: として続けられ、結合される
  • : コメント行は無視される(": this is a comment")

ブラウザはこのストリームをパースし、空行ごとにイベントを生成して JavaScript 側のイベントハンドラへ渡します。

ブラウザ側(EventSource API)

基本的な使い方は簡単です。JavaScript では次のように EventSource を作成します:

const es = new EventSource('/events');
// 全てのメッセージ(デフォルト)を受ける
es.onmessage = (e) => console.log('message', e.data);
// 名前付きイベント
es.addEventListener('update', (e) => console.log('update', e.data));
// エラー
es.onerror = (e) => console.error('SSE error', e);
// 認証クッキーなどを送る(オプション)
const es2 = new EventSource('/events', { withCredentials: true });

EventSource は自動再接続機能を持っており、接続が切れると retry(サーバーが指示)やデフォルトの間隔で自動的に再接続します。再接続時、ブラウザは最後に受け取った id を Last-Event-ID ヘッダとして送信するため、サーバーはその値を参照して中断点からの配信を実装できます。

SSE の特徴(長所と短所)

利点:

  • 実装が簡単:HTTP GET とテキスト出力だけで済むためサーバー実装が単純。
  • 自動再接続やイベントIDの仕組みを標準で持つ。
  • ブラウザのネイティブAPIで扱いやすく、イベント名でハンドリング可能。
  • HTTP の仕組みをそのまま利用できるため(キャッシュ制御、認証クッキー等)既存のインフラとの親和性が高い。

欠点・制約:

  • 一方向(サーバー→クライアント)であり、クライアントからの低レイテンシ双方向通信には不向き(WebSocket が代替)。
  • テキスト(UTF-8)ストリームであり、バイナリを直接送ることはできない(Base64 等でエンコードすれば可能)。
  • 古いブラウザ(代表的には Internet Explorer)ではネイティブサポートがないためフォールバックが必要な場合がある。
  • 接続ごとにサーバー側でソケットを保持するため、大量の同時接続を扱う際はスケーリング設計が必要。

WebSocket との比較

簡潔に言うと:

  • SSE:HTTP ベース、サーバー→クライアントの一方向、テキスト、実装が簡単、自動再接続あり。
  • WebSocket:専用プロトコル、双方向通信、バイナリ対応、低レイテンシでインタラクティブな用途に適する。

用途によって選択すると良いです。例えば「ライブ通知やログのストリーミング」のようなケースは SSE で十分かつ簡潔に実装できますが、リアルタイムな双方向ゲームや音声/映像ストリーミングなどは WebSocket(か WebRTC)が適しています。

実装上の注意点と運用上の落とし穴

1) ヘッダとステータスコード:サーバーは 200 ステータスと Content-Type: text/event-stream; charset=utf-8 を返すこと。204(No Content)や 304 などではストリームとして機能しません。

2) プロキシやロードバランサー:多くのリバースプロキシやロードバランサはレスポンスをバッファリングするため、SSE のようなストリーミングが遅延したりブロックされることがあります。代表的に nginx や Apache の設定で「バッファリング無効化」や「チャンク転送」を有効にする必要があります。具体的な設定は使用するプロキシのドキュメントに従ってください(例:nginx では proxy_buffering off; 等の設定が必要になることがある)。

3) 接続数の制限:ブラウザや中間デバイスは同一ホストに対する同時接続数を制限しています(ブラウザの接続上限やプロキシのタイムアウト)。多数のクライアントを抱えるサービスでは、コネクションごとのリソース(メモリ、スレッド、ファイルディスクリプタ)をどう捌くか設計が必要です。

4) 再接続と状態管理:EventSource の再接続は自動だが、サーバー側で再接続時にどこから再開するかはアプリケーションで設計する必要があります。id(Last-Event-ID)を使うか、サーバー側でタイムスタンプやシーケンス番号による差分送信ロジックを用いるとよいでしょう。

5) 認証と CORS:EventSource はデフォルトで同一オリジンでクッキーを送る動作がある点、またクロスオリジンの場合はサーバー側で Access-Control-Allow-Origin 等の CORS ヘッダを正しく設定する必要があります。カスタムヘッダ(例:Authorization)を EventSource の標準リクエストに付けられない制約があるため、トークンをクエリやクッキーで渡す等の工夫が必要になることがあります。ただし最近の仕様で new EventSource(url, { withCredentials: true }) が利用可能でクッキーを有効にできるブラウザもあります。

スケーリング戦略

SSE は接続ごとにサーバー側のリソース(ソケット)を使うため、多数の同時接続を捌くには工夫が必要です。一般的なパターン:

  • 非同期/イベント駆動型サーバー(Node.js、Go、Rust、nginx+バックエンド)を使い、少ないスレッドで多数コネクションを受ける。
  • 接続を受けるインスタンスは内部で pub/sub(Redis Pub/Sub、NATS、Kafka 等)を購読し、必要なイベントが来たら接続済みクライアントへ配信するブローカー役を担う。
  • 水平スケール時はロードバランサーでコネクションを分散。ステートフルな情報が必要なら sticky セッションや共通のメッセージブローカーを使う。
  • イベント送信のファンアウトはバックエンドで効率よく実装する(バッチング、フィルタリング、トピック毎の分割など)。

実装例(概要)

Node.js(Express)での簡単な例(概念レベル):

app.get('/events', (req, res) => {
  res.writeHead(200, {
    'Content-Type': 'text/event-stream; charset=utf-8',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive'
  });
  res.write('\n'); // ブラウザがストリーム開始を認識しやすくするために書くことがある
  const id = generateId();
  const interval = setInterval(() => {
    res.write('id: ' + id + '\n');
    res.write('event: update\n');
    res.write('data: ' + JSON.stringify({ time: Date.now() }) + '\n\n');
  }, 1000);

  req.on('close', () => {
    clearInterval(interval);
  });
});

実運用では中間ノードの設定、エラーハンドリング、メモリリーク対策、接続上限管理等を追加する必要があります。

デバッグと運用のヒント

  • ブラウザの開発者ツールのネットワークタブでレスポンスがフラッシュされているか(チャンクが届くか)を確認する。
  • プロキシや CDN を挟む場合はストリーミングがブロックされないか(バッファされないか)を検証する。
  • 接続切断時にクリーンアップ(タイマー解除やイベント購読解除)を確実に行う。
  • 大規模配信では内部でメッセージブローカーを使ったパブサブ設計を採ると拡張しやすい。

セキュリティ上の注意

SSE 自体は HTTP 上のテキスト送信に過ぎませんが、次の点に気をつけてください:

  • 機密データを送る場合は必ず HTTPS を使う(中間者攻撃を防ぐため)。
  • CORS を正しく設定し、不要なオリジンへの公開を避ける。
  • 認証情報の扱い:EventSource はカスタムヘッダを送れないことがあるので、セキュアな方法(Cookie の Secure/HttpOnly、短寿命トークン等)で認証を設計する。
  • DoS 対策:多数のコネクション確立によるリソース枯渇を防ぐためのレート制限や接続上限の設計が必要。

実際のユースケース

  • ニュースやスポーツのライブ更新、株価・為替のティッカー表示
  • サーバーのログやイベントのリアルタイム表示(監視ダッシュボード)
  • プッシュ通知(小規模な通知センターやダッシュボード)
  • 長時間実行タスクの進捗報告(ビルドやデータ処理の進捗をフロントにストリーミング)

対応ブラウザと互換性

モダンブラウザ(Chrome、Firefox、Safari、Chromium ベースの Edge 等)では SSE がサポートされています。Internet Explorer(レガシー)はネイティブサポートがなく、必要ならポリフィルやフォールバック(長いポーリング)を用意する必要があります。具体的なブラウザ対応状況は随時変わるため、ターゲットとするブラウザでのサポートを確認してください(MDN 等の最新互換表を参照)。

まとめ(いつ使うべきか)

SSE は「サーバーからクライアントへ継続的にテキストイベントを送る」用途に対してシンプルかつ効果的な選択肢です。双方向性やバイナリが必要ない場面、実装のシンプルさや自動再接続機能を重視する場面では非常に有用です。一方で多数の同時接続をスケーリングする必要がある場合や、低レイテンシ双方向通信が必要な場合は WebSocket やその他の技術(WebRTC 等)を検討してください。

参考文献