#このページで確認できること
- Upgrade ハンドシェイクのヘッダーと 101 Switching Protocols を確認する
- DevTools の WS タブで、ブラウザが表示するメッセージや制御フレームの送受信を確認する
- プロキシ / TLSインスペクション経由で「101 が返るか」を切り分ける手順
- 観察するヘッダー:
ConnectionUpgradeSec-WebSocket-KeySec-WebSocket-Accept
実装上の制約: エコーサーバーはローカル観測専用としてリポジトリに同梱しています(demos/websocket-node/)。公開環境では起動しない方針です。docker compose up websocket で起動し、ws://localhost:9001 に接続できます。
#ハンドシェイク
クライアントは通常のHTTPリクエストに Upgrade 系ヘッダーを付けて送り、サーバーが 101 Switching Protocols を返した瞬間からプロトコルが切り替わります。
GET /ws HTTP/1.1
Host: example.com
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
response
HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
- Sec-WebSocket-Key: クライアントが生成するランダムな16バイトのBase64。認証ではなく「相手がWebSocketを理解していること」の確認用。
- Sec-WebSocket-Accept: Key に固定GUID
258EAFA5-E914-47DA-95CA-C5AB0DC85B11を連結して SHA-1 → Base64 した値。相手がWebSocketの開始ハンドシェイクを理解して応答したことを確認し、意図しないHTTP応答を受け入れにくくする。認証目的ではない。 - 101 応答の後は、同じTCPコネクション上を WebSocketフレーム(opcode: text / binary / ping / pong / close)が双方向に流れる。
- クライアント→サーバーのフレームは必ずマスクされる(プロキシキャッシュ汚染攻撃への対策)。
#通信シーケンス
participants: Client, Server Client -> Server: GET /ws HTTP/1.1 + Upgrade: websocket Server --> Client: HTTP/1.1 101 Switching Protocols note: ここからはHTTPではなくWebSocketフレーム Client -> Server: text frame "hello" Server -> Client: text frame "world" (サーバー起点の送信が可能) Server -> Client: ping frame Client --> Server: pong frame Client -> Server: close frame Server --> Client: close frame
participants: Client, Proxy, Server Client -> Proxy: GET /ws + Upgrade headers Proxy -> Server: Upgrade リクエストを転送 Server --> Proxy: 101 Switching Protocols Proxy --> Client: 101 Switching Protocols note: 以降プロキシは双方向ストリームを素通しできる必要がある Client <-> Server: WebSocket frames (via proxy)
プロキシ通過性は重要な確認点: Upgrade は hop-by-hop です。HTTPを終端して再転送するリバースプロキシ・ロードバランサ・インスペクション装置は、WebSocket Upgradeと以降の双方向通信を扱える必要があります。一方、WSSを不透明なTCPトンネルとして中継するフォワードプロキシは、WebSocketフレーム自体を理解しなくても転送できます。nginxからHTTP/1.1で上流へリバースプロキシする構成では、
proxy_http_version 1.1; と適切な Upgrade / Connection ヘッダー転送が必要です。経路上の設定によっては101が返らない、またはidle timeoutで切断されます。
#検証コマンド
デモサーバーの起動 (リポジトリ同梱)$ docker compose up websocket # ws://localhost:9001 でエコーサーバーが起動する (受信メッセージをそのまま返す)curl — ハンドシェイクだけを観測
# Upgrade ヘッダーを手動で付けて 101 を確認する(フレーム交換はしない) $ curl -i -N \ -H 'Connection: Upgrade' \ -H 'Upgrade: websocket' \ -H 'Sec-WebSocket-Version: 13' \ -H 'Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==' \ http://localhost:9001/ # curl 7.86+ なら ws:// スキームでハンドシェイクし、受信フレームを観測できる $ curl --include --no-buffer ws://localhost:9001/expected response
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
ブラウザ — DevTools Console
const ws = new WebSocket("ws://localhost:9001/");
ws.onopen = () => ws.send("hello");
ws.onmessage = (e) => console.log("recv:", e.data); // → recv: hello
デモサーバーの制限: 同梱のエコーサーバーはローカル観測専用で、公開環境では起動しません。メッセージサイズ上限64KB、同時接続50、30秒ごとのpingにpongが返らない接続の切断、ペイロード非記録を実装しています。方針は
docs/demos-local-only.md を参照してください。なお HTTPS で配信されたページからは ws://localhost への接続がブラウザにブロックされる場合があります(混在コンテンツ)。その場合はローカル配信(http://localhost:8080)のページから試してください。
#観測ポイント
| curl | 101 が返るか、Sec-WebSocket-Accept の値が正しいか。200 や 400 が返る場合は中間装置がUpgradeを処理できていない可能性。 |
|---|---|
| DevTools | Network → WS フィルタ。ハンドシェイクのヘッダーと、Messages タブで送受信方向やメッセージ、制御フレームなどを確認できる。フラグメントされたデータの表示単位、色、表示名はブラウザとバージョンにより変わり得る。 |
| Proxy | 101 がクライアントまで届くか。長時間接続が idle timeout(60秒等)で切られないか。Connection/Upgrade ヘッダーがプロキシで落とされていないか。 |
| tcpdump / Wireshark | 平文なら websocket フィルタでフレーム解析可能。ハンドシェイクはHTTPとして、以降はWebSocketフレームとしてデコードされる。マスクの有無も見える。 |
| TLS inspection | 装置がWebSocketを認識して素通しするか、切断するかは製品・設定差が大きい。direct で動き proxy 経由で動かない場合の一次切り分けは「101 が返っているか」。 |
#関連
HTTP/2 環境では Upgrade の代わりに CONNECT メソッドの拡張(RFC 8441)でWebSocketをストリームに載せる方式があり、HTTP/3向けにもExtended CONNECTを使うブートストラップ(RFC 9220)が定義されています。実際に利用できるかはクライアント、サーバー、中間装置の対応によります。双方向ストリーミングが目的なら gRPC のstreaming RPCも比較対象になります。