このページの目次
WebSocket と SSE
HTTP のリクエスト・レスポンスモデルはリアルタイムシーンには不向き——WebSocket はフルデュプレックスチャネルにアップグレードされ、SSE は単方向ストリームで簡潔さを保ちます。どちらを選ぶかは「サーバーがプッシュを必要とするか」「バイナリフレームを必要とするか」にかかっています。
概要
WebSocket(RFC 6455, 2011)は HTTP ハンドシェイクに基づいて永続的な双方向 TCP チャネルを作成します——ブラウザとサーバーはいつでも互いにメッセージを送信できます。これは HTTP の「リクエスト・レスポンス」という単方向の制限を解決し、リアルタイムアプリケーション(チャット、ゲーム、ライブ配信の弾幕コメント、Grafana Live など)を可能にします。SSE(Server-Sent Events)はよりシンプルな単方向の代替手段です:サーバーはイベントストリームをクライアントにプッシュし、純粋な HTTP に基づいており、ファイアウォールとの互換性がより優れています。両方ともハートビートメカニズム(ping/pong vs SSE の自動再接続)を持っており、NAT/proxy 環境でロングコネクションが切れずに保たれます。
WebSocket アップグレード
Client → Server: GET /ws HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== ← ランダム、base64
Sec-WebSocket-Version: 13
Sec-WebSocket-Protocol: chat ← オプションのサブプロトコル
Server → Client: HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= ← SHA1(Key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
Sec-WebSocket-Protocol: chat
→ ハンドシェイク完了 → TCP 接続が双方向 WebSocket にアップグレード
WebSocket フレーム
0 1 2 3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Masking-key (if MASK set, 4 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Payload Data (可変、クライアントからの場合はマスク済み) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
FIN(1): 1 = これはメッセージの最後のフレーム
opcode(4): 0=continuation, 1=text(UTF-8), 2=binary, 8=close, 9=ping, 10=pong
MASK(1): クライアント→サーバーはマスク必須、サーバー→クライアントはマスク不可
Masking (クライアント → サーバー):
1. クライアント: 4バイトのマスクキーをランダム生成
2. ペイロードの各バイトに対して: payload[i] = original[i] XOR key[i % 4]
3. 目的: 中間プロキシによるキャッシュポイズニングを防ぐ(攻撃者がブラウザに作成された WS ペイロードを送信させ、
→ プロキシが誤って HTTP レスポンスとして解析し、キャッシュを破損させるのを防ぐ)
Close フレーム (opcode=8):
[ステータスコード (2バイト、ビッグエンディアン)][理由 (UTF-8、オプション)]
1000: 正常なクローズ
1001: 終了(サーバーシャットダウン、ページ遷移)
Ping/Pong (ハートビート)
Ping フレーム (opcode=9): オプションのペイロード (アプリケーション固有)
Pong フレーム (opcode=10): ping のペイロードをコピー必須
→ アプリケーション層のハートビート (TCP keepalive とは異なる)
→ 検出: 死んだ接続 (TCP は生きていてもリモートアプリがハングしている場合)
プロキシ/サーバーのタイムアウト: nginx の proxy_read_timeout は WebSocket に適用される →
ping/pong がなければ、接続はプロキシのタイムアウトで切断される可能性がある
SSE (Server-Sent Events)
単方向 (サーバー → クライアント) ストリーム、HTTP chunked encoding に基づく:
HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
id: 42 ← 最後のイベント ID (この ID から自動再接続)
event: alert ← オプションのイベントタイプ (指定しない場合は "message")
data: {"status":"firing"} ← イベントデータ
data: 複数行のデータ
data: 2行目
← 空行 → イベントディスパッチ
クライアント API: EventSource (HTML5 組み込み):
const es = new EventSource("/events");
es.onmessage = (e) => { console.log(e.data); };
es.addEventListener("alert", (e) => { ... });
WebSocket vs SSE
| WebSocket | SSE | |
|---|---|---|
| 方向 | 双方向 | サーバー → クライアント |
| プロトコル | WebSocket (アップグレード) | HTTP chunked |
| バイナリデータ | 標準 (opcode=2) | base64 エンコードが必要 |
| ファイアウォール互換性 | 低い (一部のプロキシはアップグレードをサポートしない) | 高い (純粋な HTTP) |
| 自動再接続 | 手動 | EventSource が自動 (last-event-id から) |
| 適した用途 | チャット、ゲーム、リアルタイム共同作業 | 通知、フィード、リアルタイムデータプッシュ |
参考
- RFC: 6455
- SSE: html.spec.whatwg.org/multipage/server-sent-events.html
- WS テスト: websocat (CLI WebSocket クライアント)
キーワード: WebSocket, アップグレード, フレーム, マスキング, ping/pong, SSE, EventSource, ハートビート