SSE(Server-Sent Events)とは|仕組み・WebSocketとの違い・実装を解説
SSE(Server-Sent Events)は、サーバーからクライアントへ一方向にデータを送り続けるHTTPベースのリアルタイム通信です。ブラウザ標準のEventSourceで接続を1本張れば、サーバーは接続を保ったまま、更新が起きたタイミングでだけイベントを送れます。WebSocketのような独自プロトコルもハンドシェイクも不要で、通常のHTTPレスポンスをストリームとして返すだけで成立します。通知・進捗表示・生成AIの逐次出力のように「サーバーからの片方向push」で足りる場面に向いた技術です。この記事では定義から仕組み、メッセージ形式、WebSocketとの使い分け、デメリット、Node.js・Python・PHPでの実装までをまとめます。
まとめ:SSEは「片方向push」で十分な場面の最短手
SSEの要点は次の通りです。クライアントからサーバーへ送り返す必要がなく、サーバーからの一方的な更新配信で足りるなら、SSEがWebSocketより実装・運用コストが小さい。通信はtext/event-streamというプレーンテキストのストリームで、data・event・id・retryの4フィールドだけで成り立ちます。切断時はEventSourceが自動で再接続し、idを付けておけばLast-Event-IDで続きから受信を再開できます。逆に双方向のやり取りが要るチャットの送信側やオンラインゲームはWebSocketの領分です。かつて弱点とされた「同一ドメイン約6接続まで」という制限はHTTP/1.1固有のもので、HTTP/2環境では多重化により実質的に解消されています。以降で仕組みと判断基準、実装を具体的に見ていきます。
SSE(Server-Sent Events)の定義とHTTPベースの一方向通信
SSEは、クライアントが1度HTTPリクエストを送ると、サーバーがそのレスポンスを閉じずに開いたまま保持し、イベントが発生するたびにデータを書き込んでいく仕組みです。レスポンスのContent-Typeにtext/event-streamを指定することで、ブラウザはこの接続をイベントストリームとして解釈します。HTML標準仕様(WHATWG HTML Living Standard)の一部として定義されており、Internet Explorerを除く主要ブラウザ(Chrome・Firefox・Safari・Edge)が標準対応しています。新しいライブラリを足さずに、ブラウザ組み込みのEventSourceだけでリアルタイム受信を始められる点が、ポーリングやWebSocketと比べたときの導入しやすさにつながります。
ポーリング・ロングポーリング・WebSocketとの位置づけ
Webでリアルタイム性を出す手段は、用途と実装コストで整理できます。下表は4方式の通信方向と接続の持ち方を比較したものです。SSEは「サーバー→クライアントの一方向」「HTTP接続を1本維持」というポジションにあり、ポーリングの無駄打ちとWebSocketの構成の重さの中間に位置します。
| 方式 | 通信方向 | 接続 | 主な用途 |
|---|---|---|---|
| ポーリング | 都度リクエスト | 毎回新規 | 更新頻度が低い軽量用途 |
| ロングポーリング | 都度(応答保留) | 応答ごとに張り直し | 準リアルタイム |
| SSE | サーバー→クライアント | 1本維持 | 通知・進捗・逐次表示 |
| WebSocket | 双方向 | 1本維持(全二重) | チャット・ゲーム・協同編集 |
判断の出発点はシンプルです。クライアントからサーバーへ随時送り返す必要がなければSSE、必要ならWebSocket。ポーリングは更新がまれで実装を最小にしたいときに限って選びます。なお4方式に加えWebRTCやWebTransportまで含めた性能比較はパフォーマンス比較: WebSockets、SSE、WebRTC、WebTransportで詳しく扱っています。
SSEの仕組み|EventSourceによる接続・維持・再接続のフロー
SSEの動作は「接続の確立」「接続の維持」「切断時の再接続」の3段階で理解すると整理しやすく、それぞれGSCで実際に検索されている意図(sse接続・sse stream・sse 仕組み)に対応します。以下、クライアント側・サーバー側・再接続の順に分けて説明します。
クライアント側:EventSourceでの接続とイベント受信
クライアントはJavaScript組み込みのEventSourceに接続先URLを渡すだけで接続を開始します。標準メッセージはonmessage、サーバーがevent名を付けたカスタムイベントはaddEventListenerで受け取ります。受信データはevent.dataに文字列で入るため、JSONで送ってクライアントでJSON.parseする設計が扱いやすいです。接続状態はreadyState(CONNECTING / OPEN / CLOSED)で確認でき、不要になればclose()で明示的に切れます。
const source = new EventSource(
"/events");
source.onmessage = (e) => {
const data = JSON.parse(e.data);
console.log(data);
};
source.addEventListener("notice", (e) => {
console.log("notice:", e.data);
});
source.onerror = () => {
console.log("readyState:", source.readyState);
};
サーバー側:レスポンスを開いたまま維持する仕組み
サーバーは通常のHTTP GETに対し、レスポンスを完了させずにストリームとして開いたまま保持します。必須ヘッダーはContent-Type: text/event-streamで、これが無いとブラウザはSSEとして扱いません。あわせてCache-Control: no-cacheでキャッシュを無効化し、プロキシによる切断を避けるためConnection: keep-aliveを付けます。アイドルが続くと中間機器が接続を切ることがあるため、コロン始まりのコメント行(例:: ping)を定期送信して接続を保つのが定石です。
切断時の自動再接続とLast-Event-IDによる再開
SSEは耐障害性を仕様に組み込んでいます。ネットワーク断などで接続が切れると、EventSourceが自動再接続します(待機間隔は多くのブラウザで約3秒の実装依存既定値。retryフィールドで変更可)。再接続時にはブラウザが直近に受信したidをLast-Event-IDヘッダーで送るため、サーバー側でこの値を見て「続きから」配信すれば、メッセージの欠落や重複を防げます。これを活かすには、送信するイベントごとにidを必ず付け、サーバー側で過去イベントを再送できるよう保持しておく設計が前提になります。
SSEのメッセージ形式|data・event・id・retryの構成
SSEのストリームはプレーンテキストで、1イベントは空行で区切ります。使えるフィールドは4つだけです。役割を押さえれば仕様の全体像はほぼ把握できます。
| フィールド | 役割 | 備考 |
|---|---|---|
| data | 送信本文 | 複数行は data 行を連続させると結合 |
| event | カスタムイベント名 | addEventListener でフック |
| id | イベント識別子 | Last-Event-IDで再開に使用 |
| retry | 再接続待機(ミリ秒) | 実装依存で約3000 |
コロン(:)で始まる行はコメントとして無視され、前述の接続維持pingに使います。文字エンコーディングはUTF-8固定なので、日本語やマルチバイトもそのまま送れます。注意点は、dataに改行を含めたいときは1行ずつdata:を重ねること。生の改行を入れるとイベントの区切りと誤認されます。
SSEとWebSocket・ポーリングの違いと使い分け
「sse websocket 違い」は実際に5位前後で検索される意図で、ここを明確にすることがSSE選定の核心です。両者の根本差は通信方向とプロトコルにあります。SSEはHTTPベースの一方向、WebSocketは初回HTTPからアップグレードして独自プロトコルで全二重通信を行います。バイナリ送受信や低レイテンシの相互通信が要るならWebSocket、サーバーからの配信だけで足りるならSSE、という切り分けが基本です。
| 観点 | SSE | WebSocket |
|---|---|---|
| 通信方向 | 一方向 | 双方向(全二重) |
| プロトコル | HTTP | ws/wss(独自) |
| データ形式 | テキストのみ | テキスト/バイナリ |
| 自動再接続 | 標準装備 | 自前実装 |
| 実装コスト | 小 | 中〜大 |
判断を言い切ると、チャットアプリは「送信=HTTP POST、受信=SSE」と分離すれば、WebSocketを使わずに安定したリアルタイム受信を構成できます。逆に協同編集やマルチプレイヤーゲームのように両端が頻繁に送り合う設計でSSEを選ぶのは不適切で、受信専用のSSEに無理やり送信経路を足すとかえって複雑になります。WebSocketを土台にフォールバックまで面倒を見るライブラリを使いたい場合はSocket.IOとは何か:リアルタイム双方向通信の基礎を理解するを、HTTPストリーム自体の扱いはStreams APIとは?その基本概念と重要な活用ポイントも参考になります。
SSEのデメリットと運用上の注意点
SSEは導入が軽い一方、実運用では引っかかる点があります。「server-sent events デメリット」はCTRが高く(実測18%超)、ここを正確に書けるかが他記事との差になります。重要度の高い順に、接続数の上限・HTTP/2との関係・認証の制約・モバイル環境を取り上げます。
同一ドメイン約6接続の上限とHTTP/2による解消
最大の落とし穴は接続数です。HTTP/1.1では、ブラウザが1ドメインあたりに張れる同時接続が約6本に制限されます。SSEは接続を保持し続けるため、複数タブを開くとこの6本をすぐ使い切り、新しいSSE接続やほかの通信が詰まります。ここで誤解されがちなのが「SSEはHTTP/2に対応していない」という説明ですが、これは正しくありません。SSEはHTTP/2上でも動作し、HTTP/2は1本のTCP接続上で複数ストリームを多重化する(サーバー・クライアント間でネゴシエートされ既定100程度)ため、この6接続制限そのものを解消します。したがって現実的な対策は「HTTP/2で配信する」ことであり、HTTP/1.1に固定する必要はありません。多数同時接続を捌くなら、加えてロードバランサーでの分散やRedis Pub/Subなどでの接続管理外部化を検討します。
EventSourceのヘッダー制約と認証設計
EventSourceは仕様上、リクエストに任意のカスタムヘッダーを付けられません。そのためAuthorization: Bearer方式のトークン認証をそのままでは載せられず、これがSSE特有の制約になります。現実的な解は、Cookieベースのセッションで認証状態を確立してから接続する方法か、短命の使い捨てトークンを事前にAPIで取得してクエリパラメータに載せる方法です。後者はURLが履歴やログに残るため、有効期限を短く切り、クライアント情報と紐づけて使い回しを防ぎます。クロスオリジンで送る場合はEventSourceのwithCredentialsとサーバー側のCORS設定(Access-Control-Allow-Origin等)を合わせます。
モバイル・不安定回線での挙動とエラーハンドリング
モバイル回線や公共Wi-Fiでは切断と再接続を繰り返しやすく、表面上は自動再接続で動いて見えても、再接続の嵐がバッテリーとサーバー負荷を押し上げることがあります。idベースの差分配信で重複受信を抑えつつ、サーバー側で再接続回数を記録し閾値超過時に制限する運用が効きます。また自動再接続では拾えないエラー(不正なレスポンス形式、恒久的なサーバーダウン)もあるため、onerrorでログと通知を実装し、再接続を止めたいときはサーバーがHTTP 204を返して終了させる、といった制御を用意しておきます。
SSEの実装|EventSource(JS)とサーバー側(Node.js・Python・PHP)
サーバー側の実装言語は「sse php」「java sse」「express sse」「vue sse」など幅広く検索されています。共通する勘所は、レスポンスヘッダーを正しく設定し、書き込み後に確実にflushして即時送信することの2点です。代表的なNode.jsとPythonの最小実装を示します。
Node.js(Express)でのSSE送信
Expressではres.writeHeadでtext/event-streamを含むヘッダーを返し、res.writeでdata:行を書き込みます。バッファされず即送信されるよう、必要に応じてflushを意識します。
app.get("/events", (req, res) => {
res.writeHead(200, {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
});
let id = 0;
const timer = setInterval(() => {
res.write(`id: ${++id}\n`);
res.write(`data: ${JSON.stringify({ time: Date.now() })}\n\n`);
}, 1000);
req.on("close", () => clearInterval(timer));
});
Python(Flask)でのSSE送信
FlaskではジェネレータでyieldしながらResponseのmimetypeにtext/event-streamを指定します。各イベントはdata:で始め、空行で区切ります。なお開発サーバーでは即時送信されますが、本番でgunicornなどのWSGIサーバーを使う場合はワーカーのバッファリングで配信が詰まることがあるため、バッファ無効化の設定を確認します。
from flask import Flask, Response
import json, time
app = Flask(__name__)
@app.route("/events")
def events():
def generate():
i = 0
while True:
i += 1
yield f"id: {i}\n"
yield f"data: {json.dumps({'t': time.time()})}\n\n"
time.sleep(1)
return Response(generate(), mimetype="text/event-stream")
PHPでのSSE送信
PHPはheader()でtext/event-streamを返し、echoでdata:行を出力したあとob_flush()とflush()で即時送信します。出力バッファが残ると配信が遅延するため、flushの明示が要点です。
<?php
header("Content-Type: text/event-stream");
header("Cache-Control: no-cache");
$id = 0;
while (true) {
$id++;
echo "id: {$id}\n";
echo "data: " . json_encode(["time" => time()]) . "\n\n";
ob_flush();
flush();
sleep(1);
}
PHPやGo、Java、Rubyでも考え方は同じで、各言語のHTTPストリーミング機能でdata:行を逐次出力します。フレームワーク側のライブラリ(Express系のexpress-sse、Flask向けのflask-sseなど)を使うと、ヘッダー設定や接続管理を肩代わりしてくれます。SSEとよく対比されるStreamable HTTPなど近年のストリーミング方式はStreamable HTTPトランスポートとは何か?その概要と目的を詳解で整理しています。
SSEのセキュリティ|HTTPS・CORS・アクセス制御
SSEはHTTP通信のため、暗号化しなければ内容が盗聴されます。全通信をHTTPS(TLS)で行い、HSTSの導入で意図しないHTTP接続を排除するのが前提です。アクセス制御は多層で考えます。SSEエンドポイントは認証済みユーザーのみに開放し、CORSで許可ドメインを限定、Originを検証してクロスサイトからのストリーム取得を防ぎます。前述のとおりカスタムヘッダーを付けられない制約があるため、CSRF対策はセッション確立後の接続か、有効期限の短いトークンのクエリ付与で行います。配信するイベントには必要最小限のデータだけを載せ、機密情報をストリームに流さない設計が情報漏えいリスクを最も確実に下げます。
よくある質問(FAQ)
Server-Sent Eventsとは何ですか?
サーバーからクライアントへHTTP接続を通じて一方向にデータを送り続ける仕組みです。ブラウザ標準のEventSourceで接続し、サーバーはtext/event-stream形式でイベントをpushします。通知や進捗表示、生成AIの逐次出力など「片方向の配信」で足りる用途に向きます。
SSEとストリーミングは同じものですか?
厳密には別です。ストリーミングは「データを少しずつ連続して送る」概念全般を指し、SSEはそれをHTTP上でブラウザが扱いやすい形に標準化した一実装です。生成AIの応答が文字単位で表示されるのは、SSEというストリーミングの一形態が使われている代表例です。
SSEとWebSocketの違いは何ですか?
SSEはサーバー→クライアントの一方向でHTTPベース、WebSocketは双方向(全二重)で独自プロトコルです。受信だけならSSEが軽量で自動再接続も標準装備。クライアントから頻繁に送り返す双方向用途ならWebSocketが適します。
SSEはHTTP/2で使えますか?
使えます。SSEはHTTP/2上で動作し、むしろHTTP/2の多重化(サーバー・クライアント間でネゴシエートされ既定100程度)が、HTTP/1.1で問題になる「同一ドメイン約6接続」の上限を解消します。HTTP/1.1に固定する必要はなく、HTTP/2配信が推奨です。
SSEのデメリットは何ですか?
HTTP/1.1での同時接続数の上限、テキストのみで双方向通信ができない点、EventSourceがカスタムヘッダーを付けられず認証設計に工夫が要る点です。接続数はHTTP/2、認証はCookieや短命トークンで実務上は回避できます。