WebSocket (RFC 6455) の詳細

Tech

本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。

WebSocket (RFC 6455) の詳細

背景

Webアプリケーションの高度化に伴い、従来のHTTP/1.1のステートレスなリクエスト/レスポンスモデルでは、リアルタイム性の高い双方向通信を実現する上で多くの課題が生じました。例えば、サーバーからのプッシュ通知にはロングポーリングやCometといった手法が用いられましたが、これらはHTTPヘッダのオーバーヘッドが大きく、レイテンシやサーバー負荷の点で非効率でした。ゲーム、チャット、リアルタイムデータフィードなど、低レイテンシかつ持続的な双方向通信を必要とするアプリケーションの増加が、これらの課題を抜本的に解決する新しいプロトコルの必要性を高めました。

設計目標

WebSocket (RFC 6455) は、以下の設計目標を掲げています。

  • 全二重通信: クライアントとサーバー間で同時にデータ送受信が可能な持続的接続の提供。

  • 低オーバーヘッド: 確立後はHTTPヘッダのオーバーヘッドを大幅に削減し、効率的なデータ転送を実現。

  • ファイアウォール親和性: HTTP/HTTPSプロトコルを利用したハンドシェイクを行うことで、既存のWebインフラ(プロキシ、ファイアウォールなど)との互換性を確保。

  • シンプルさ: 軽量なバイナリフレーミングプロトコルにより、実装の複雑さを軽減。

  • テキスト/バイナリデータ対応: テキスト(UTF-8)とバイナリデータの両方を効率的に転送する機構の提供。

詳細

ハンドシェイク(接続確立)

WebSocketの接続は、HTTP/1.1の「Upgrade」メカニズムを利用して確立されます。クライアントがHTTPリクエストを送信し、サーバーが特定のヘッダを含むHTTPレスポンスを返すと、プロトコルがHTTPからWebSocketへ切り替わります。このプロセスは「ハンドシェイク」と呼ばれます。

sequenceDiagram
    participant C as Client
    participant S as Server

    C ->> S: |HTTP GET /chat HTTP/1.1|
    C ->> S: |Host: example.com|
    C ->> S: |Upgrade: websocket|
    C ->> S: |Connection: Upgrade|
    C ->> S: |Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==|
    C ->> S: |Sec-WebSocket-Version: 13|
    Note over C,S: HTTP/1.1 Upgrade Request

    S ->> C: |HTTP/1.1 101 Switching Protocols|
    S ->> C: |Upgrade: websocket|
    S ->> C: |Connection: Upgrade|
    S ->> C: |Sec-WebSocket-Accept: s3pPLMBiSuxCbqhJz2+MRG7NKpY=|
    Note over C,S: HTTP/1.1 Upgrade Response

    Note over C,S: WebSocket Connection Established
    C ->> S: |WebSocket Frame (Data)|
    S ->> C: |WebSocket Frame (Data)|
  • Sec-WebSocket-Key: クライアントが生成する16バイトのランダムなBase64エンコード文字列。サーバーからの応答の検証に使用されます。

  • Sec-WebSocket-Version: クライアントがサポートするWebSocketプロトコルバージョンを指定します。RFC 6455はバージョン13です。

  • 101 Switching Protocols: サーバーはプロトコル切り替えに同意する際、このステータスコードを返します。

  • Sec-WebSocket-Accept: サーバーはSec-WebSocket-Keyと定義されたGUID (258EAFA5-E914-47DA-95CA-C5AB0DC85B11) を連結し、SHA-1ハッシュを計算し、Base64エンコードした文字列を返します。これにより、クライアントはサーバーが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
+-+-+-+-+-------+-+-------------+-------------------------------+
|FIN|RSV1|RSV2|RSV3|Opcode |M|Payload len  | Masking-Key (if M=1)|
+-+-+-+-+-------+-+-------------+-------------------------------+
| Masking-Key (cont.)           | Extended payload length (if P=126)|
+-------------------------------+-------------------------------+
| Extended payload length (cont.) | Payload data                  |
+---------------------------------------------------------------+
  • FIN (1 bit): フラグメント化されたメッセージの最終フレームであるかを示します。

  • RSV1, RSV2, RSV3 (1 bit each): 将来のために予約されています。現在は0である必要があります。

  • Opcode (4 bits): ペイロードの解釈方法を定義します。

    • %x0: 継続フレーム

    • %x1: テキストフレーム (UTF-8)

    • %x2: バイナリフレーム

    • %x8: 接続終了 (Close)

    • %x9: Ping

    • %xA: Pong

    • %x3-%x7, %xB-%xF: 予約済み

  • M (MASK) (1 bit): ペイロードデータがマスキングされているかを示します。クライアントからサーバーへのすべてのフレームはマスキングされなければなりません。

  • Payload len (7 bits): ペイロードデータの長さを表します。

    • 0-125: 実際のペイロード長。

    • 126: 次の2バイト (16 bits) が拡張ペイロード長。

    • 127: 次の8バイト (64 bits) が拡張ペイロード長。

  • Masking-Key (0 or 4 bytes): MASKビットが1の場合に存在します。ペイロードデータをマスキング/アンマスキングするための32ビットの値。

  • Payload data: アプリケーションデータ。

制御フレーム

WebSocketは、データ転送以外に接続の状態を管理するための制御フレームを定義しています。

  • PINGフレーム (Opcode %x9): 接続の活性チェックやネットワークレイテンシ測定に使用されます。PINGフレームには任意のペイロードを含めることができます。

  • PONGフレーム (Opcode %xA): PINGフレームを受信した際、クライアントまたはサーバーは同じペイロードを持つPONGフレームを返信します。

  • CLOSEフレーム (Opcode %x8): 接続を正常に終了するために使用されます。ペイロードにはステータスコードとオプションのUTF-8形式の理由を含めることができます。

データマスキング

クライアントからサーバーへ送信されるすべてのデータフレームは、RFC 6455で定義されたマスキングキーを使用してペイロードがXORされます。これは、中間ノード(例えばプロキシ)が、古いHTTPプロキシキャッシュポイズニング攻撃を防止するため、HTTPリクエストであると誤解しないようにするためのセキュリティ対策です。サーバーからクライアントへのフレームはマスキングされません。

相互運用

WebSocketはHTTPの上位プロトコルとして動作するため、既存のWebインフラとの相互運用性を考慮する必要があります。

  • HTTP/1.1: WebSocketのハンドシェイクはHTTP/1.1のUpgradeヘッダを利用します。

  • HTTP/2: HTTP/2は多重化ストリームを提供し、複数のリクエスト/レスポンスを単一のTCP接続で処理できます。しかし、WebSocketのような全二重の持続的セッションとは異なり、リクエスト/レスポンスモデルが基本です。HTTP/2のCONNECTメソッドを使ったWebSocket over HTTP/2の標準化も進められましたが、普及には至っていません。WebSocketのフレーム構造はHTTP/2のストリームで効率的に転送できますが、HTTP/2が提供するServer Pushは単方向であり、WebSocketの双方向性とは目的が異なります。

  • HTTP/3: HTTP/3はQUICプロトコル上で動作し、TCPではなくUDPを使用します。ストリームレベルでのHOL blocking (Head-of-Line blocking) をQUICが解決するため、基盤となるトランスポート層の効率が向上します。WebSocket自体はTCPを前提としたプロトコルですが、将来的にWebSocketをQUIC上で動作させるためのドラフト(WebTransport)も議論されています。これは、WebSocketのアプリケーション層のセマンティクスをQUICの信頼性の高い多重化ストリームに乗せることで、より低レイテンシで堅牢な通信を実現する可能性があります。

既存プロトコルとの比較(箇条書き):

  • HTTP/1.1 vs WebSocket:

    • HTTP/1.1: ステートレス、リクエスト/レスポンス、ヘッダオーバーヘッド大、全二重不可(ロングポーリングなどで擬似的に実現)。

    • WebSocket: ステートフル、全二重、確立後ヘッダオーバーヘッド小、リアルタイム通信に最適。

  • HTTP/2 vs WebSocket:

    • HTTP/2: 多重化ストリーム(単一TCP接続)、バイナリフレーミング、サーバープッシュ、リクエスト/レスポンスモデルが基本。ストリーム間でHOL blocking回避(TCPレベルでは依然発生)。

    • WebSocket: 単一TCP接続内での全二重論理チャネル、軽量バイナリフレーム。目的が異なるため、直接的な代替ではなく補完関係。

  • HTTP/3 (QUIC) vs WebSocket:

    • HTTP/3: UDPベース、ストリームレベルでのHOL blocking回避(QUIC層)、高速な接続確立(0-RTTサポート)。

    • WebSocket: TCPベース。HTTP/3の基盤技術はWebSocketの課題(TCPのHOL blockingなど)を解決する可能性を秘めるが、WebSocket自体はTCPの上で動作するため、直接的な恩恵を受けるにはWebTransportなどの新しいプロトコルが必要。

セキュリティ

WebSocketはHTTPを起点とするため、Webアプリケーションのセキュリティモデルを引き継ぎますが、持続的な接続であることによる固有の考慮事項があります。

  • オリジンベースのセキュリティ: WebSocket接続は、クライアントのオリジン情報(Originヘッダ)をサーバーに送信します。サーバーはこれを利用して、Cross-Site WebSocket Hijacking (CSWSH) などの攻撃を防ぐために、許可されたオリジンからの接続のみを受け入れるべきです。

  • ダウングレード攻撃: WebSocketプロトコルはTLS (Transport Layer Security) 上で実行されるwss://スキーマの使用を強く推奨しています。これにより、盗聴、改ざん、なりすましなどの攻撃を防ぎます。HTTPからWebSocketへのハンドシェイク中に、意図しない平文通信へのダウングレード(ws://への強制など)を防ぐ必要があります。HSTS (HTTP Strict Transport Security) を利用することも有効です。

  • キー更新: TLS接続の暗号鍵は定期的に更新されるべきです。WebSocket自体には鍵更新メカニズムはありませんが、基盤となるTLS接続の再ネゴシエーション(TLS 1.2以前)や、TLS 1.3のキー更新機能によってセキュリティを維持します。

  • 0-RTTの再送リスク: TLS 1.3の0-RTT (Zero Round Trip Time) 機能は接続確立を高速化しますが、初期のアプリケーションデータがリプレイ攻撃に対して脆弱である可能性があります。WebSocketハンドシェイク自体は0-RTTでは行われませんが、もし将来的にWebTransportなどQUICベースのプロトコルがWebSocketの代替となり、0-RTTデータ転送をサポートする場合、アプリケーション層でリプレイ保護を考慮する必要があります。RFC 8446 (TLS 1.3) に従って、0-RTTデータはべき等性のある操作に限定するか、リプレイ検出メカニズムを実装する必要があります。

  • サービス拒否攻撃 (DoS): 大量のWebSocket接続要求や、特大のフレーム、継続的にPINGフレームを送信するなどの攻撃に対して、サーバー側で接続数、フレームサイズ、流量を制限するメカニズムが必要です。

実装メモ

WebSocketの実装では、プロトコル仕様の遵守に加えて、ネットワークエンジニアとして以下の点に注意を払う必要があります。

  • MTU/Path MTU: WebSocketフレームは比較的自由にペイロード長を設定できますが、基盤となるTCP/IP層のMTU (Maximum Transmission Unit) を考慮しないと、IPフラグメンテーションが発生し、性能劣化やパケットロス耐性の低下を招きます。Path MTU Discovery (PMTUD) を活用するか、アプリケーションレベルで適切なフレームサイズを決定するべきです。

  • HOL blocking回避: WebSocket自体は単一のTCP接続上で動作するため、TCPレベルでのHead-of-Line blockingの影響を受けます。つまり、TCPセグメントのロスが発生した場合、後続のすべてのデータがその再送完了を待つことになります。WebSocketフレームの順序保証はこのTCPの特性に依存します。アプリケーションで複数の論理チャネルを必要とする場合は、複数のWebSocket接続を確立するか、WebTransportのような基盤プロトコルを利用することを検討してください。

  • キュー制御とバックプレッシャー: サーバーがクライアントにデータを送信する速度と、クライアントが受信・処理する速度のバランスを取るために、適切なキュー制御とバックプレッシャーメカニズムを実装する必要があります。サーバーがクライアントの処理能力を超えてデータを送信し続けると、クライアントのバッファオーバーフローやサーバー側のメモリ枯渇につながる可能性があります。

  • 優先度制御: PING/PONGやCLOSEフレームのような制御フレームは、通常のデータフレームよりも高い優先度で処理されるべきです。特にPING/PONGは接続のヘルスチェックに関わるため、優先的に送受信されることで接続の健全性を維持できます。

  • TLSオフロード: サーバーサイドでは、TLSハンドシェイクと暗号化/復号処理の負荷を軽減するために、ハードウェアアクセラレータや専用のTLSプロキシ(ロードバランサなど)によるTLSオフロードを検討します。

  • エラーハンドリングと再接続ロジック: ネットワークの一時的な切断やサーバー側の障害に備え、堅牢なエラーハンドリングと指数バックオフなどの再接続ロジックをクライアント・サーバー双方で実装することが重要です。

まとめ

WebSocket (RFC 6455) は、HTTP/1.1の限界を克服し、Web上で効率的かつリアルタイムな双方向通信を実現する画期的なプロトコルです。HTTP Upgradeメカニズムによるハンドシェイク、低オーバーヘッドのバイナリフレーム、堅牢なセキュリティ考慮事項、そして多様なデータタイプへの対応がその特徴です。ネットワークエンジニアとしては、プロトコルの詳細な理解に加え、基盤となるTCP/IP、TLS、そして将来のHTTP/3などの技術との相互作用、さらにはMTU、HOL blocking、セキュリティ、パフォーマンスといった運用上の課題への深い洞察が、信頼性と効率性の高いWebSocketアプリケーション実装の鍵となります。

ライセンス:本記事のテキスト/コードは特記なき限り CC BY 4.0 です。引用の際は出典URL(本ページ)を明記してください。
利用ポリシー もご参照ください。

コメント

タイトルとURLをコピーしました