RFC 6455: WebSocket
毎回HTTPヘッダを往復させるロングポーリングの無駄を、1本の持続TCP接続と最小2バイトのフレームに置き換え、全二重の双方向通信を実現します。ハンドシェイクからマスキング必須の理由までRFC 6455を原理から読み解きます。
- WebSocketはHTTP Upgradeで接続した後、独自フレームを使う全二重通信へ切り替わる。Accept値でハンドシェイクを確認する。
- フレームはFIN、opcode、長さ、payloadで構成される。クライアント送信はプロキシ汚染を防ぐためマスクが必須。
- データに加えてping・pong・closeの制御フレームを持つ。終了時は双方がcloseを交換し、状態コードで理由を伝える。
このRFCが決めたこと
RFC 6455 は、1 本の TCP 接続の上で全二重・双方向・低オーバーヘッドのメッセージ通信を可能にする WebSocket プロトコルを 2011 年に標準化しました。ブラウザとサーバーが、どちらからでも好きなタイミングでメッセージを送り合えます。
それ以前、Web でサーバー起点の更新を届ける定番は HTTP ロングポーリングでした。クライアントが要求を送り、サーバーは応答を保留して更新が起きるまで待ち、返したらクライアントはすぐ次の要求を張り直す、という擬似プッシュです。しかしこの方式は、メッセージのたびにフルの HTTP ヘッダが往復し、接続の張り直しにも隙間が生まれます。
| 観点 | HTTPロングポーリング | WebSocket |
|---|---|---|
| 接続 | メッセージ毎にHTTP要求を張り直す | 1本のTCP接続を維持する |
| 方向 | 実質は要求応答の片方向的な擬似プッシュ | 全二重の双方向 |
| オーバーヘッド | 毎回フルのHTTPヘッダが往復する | フレームヘッダは最小2バイト |
| サーバープッシュ | 応答を保留して疑似的に実現 | 確立後はいつでも即時に送信可 |
しかも WebSocket は既存の HTTP と同じポート 80/443 を使うため、ファイアウォールやプロキシを素直に通過できます。ブラウザ API としての使い方は /web/websocket/ を参照してください。
アップグレードハンドシェイク
横にスクロール
接続は普通の HTTP GET で始まります。クライアントは「この接続を WebSocket に格上げしたい」という意思を、いくつかのヘッダで伝えます。
(client → server)
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
(server → client)
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Key はクライアントが生成した 16 バイトのランダム値を base64 した文字列です。サーバーはこれに固定の GUID 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 を文字列連結し、SHA-1 でハッシュした結果を base64 して Sec-WebSocket-Accept に入れて返します。クライアントは自分が送った鍵から同じ計算を再現し、値の一致を確かめます。
この計算は暗号でも認証でもありません。狙いは、相手が本当に WebSocket を理解して 101 を返したという確認です。GUID を知らないキャッシュや、たまたま Upgrade ヘッダを無視する古いサーバーが、偶発的にハンドシェイクを成立させてしまう事故を防ぎます。
この鍵は乱数の往復確認にすぎず、盗聴や改ざんへの防御は一切与えません。通信そのものを保護したいなら、TLS の上で動かす wss:// を使います。ws:// は平文で、http:// と同じ扱いだと考えてください。
フレーム構造とマスキング
101 を返した瞬間から、その接続は 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| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Masking-key(MASK=1 のとき 4 バイト) |
+---------------------------------------------------------------+
| Payload Data ... |
+---------------------------------------------------------------+
先頭ビットの FIN はメッセージの最終フレームかどうかを示し、これにより 1 メッセージを複数フレームへ分割(フラグメント)できます。続く RSV1〜3 は拡張用の予約ビットです。opcode(4 ビット)はフレームの種別で、0x1 がテキスト、0x2 がバイナリ、0x0 が継続、0x8 が close、0x9 が ping、0xA が pong を表します。
ペイロード長は可変長にエンコードされます。まず 7 ビットで表現し、それに収まらなければ拡張長へ逃がします。
| 7ビットの値 | 実際の長さの読み方 | 扱える範囲 |
|---|---|---|
| 0〜125 | その値がそのまま長さ | 〜125バイト |
| 126 | 続く2バイト(16ビット)を長さとする | 126〜65535バイト |
| 127 | 続く8バイト(64ビット)を長さとする | 最大 2^63-1 バイト |
MASK ビットが立っていれば、ペイロードの直前に 4 バイトのマスキング鍵が入ります。マスキングは、ペイロードの各バイトを鍵の 4 バイトと順に XOR する単純な変換です(j 番目のバイトは鍵の j mod 4 番目と XOR する)。そして クライアントからサーバーへ送るフレームは、この 32 ビット鍵によるマスキングが必須 です。逆にサーバーからクライアントへ送るフレームはマスクしてはいけません。
つまずきやすい点
マスキングは秘匿のためではありません。 目的はプロキシのキャッシュ汚染攻撃を防ぐことです。もしブラウザ上の悪意あるスクリプトがワイヤ上に出るバイト列を自由に決められると、WebSocket を理解しない透過型プロキシに対して、それがあたかも別の HTTP 要求であるかのように見せかけ、汚染した応答をキャッシュさせられる恐れがあります。フレームごとに予測不能な鍵で XOR することで、スクリプト側はワイヤ上のバイト列を制御できなくなり、この攻撃が成立しなくなります。秘匿性はまったく別の話で、それは wss:// が担います。
制御フレームには強い制約があります。 ping・pong・close はいずれも分割禁止(FIN=1 必須)で、ペイロードは 125 バイト以下でなければなりません。ping を受け取った側は、原則として同じアプリケーションデータを載せた pong を返します。これは接続の生存確認(キープアライブ)に利用できます。
クローズは握手です。 一方が close フレームを送ると、相手も close を返し、その後で TCP を閉じます。close のペイロードには、先頭 2 バイトのビッグエンディアンでステータスコードを載せられます。
| コード | 意味 | 備考 |
|---|---|---|
| 1000 | 正常終了 | 通常のクローズ |
| 1001 | エンドポイント退場 | ページ遷移やサーバー停止 |
| 1002 | プロトコルエラー | 不正なフレームを受信した |
| 1007 | 不正なペイロードデータ | テキストなのに非UTF-8など |
| 1009 | メッセージ過大 | 受信側の上限を超えた |
またテキストフレームのペイロードは正しい UTF-8 でなければならず、違反を受け取った側は 1007 で接続を閉じます。バイナリフレームにはこの制約がなく、任意のバイト列を運べます。
まとめ
WebSocket は、HTTP の握手で入口を借りて 1 本の TCP 接続を確立し、その後は最小 2 バイトのフレームで全二重に会話するプロトコルです。要点は三つ。(1) Sec-WebSocket-Accept は SHA-1 と固定 GUID による「解したことの確認」であって暗号ではないこと、(2) フレームは FIN・opcode・可変長ペイロード長で構成されること、(3) クライアントからサーバーへのマスキングはキャッシュ汚染対策であって秘匿ではないこと。土台となる TCP は /network/tcp-udp/ を、入口となった HTTP 側の背景は /network/http-versions/ を合わせて読むと像が結びます。他の RFC 精読は /rfc/ から辿れます。
RFC精読の記事ガイド
RFC 6455: WebSocketを実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
RFC
比較で見る軸
難易度: advanced / カテゴリ: RFC精読 / タグ数: 5
導入後に効く点
フレームはFIN、opcode、長さ、payloadで構成される。クライアント送信はプロキシ汚染を防ぐためマスクが必須。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- RFC精読
- タグ数
- 5
判断チェックリスト
- 自社の用途が「RFC / WebSocket」に近いか確認する。
- 強みである「WebSocketはHTTP Upgradeで接続した後、独自フレームを使う全二重通信へ切り替わる。Accept値でハンドシェイクを確認する。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。