WebSocketプロトコル実装ガイド (RFC 6455)
このドキュメントは、RFC 6455の詳細な技術ガイドおよび実装リファレンスであり、プロトコルの説明、コード例、ベストプラクティスを含んでいます。RFC公式章の翻訳については、各章のドキュメントを参照してください。
RFC 6455 - WebSocketプロトコル
Internet Engineering Task Force (IETF)
Request for Comments: 6455
カテゴリ: Standards Track
著者:
I. Fette (Google Inc.)
A. Melnikov (Isode Ltd.)
発行日: 2011年12月
このメモランダムのステータス
これはInternet Standards Trackドキュメントです。
このドキュメントは、Internet Engineering Task Force (IETF) の成果物であり、IETFコミュニティのコンセンサスを表しています。
著作権表示
Copyright (c) 2011 IETF Trust and the persons identified as the document authors. All rights reserved.
概要
WebSocketプロトコル (WebSocket Protocol) は、単一のTCP接続上でクライアントとサーバー間の全二重通信 (Full-Duplex Communication) を可能にします。WebSocketプロトコルは、WebブラウザおよびWebサーバーでの実装を目的として設計されていますが、任意のクライアントまたはサーバーアプリケーションで使用できます。
WebSocketプロトコルは、TCPベースの独立したプロトコルです。HTTPとの唯一の関係は、そのハンドシェイク (Handshake) がHTTPサーバーによってアップグレードリクエスト (Upgrade Request) として解釈されることです。
設計により、WebSocketプロトコルは既存のHTTPインフラストラクチャ上で動作することを目指しているため、HTTPポート80および443を使用し、HTTPプロキシおよび中間装置をサポートします。これは、ある程度の複雑さを意味する場合でもそうです。
目次
- 1. 導入 (Introduction)
- 2. 適合性要件 (Conformance Requirements)
- 3. WebSocket URI
- 4. オープニングハンドシェイク (Opening Handshake)
- 5. データフレーミング (Data Framing)
- 6. データの送受信 (Sending and Receiving Data)
- 7. 接続のクローズ (Closing the Connection)
- 8. エラー処理 (Error Handling)
- 9. 拡張 (Extensions)
- 10. セキュリティに関する考慮事項 (Security Considerations)
- 11. IANAに関する考慮事項 (IANA Considerations)
- 12. 他の仕様からのWebSocketプロトコルの使用
- 13. 謝辞
- 14. 参考文献
WebSocketコア用語集
基本概念
| 英語用語 | 日本語訳 | 説明 |
|---|---|---|
| WebSocket Protocol | WebSocketプロトコル | 単一のTCP接続上で全二重通信を可能にするプロトコル |
| Full-Duplex Communication | 全二重通信 | 双方向同時通信 |
| Opening Handshake | オープニングハンドシェイク | WebSocket接続を確立するためのHTTPアップグレードプロセス |
| Closing Handshake | クロージングハンドシェイク | WebSocket接続を正常にクローズするためのプロセス |
| Frame | フレーム | WebSocketデータ転送の基本単位 |
| Message | メッセージ | 1つ以上のフレームで構成される完全なアプリケーションデータ |
| Client | クライアント | WebSocket接続を開始する側 (通常はブラウザ) |
| Server | サーバー | WebSocket接続を受け入れる側 |
| Endpoint | エンドポイント | クライアントまたはサーバー |
| Connection | 接続 | クライアントとサーバー間のWebSocket接続 |
ハンドシェイク関連
| 英語用語 | 日本語訳 | 説明 |
|---|---|---|
| Upgrade Request | アップグレードリクエスト | WebSocketへのHTTPアップグレードリクエスト |
| Sec-WebSocket-Key | WebSocketキー | クライアントが送信するランダムキー |
| Sec-WebSocket-Accept | WebSocket受け入れ | サーバーが返す検証キー |
| Sec-WebSocket-Protocol | WebSocketサブプロトコル | 交渉されたアプリケーション層サブプロトコル |
| Sec-WebSocket-Extensions | WebSocket拡張 | 交渉されたプロトコル拡張 |
| Sec-WebSocket-Version | WebSocketバージョン | プロトコルバージョン番号 (現在は13) |
| Origin | オリジン | 接続を開始するWebページの発信元 |
フレーム構造関連
| 英語用語 | 日本語訳 | 説明 |
|---|---|---|
| FIN | FIN / 終了ビット | これがメッセージの最後のフレームであることを示す |
| RSV | RSV / 予約ビット | 拡張用に予約されたフラグビット |
| Opcode | オペコード / 操作コード | フレームのタイプを定義 |
| Mask | マスク | クライアントからサーバーへのデータはマスク必須 |
| Masking-key | マスキングキー | データマスキング用の32ビットキー |
| Payload Length | ペイロード長 | データ長 |
| Payload Data | ペイロードデータ | 実際に転送されるデータ |
| Extension Data | 拡張データ | 拡張によって交渉された追加データ |
| Application Data | アプリケーションデータ | アプリケーション層の実際のデータ |
フレームタイプ
| 英語用語 | 日本語訳 | オペコード | 説明 |
|---|---|---|---|
| Continuation Frame | 継続フレーム | 0x0 | メッセージの後続フレーム |
| Text Frame | テキストフレーム | 0x1 | UTF-8エンコードされたテキストデータ |
| Binary Frame | バイナリフレーム | 0x2 | バイナリデータ |
| Close Frame | クローズフレーム | 0x8 | 接続クローズ用制御フレーム |
| Ping Frame | Pingフレーム | 0x9 | ハートビート検出リクエスト |
| Pong Frame | Pongフレーム | 0xA | ハートビート検出レスポンス |
| Control Frame | 制御フレーム | 0x8-0xF | 接続制御用フレーム |
| Data Frame | データフレーム | 0x1-0x2 | アプリケーションデータ転送用フレーム |
クローズ関連
| 英語用語 | 日本語訳 | 説明 |
|---|---|---|
| Close Code | クローズコード | 接続クローズの理由を示す数値コード |
| Close Reason | クローズ理由 | クローズのテキスト説明 |
| Normal Closure | 正常クローズ | コード1000、目的達成後の正常なクローズ |
| Going Away | 離脱 | コード1001、エンドポイントが離脱 (ページナビゲーションなど) |
| Protocol Error | プロトコルエラー | コード1002、プロトコル違反 |
| Unsupported Data | サポートされないデータ | コード1003、受け入れられないデータタイプを受信 |
| Invalid Frame Payload Data | 無効なフレームペイロードデータ | コード1007、データの不整合 |
| Policy Violation | ポリシー違反 | コード1008、ポリシーの違反 |
| Message Too Big | メッセージが大きすぎる | コード1009、処理するには大きすぎるメッセージ |
| TLS Handshake Failure | TLSハンドシェイク失敗 | コード1015、TLSハンドシェイク失敗 |
ドキュメント構造の説明
セクション1: 導入と概要
- WebSocketの背景とニーズ
- プロトコルの設計目標と原則
- HTTP/TCPとの関係
- セキュリティモデルと設計哲学
セクション2-3: 基本要件
- 適合性要件と用語定義
- WebSocket URIフォーマット (ws:// および wss://)
セクション4: オープニングハンドシェイク (コア)
- クライアントがハンドシェイクを開始する方法
- サーバーがハンドシェイクに応答する方法
- サブプロトコルと拡張の交渉
- 複数バージョンのサポート
セクション5: データフレーミング (コア)
- フレーム構造の詳細説明
- マスキングメカニズム
- 断片化 (大きなメッセージを複数のフレームで転送)
- 制御フレームとデータフレーム
セクション6-7: データ転送と接続クローズ
- メッセージの送受信ルール
- 正常なクローズフロー
- 例外処理
- クローズステータスコード
セクション8-10: 拡張、エラー、およびセキュリティ
- 拡張メカニズム
- エラー処理
- 詳細なセキュリティ考慮事項
WebSocket接続確立フロー
1. クライアントがHTTPアップグレードリクエストを開始
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
2. サーバーがアップグレードで応答
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat
3. 接続確立、双方向通信開始
Client ←→ Server
↓ ↓
Text/Binary Frames
Ping/Pong Frames
Close Frame
4. 接続をクローズ
Client → Server: Close Frame (Code: 1000)
Server → Client: Close Frame (Code: 1000)
TCP接続をクローズ
ハンドシェイクキー計算
Sec-WebSocket-Acceptの計算方法
// 1. クライアントがランダムなSec-WebSocket-Keyを生成 (Base64エンコードされた16バイトの乱数)
const clientKey = "dGhlIHNhbXBsZSBub25jZQ==";
// 2. マジック文字列 (RFC 6455で定義された固定GUID)
const magicString = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
// 3. 連結してSHA-1ハッシュを計算
const concatenated = clientKey + magicString;
const hash = SHA1(concatenated);
// 4. Base64エンコードでSec-WebSocket-Acceptを取得
const serverAccept = Base64Encode(hash);
// 結果: "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="
このメカニズムは以下を保証します:
- サーバーがWebSocketプロトコルを理解している (通常のHTTPサーバーではない)
- キャッシングプロキシが誤った応答を返すのを防ぐ
- 基本的なハンドシェイク検証を提供
WebSocketフレーム構造の詳細
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| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+
フィールドの説明
FIN (1ビット):
- 0 = さらにフレームが続く
- 1 = これがメッセージの最後 (または唯一) のフレーム
RSV1, RSV2, RSV3 (各1ビット):
- 拡張用に予約
- 拡張が交渉されていない場合は0でなければならない
Opcode (4ビット):
- 0x0 = Continuation (継続フレーム)
- 0x1 = Text (テキストフレーム、UTF-8)
- 0x2 = Binary (バイナリフレーム)
- 0x8 = Close (クローズ)
- 0x9 = Ping
- 0xA = Pong
- 0x3-0x7, 0xB-0xF = 予約済み
MASK (1ビット):
- クライアントからサーバーへ: 1でなければならない
- サーバーからクライアントへ: 0でなければならない
Payload Length (7ビット、7+16ビット、または7+64ビット):
- 0-125: 実際の長さ
- 126: 次の16ビットが実際の長さ
- 127: 次の64ビットが実際の長さ
Masking-key (0または4バイト):
- MASK=1の場合、32ビットマスキングキーを含む
Payload Data:
- Extension data + Application data
マスキングメカニズムの説明
なぜマスキングが必要なのか?
セキュリティ上の理由: キャッシュポイズニング攻撃を防ぐため。一部の中間プロキシがWebSocketフレームを誤ってキャッシュする可能性があり、マスキングによりデータが予測不可能であることを保証します。
マスキングアルゴリズム
// クライアントがデータを送信する際
function maskData(data, maskingKey) {
const masked = new Uint8Array(data.length);
for (let i = 0; i < data.length; i++) {
masked[i] = data[i] ^ maskingKey[i % 4];
}
return masked;
}
// サーバーがデータを受信する際 (デコード用に同じアルゴリズムを使用)
function unmaskData(maskedData, maskingKey) {
return maskData(maskedData, maskingKey); // XORは自己逆演算
}
重要なポイント:
- XOR演算 (^) を使用
- キーの長さは4バイト
- キーを循環的に使用
- クライアント→サーバー: マスクが必須
- サーバー→クライアント: マスク禁止
メッセージの断片化 (Fragmentation)
大きなメッセージは複数のフレームで送信できます:
例: 大きなテキストメッセージの送信
Frame 1: FIN=0, Opcode=0x1 (Text), Payload="Hello "
Frame 2: FIN=0, Opcode=0x0 (Continuation), Payload="World"
Frame 3: FIN=1, Opcode=0x0 (Continuation), Payload="!"
最終メッセージ: "Hello World!"
ルール:
- 最初のフレーム: FIN=0, Opcode=データタイプ (0x1または0x2)
- 中間のフレーム: FIN=0, Opcode=0x0 (Continuation)
- 最後のフレーム: FIN=1, Opcode=0x0 (Continuation)
- 制御フレームは断片化できないし、断片化されたデータフレームの間に挿入できる
制御フレームの詳細
Closeフレーム (0x8)
構造:
+--------+--------+------------------+
| Code | Reason |
| (2バイト) | (UTF-8テキスト) |
+--------+--------+------------------+
例:
Close Code: 1000 (正常クローズ)
Close Reason: "Going away"
よく使用されるクローズコード:
- 1000: Normal Closure (正常クローズ)
- 1001: Going Away (エンドポイントが離脱、ページナビゲーションなど)
- 1002: Protocol Error (プロトコルエラー)
- 1003: Unsupported Data (サポートされないデータタイプ)
- 1006: Abnormal Closure (異常クローズ、Closeフレーム未送信)
- 1009: Message Too Big (メッセージが大きすぎる)
- 1011: Internal Server Error (サーバー内部エラー)
Pingフレーム (0x9)
- クライアントまたはサーバーから送信可能
- ハートビート検出に使用 (接続をアクティブに保つ)
- アプリケーションデータを運ぶことができる (最大125バイト)
- 受信者は必ずPongフレームで応答しなければならない
Pongフレーム (0xA)
- Pingフレームへの応答に使用
- Pingフレームと同じペイロードを含まなければならない
- 能動的に送信することもできる (単方向ハートビート)
クローズフロー
正常クローズ (Clean Close)
1. イニシエーターがCloseフレームを送信
Client → Server: Close Frame (Code: 1000)
2. 受信者がCloseフレームで応答
Server → Client: Close Frame (Code: 1000)
3. イニシエーターがTCP接続をクローズ
Client: TCP接続をクローズ
4. 受信者もTCP接続をクローズ
Server: TCP接続をクローズ
異常クローズ
- TCP接続が突然切断 (Closeフレームなし)
- 無効なフレームデータを受信
- 応答なしでタイムアウト
- プロトコルルール違反
ステータスコード: 1006 (Abnormal Closure) - このコードはCloseフレームで送信されず、報告のみに使用
WebSocket URIフォーマット
ws:// (非暗号化)
ws://example.com/socket
ws://example.com:8080/chat
ws://192.168.1.1/data
- デフォルトポート: 80
- http:// のセキュリティレベルと同等
wss:// (TLS暗号化)
wss://example.com/socket
wss://secure.example.com:443/chat
- デフォルトポート: 443
- https:// のセキュリティレベルと同等
- 本番環境では wss:// の使用を強く推奨
サブプロトコル (Subprotocol)
WebSocketはトランスポート層プロトコルであり、サブプロトコルはアプリケーション層のメッセージフォーマットを定義します:
クライアントリクエスト:
Sec-WebSocket-Protocol: chat, superchat
サーバーレスポンス:
Sec-WebSocket-Protocol: chat
確立された接続は"chat"サブプロトコルを使用
一般的なサブプロトコル:
- STOMP: Simple Text Oriented Messaging Protocol
- MQTT: Message Queuing Telemetry Transport
- WAMP: Web Application Messaging Protocol
- カスタムアプリケーションプロトコル
拡張 (Extensions)
拡張はWebSocket機能を強化できます:
クライアントリクエスト:
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
サーバーレスポンス:
Sec-WebSocket-Extensions: permessage-deflate
よく使用される拡張:
- permessage-deflate: メッセージ圧縮 (RFC 7692)
- permessage-bzip2: Bzip2圧縮
- カスタム拡張
セキュリティ考慮事項の要点
1. オリジン検証 (Origin Validation)
// サーバー側のオリジン検証
const allowedOrigins = ['https://example.com', 'https://app.example.com'];
const origin = request.headers['origin'];
if (!allowedOrigins.includes(origin)) {
// 接続を拒否
response.status(403).send('Forbidden');
}
2. TLS暗号化
- 本番環境では wss:// を使用しなければならない
- 中間者攻撃を防ぐ
- データの機密性と完全性を保護
3. 認証と認可
方法1: ハンドシェイク時にCookieを介して認証
GET /socket HTTP/1.1
Cookie: session=abc123
方法2: サブプロトコルを介してトークンを渡す
ws://example.com/socket?token=jwt_token
方法3: 接続後の最初のメッセージで認証情報を運ぶ
4. レート制限
- 接続数を制限 (DoS防止)
- メッセージサイズを制限
- メッセージ頻度を制限
5. 入力検証
- UTF-8エンコーディングを検証 (テキストフレーム)
- メッセージフォーマットを検証
- インジェクション攻撃を防ぐ
実装のベストプラクティス
クライアント (ブラウザ)
// 1. WebSocket接続を作成
const ws = new WebSocket('wss://example.com/socket', ['chat']);
// 2. イベントリスナーを登録
ws.addEventListener('open', (event) => {
console.log('接続が確立されました');
ws.send('Hello Server!');
});
ws.addEventListener('message', (event) => {
console.log('メッセージを受信:', event.data);
});
ws.addEventListener('error', (event) => {
console.error('WebSocketエラー:', event);
});
ws.addEventListener('close', (event) => {
console.log('接続がクローズされました', event.code, event.reason);
// 再接続ロジックを実装
if (event.code !== 1000) {
setTimeout(() => reconnect(), 5000);
}
});
// 3. データを送信
ws.send('テキストメッセージ');
ws.send(new Blob(['バイナリデータ']));
ws.send(new ArrayBuffer(8));
// 4. 接続をクローズ
ws.close(1000, '正常クローズ');
サーバー側 (Node.js例)
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', (ws, request) => {
// オリジンを検証
const origin = request.headers.origin;
// ...検証ロジック
// ハートビート検出
ws.isAlive = true;
ws.on('pong', () => { ws.isAlive = true; });
// メッセージを受信
ws.on('message', (data) => {
console.log('受信:', data);
// 全クライアントにブロードキャスト
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(data);
}
});
});
// エラー処理
ws.on('error', (error) => {
console.error('エラー:', error);
});
// クローズ
ws.on('close', (code, reason) => {
console.log('接続がクローズされました:', code, reason);
});
});
// ハートビート検出
const interval = setInterval(() => {
wss.clients.forEach((ws) => {
if (ws.isAlive === false) {
return ws.terminate();
}
ws.isAlive = false;
ws.ping();
});
}, 30000);
一般的な適用シナリオ
1. リアルタイムチャットアプリケーション
- インスタントメッセージング
- オンラインステータス表示
- タイピングインジケーター
2. リアルタイムコラボレーションツール
- ドキュメント共同編集 (Google Docsなど)
- ホワイトボード共有
- コラボレーティブコードエディタ
3. リアルタイムデータストリーム
- 株価
- スポーツスコア
- IoTデータ
4. オンラインゲーム
- マルチプレイヤーオンラインゲーム
- リアルタイムゲーム状態同期
- 低遅延インタラクション
5. プッシュ通知
- ブラウザプッシュ
- リアルタイムアラート
- システム監視
他の技術との比較
WebSocket vs HTTP Long Polling
| 特性 | WebSocket | Long Polling |
|---|---|---|
| 接続 | 永続的な接続 | 繰り返しHTTPリクエスト |
| レイテンシ | 非常に低い | 高い (HTTPオーバーヘッド) |
| 双方向 | 真の双方向 | 疑似双方向 |
| リソース | 低 (1つの接続) | 高 (複数の接続) |
| 複雑さ | 中程度 | より単純 |
WebSocket vs Server-Sent Events (SSE)
| 特性 | WebSocket | SSE |
|---|---|---|
| 双方向通信 | ✅ 双方向 | ❌ サーバー→クライアントのみ |
| データフォーマット | バイナリまたはテキスト | テキスト (UTF-8) |
| プロトコル | 独立したプロトコル | HTTPベース |
| ブラウザサポート | 広くサポート | 広くサポート (IEを除く) |
| 再接続 | 手動実装 | 自動再接続 |
デバッグツール
ブラウザ開発者ツール
- Chrome DevTools → Network → WS
- フレームの送受信を表示
- 接続状態を表示
コマンドラインツール
# wscat (Node.js)
npm install -g wscat
wscat -c ws://echo.websocket.org
# websocat (Rust)
websocat ws://echo.websocket.org
オンラインテストツール
- websocket.org Echo Test
- Hoppscotch (旧Postwoman)
- Firecamp
リファレンスリソース
関連RFC標準
- RFC 6455: WebSocketプロトコル (このドキュメント)
- RFC 7692: WebSocket圧縮拡張
- RFC 8441: HTTP/2上のWebSocketブートストラッピング
- RFC 8307: WebSocket用Well-Known URI
推奨実装ライブラリ
JavaScript (ブラウザ):
- ネイティブWebSocket API
Node.js:
- ws (軽量)
- Socket.IO (機能豊富、自動フォールバック)
- uWebSockets.js (高性能)
Python:
- websockets (asyncio)
- ws4py
- Tornado
Java:
- Java WebSocket API (JSR 356)
- Netty
- Spring WebSocket
Go:
- gorilla/websocket
- nhooyr/websocket
次のステップの学習提案
- 基礎: WebSocketハンドシェイクとフレーム構造を理解
- 実践: シンプルなチャットアプリケーションを実装
- 応用: サブプロトコルと拡張を学習
- 最適化: ハートビート、再接続、エラー処理を実装
- セキュリティ: セキュリティ考慮事項とベストプラクティスを深く理解
まとめ
WebSocketプロトコルはWebリアルタイム通信の基盤です:
- 正確性: 双方向通信の正確な実装
- 伝達性: 明確なプロトコル仕様
- 優雅性: エレガントな設計と実装
コアバリュー:
- 🔄 真の全二重通信
- ⚡ 低遅延リアルタイムデータ転送
- 📦 軽量フレームプロトコル
- 🔐 組み込みセキュリティメカニズム
最適な使用シナリオ:
- リアルタイムチャット
- コラボレーティブ編集
- リアルタイムゲーム
- リアルタイムデータストリーム
- プッシュ通知
関連RFC:
- RFC 6455: WebSocketプロトコル (このドキュメント)
- RFC 7692: WebSocket圧縮拡張
- RFC 8441: HTTP/2上のWebSocketブートストラッピング