WebSocket プロトコル:HTTP からの昇格と全二重通信
ポーリングをやめて双方向のリアルタイム通信を実現できます。HTTP からの昇格ハンドシェイク、フレーム構造とマスキング、ping/pong とクローズまでを原理から理解できます。
- 1.WebSocket は HTTP の Upgrade ハンドシェイクで TCP 接続を昇格させ、以降は HTTP を離れた独自フレームで全二重通信を行う。
- 2.Sec-WebSocket-Key と固定 GUID の SHA-1 を Base64 化した Accept で握手の正当性を確認し、ブラウザ発フレームは必ずマスキングされる。
- 3.制御フレーム(ping/pong/close)で死活監視と秩序ある切断を行うが、HTTP/2 多重化とは目的が異なり競合しない。
なぜ HTTP を「昇格」させるのか
/network/http/ は本来「要求 1 つに応答 1 つ」の片方向・要求駆動モデルで、サーバーから能動的にデータを押し出せません。チャットや株価のようなサーバー起点の更新を扱うため、かつてはポーリングや Long Polling で擬似的に双方向化していましたが、毎回 HTTP ヘッダを往復させる無駄と遅延が避けられませんでした。
WebSocket(RFC 6455)は、この問題を「既存の TCP 接続を別プロトコルへ昇格させる」ことで解決します。最初の 1 往復だけ HTTP を使い、その後は HTTP を完全に離れて、1 本の TCP コネクション上で双方向に自由なタイミングでメッセージを送り合う全二重通信へ切り替えます。HTTP のポート(80/443)と既存インフラをそのまま流用できる点が普及の決め手でした。
Upgrade ハンドシェイク
握手はクライアントからの通常の HTTP GET で始まります。鍵となるのは Upgrade と Connection ヘッダ、そして Sec-WebSocket-Key です。
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
サーバーが受諾すると、通常の 200 ではなく 101 Switching Protocols を返します。これが「以降はこの TCP を WebSocket として使う」という合図です。
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Key の検証ロジック
Sec-WebSocket-Key は認証やセキュリティのための値ではありません。目的は、相手が WebSocket を正しく理解しているサーバーか、あるいはハンドシェイクを誤って中継したキャッシュ・プロキシではないかを確かめることです。検証は決定的な手順で行われます。
accept = Base64( SHA-1( key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" ) )
クライアントが送った 16 バイトのランダム値を Base64 化した key に、RFC で固定された マジック GUID 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 を文字列連結し、SHA-1 ハッシュを取って Base64 でエンコードしたものが Sec-WebSocket-Accept です。クライアントは自分の key から同じ計算をして一致を確認します。
GUID は秘密ではなく公開された定数です。狙いは「WebSocket を知らないサーバーや中間装置が、たまたまこの応答を生成してしまう確率をゼロにする」こと。鍵をそのまま返すだけでは偶然の一致や悪意ある反射が起こり得るため、両者しか実行しない決まった変換を挟むことで、握手相手が確かに RFC 6455 を実装していると保証します。暗号学的強度は不要なので SHA-1 で十分です。
フレーム構造
握手後のデータは HTTP メッセージではなく、軽量な WebSocket フレームで運ばれます。最小ヘッダは 2 バイトです。
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+- - - - - - - - - - - - - - - -+
| Masking-key (マスクありの場合 4 バイト) | Payload ...
+---------------------------------------------------------------+
要点は次の通りです。
- FIN ビット: このフレームがメッセージの最終断片かを示す。1 メッセージを複数フレームに分割(フラグメンテーション)でき、その場合は最後だけ FIN=1。
- opcode: フレーム種別。
0x1=テキスト、0x2=バイナリ、0x0=継続、0x8=Close、0x9=Ping、0xA=Pong。 - Payload len: 7 ビットで表し、126 なら続く 2 バイト、127 なら続く 8 バイトを実長とする可変長方式。小さなメッセージのヘッダオーバーヘッドを抑える設計です。
- MASK ビットとマスキングキー: ペイロードがマスクされているかと、その XOR 鍵。
| opcode | 種別 | 用途 | 分類 |
|---|---|---|---|
| 0x0 | Continuation | 分割メッセージの継続断片 | データ |
| 0x1 | Text | UTF-8 テキスト | データ |
| 0x2 | Binary | 任意のバイナリ | データ |
| 0x8 | Close | クローズ要求と理由コード | 制御 |
| 0x9 | Ping | 死活確認の問い合わせ | 制御 |
| 0xA | Pong | Ping への応答 | 制御 |
マスキングとその目的
クライアントからサーバーへ送るフレームは必ずマスクしなければならない(MASK=1)規則があります。送信側は 4 バイトのランダムなマスキングキーを選び、ペイロードの各バイトを payload[i] XOR mask[i mod 4] で変換します。逆にサーバーからクライアントへのフレームはマスクしてはいけません。
masked[i] = payload[i] XOR mask[i % 4]
この一見奇妙な規則の目的は キャッシュ・ポイズニング攻撃の防止です。悪意あるスクリプトがブラウザに WebSocket フレームを送らせ、その中身を「HTTP リクエストに見える文字列」に偽装すると、WebSocket を理解しない透過プロキシがそれを HTTP として解釈・キャッシュしてしまう恐れがあります。フレームごとに変わるランダムキーでペイロードを攪拌することで、攻撃者が中継装置に通る固定バイト列を予測・注入できなくなります。
マスクキーはフレーム内に平文で同梱されるため、盗聴者は即座に復号できます。マスキングは機密性の手段ではなく中間装置の誤解釈を防ぐ仕組みです。通信の機密性は /network/tls/ による TLS(wss://)で確保します。本番では wss:// を使うのが原則です。
ping/pong とクローズ
接続は確立後ずっと開いたままなので、相手やネットワークが静かに死んでいないかを確認する手段が要ります。これが制御フレームの役割です。
- Ping(0x9)/ Pong(0xA): どちらの側からでも Ping を送れ、受信側は同じペイロードを Pong で速やかに返さねばならない。応答が来なければ接続断と判断する。NAT//network/http/ 経路のアイドルタイムアウトでセッションが切られるのを防ぐキープアライブにも使う。
- Close(0x8): 秩序ある切断のためのハンドシェイク。クローズを始める側が Close フレーム(2 バイトのステータスコード、例
1000=正常終了、1001=離脱)を送り、相手も Close を返してから双方が TCP を閉じる。
Close を交換してから閉じることで、送信途中のデータ欠落と TCP の TIME_WAIT 周りの曖昧さを避けます。TCP 切断そのものの挙動は /network/tcp-udp/ を参照すると理解が深まります。
頻出は 3 点。(1) 昇格応答は 101 Switching Protocols。(2) Accept = Base64(SHA-1(Key + 固定GUID))。(3) クライアント発フレームは必ずマスク、サーバー発は禁止——その目的はプロキシのキャッシュポイズニング防止。マスキングを「暗号化」と書くと誤りです。
HTTP/2 多重化との関係
「HTTP/2 が 1 接続で多重化できるなら WebSocket は不要では」という誤解がありますが、両者は目的が異なり競合しません。HTTP/2 の多重化は依然として要求・応答モデルの上にあり、サーバーが任意のタイミングでクライアントへ能動的にメッセージを押し出す全二重チャネルではありません(Server Push も応答に紐づく限定的な仕組みで、現在は非推奨化が進んでいます)。
一方で、両者は協調もできます。RFC 8441 は HTTP/2 の単一ストリーム上で CONNECT メソッドを拡張して WebSocket を確立する Bootstrapping WebSockets over HTTP/2 を定めており、HTTP/2 接続を共有しつつ、その中の 1 ストリームを全二重の WebSocket として使えます。
| 観点 | HTTP/2 多重化 | WebSocket |
|---|---|---|
| 通信モデル | 要求・応答が基本 | 対称な全二重 |
| サーバー起点送信 | 限定的(Push は非推奨化) | 任意のタイミングで可能 |
| フレーム | HTTP/2 フレーム(HPACK 圧縮) | WebSocket フレーム(マスク付き) |
| 想定用途 | ページ資源の効率配信 | チャット・ゲーム・実時間更新 |
まとめ
WebSocket の本質は「HTTP の握手だけ借りて TCP を奪い、以降は対称な全二重プロトコルに化ける」点にあります。101 昇格と固定 GUID を使った Accept 検証で握手の正当性を担保し、軽量フレームと必須マスキングで安全に双方向データを流し、ping/pong と Close で接続を健全に保つ。HTTP/2 や HTTP/3 とは置き換えではなく役割分担の関係にあり、リアルタイム双方向通信の標準的な土台であり続けています。
ネットワーク Article
WebSocket プロトコル:HTTP からの昇格と全二重通信を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
WebSocket
比較で見る軸
難易度: advanced / カテゴリ: ネットワーク / タグ数: 5
導入後に効く点
Sec-WebSocket-Key と固定 GUID の SHA-1 を Base64 化した Accept で握手の正当性を確認し、ブラウザ発フレームは必ずマスキングされる。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- ネットワーク
- タグ数
- 5
判断チェックリスト
- 自社の用途が「WebSocket / HTTP」に近いか確認する。
- 強みである「WebSocket は HTTP の Upgrade ハンドシェイクで TCP 接続を昇格させ、以降は HTTP を離れた独自フレームで全二重通信を行う。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。