WebSocket ハンドシェイク&ヘッダー チートシート:Upgrade/Sec-WebSocket-Key/Accept 一覧

WebSocket を使った開発をしていると、ハンドシェイクの仕組みやヘッダーの意味を正確に理解したいと思うことがありますよね。特に、Upgrade
、Sec-WebSocket-Key
、Sec-WebSocket-Accept
といったヘッダーがどのように連携して接続を確立するのか、詳しく知っておくと実装やデバッグがスムーズになります。
この記事では、WebSocket ハンドシェイクで使用される主要なヘッダーを一覧形式で整理し、それぞれの役割や仕組みを段階的に解説します。具体的なリクエスト・レスポンス例も含めて、WebSocket 接続の流れを図解とともに理解できるようにまとめました。
WebSocket ハンドシェイク主要ヘッダー一覧
# | ヘッダー名 | 方向 | 必須 | 値の例 | 役割 |
---|---|---|---|---|---|
1 | Upgrade | クライアント → サーバー | ✓ | websocket | プロトコルを WebSocket に切り替えることを要求 |
2 | Connection | クライアント → サーバー | ✓ | Upgrade | Upgrade ヘッダーと連動し、接続の昇格を指示 |
3 | Sec-WebSocket-Key | クライアント → サーバー | ✓ | dGhlIHNhbXBsZSBub25jZQ== | ランダムな Base64 エンコード値(16 バイト) |
4 | Sec-WebSocket-Version | クライアント → サーバー | ✓ | 13 | WebSocket プロトコルのバージョン |
5 | Sec-WebSocket-Protocol | クライアント → サーバー | - | chat, superchat | サブプロトコルの指定(オプション) |
6 | Sec-WebSocket-Extensions | クライアント → サーバー | - | permessage-deflate | 拡張機能の指定(オプション) |
7 | Sec-WebSocket-Accept | サーバー → クライアント | ✓ | s3pPLMBiTxaQ9kYGzzhZRbK+xOo= | Sec-WebSocket-Key から計算した検証値 |
8 | Origin | クライアント → サーバー | - | https://example.com | リクエスト元のオリジン(CORS 検証) |
背景
WebSocket とは
WebSocket は、クライアントとサーバー間で 双方向通信 を実現するプロトコルです。従来の HTTP では、クライアントからリクエストを送って初めてサーバーが応答する「リクエスト・レスポンス型」の通信でしたが、WebSocket では接続を維持したまま、サーバーからクライアントへリアルタイムにデータを送ることができます。
チャットアプリケーション、株価のリアルタイム更新、オンラインゲームなど、低遅延で頻繁にデータをやり取りする必要があるアプリケーションに最適です。
HTTP から WebSocket への昇格
WebSocket は HTTP とは異なるプロトコルですが、接続の開始時には HTTP リクエスト を利用します。この仕組みを「プロトコルの昇格(Upgrade)」と呼びます。
クライアントは最初に HTTP リクエストを送り、Upgrade
ヘッダーで「WebSocket に切り替えたい」と伝えます。サーバーがこれを承認すると、HTTP 接続が WebSocket 接続に切り替わり、双方向通信が可能になるのです。
以下の図は、HTTP から WebSocket への昇格プロセスを示しています。
mermaidsequenceDiagram
participant Client as クライアント
participant Server as サーバー
Client->>Server: HTTP リクエスト<br/>(Upgrade: websocket)
Server->>Client: HTTP レスポンス<br/>(101 Switching Protocols)
Note over Client,Server: WebSocket 接続確立
Client->>Server: WebSocket メッセージ
Server->>Client: WebSocket メッセージ
図で理解できる要点
- WebSocket は HTTP リクエストから始まる
Upgrade
ヘッダーでプロトコル切り替えを要求- サーバーが
101 Switching Protocols
で承認すると WebSocket 通信開始
ハンドシェイクの必要性
なぜ HTTP から WebSocket へ昇格する必要があるのでしょうか。それは、既存の Web インフラ(ファイアウォール、プロキシ、ロードバランサーなど)が HTTP 通信を前提に設計されているためです。
WebSocket が HTTP リクエストから始まることで、既存のインフラをそのまま利用しながら、双方向通信を実現できます。また、ハンドシェイク時にセキュリティチェックも行われ、意図しない接続を防ぐ仕組みも組み込まれています。
課題
WebSocket 接続が失敗する原因
WebSocket 接続が失敗する主な原因は、ハンドシェイク時のヘッダー設定ミスです。以下のようなケースがよくあります。
Upgrade
ヘッダーが正しく設定されていないSec-WebSocket-Key
が送信されていない、または形式が不正- サーバー側で
Sec-WebSocket-Accept
の計算ロジックが間違っている - プロトコルバージョンの不一致(
Sec-WebSocket-Version
の値が異なる)
これらのエラーは HTTP のステータスコードやヘッダーの内容を確認することで特定できますが、それぞれのヘッダーの役割を理解していないと、どこが問題なのか判断するのが難しくなります。
ハンドシェイクのデバッグが困難
WebSocket のハンドシェイクは、一見すると HTTP リクエストと変わらないため、ブラウザの開発者ツールや curl コマンドでの確認が可能です。しかし、以下のような点でデバッグが困難になりがちです。
- ヘッダーの種類が多く、どれが必須でどれがオプションか分かりにくい
Sec-WebSocket-Accept
の計算方法が独特で、手動検証しにくい- サーバー側の実装ミスとクライアント側の実装ミスを区別しにくい
特に、Sec-WebSocket-Accept
は Sec-WebSocket-Key
をもとに SHA-1 ハッシュと Base64 エンコードを行って生成されるため、手計算での検証は現実的ではありません。
次の図は、WebSocket ハンドシェイク失敗の主な原因を分類したものです。
mermaidflowchart TD
start["ハンドシェイク失敗"] --> header_missing["必須ヘッダー不足"]
start --> header_invalid["ヘッダー値が不正"]
start --> version_mismatch["バージョン不一致"]
start --> accept_calc["Accept 計算ミス"]
header_missing --> upgrade_missing["Upgrade ヘッダーなし"]
header_missing --> key_missing["Sec-WebSocket-Key なし"]
header_invalid --> key_format["Key の形式が Base64 でない"]
header_invalid --> protocol_wrong["プロトコル名が間違い"]
version_mismatch --> version13["Version が 13 でない"]
accept_calc --> hash_error["SHA-1 ハッシュ計算ミス"]
accept_calc --> encoding_error["Base64 エンコードミス"]
図で理解できる要点
- ハンドシェイク失敗の原因は大きく 4 つに分類される
- 必須ヘッダーの不足や値の形式エラーが多い
Sec-WebSocket-Accept
の計算ミスも頻出
セキュリティリスクの理解不足
WebSocket のハンドシェイクには、セキュリティ上の重要な役割もあります。Sec-WebSocket-Key
と Sec-WebSocket-Accept
の仕組みは、単なる接続確認だけでなく、意図しないプロキシやキャッシュサーバーが WebSocket 通信を誤って処理しないようにする ためのものです。
これを理解せずに実装すると、セキュリティホールを生む可能性があります。例えば、サーバー側で Sec-WebSocket-Accept
の検証を省略すると、不正なクライアントからの接続を許してしまうことになりかねません。
解決策
WebSocket ハンドシェイクの全体像
WebSocket 接続は、以下の手順で確立されます。
- クライアントが HTTP リクエストを送信:
Upgrade
ヘッダーで WebSocket への切り替えを要求 - サーバーが HTTP レスポンスを返す:
101 Switching Protocols
で承認 - WebSocket 接続が確立:以降は HTTP ではなく WebSocket フレームでデータをやり取り
この流れをスムーズに進めるためには、各ヘッダーの役割を正確に理解し、正しく設定することが重要です。
主要ヘッダーの役割一覧
WebSocket ハンドシェイクで使用される主要なヘッダーを、以下の表にまとめました。
# | ヘッダー名 | 方向 | 必須 | 役割 |
---|---|---|---|---|
1 | Upgrade | クライアント → サーバー | ✓ | プロトコルを WebSocket に切り替えることを要求 |
2 | Connection | クライアント → サーバー | ✓ | Upgrade ヘッダーと連動し、接続の昇格を指示 |
3 | Sec-WebSocket-Key | クライアント → サーバー | ✓ | ランダムな Base64 エンコード値(16 バイト) |
4 | Sec-WebSocket-Version | クライアント → サーバー | ✓ | WebSocket プロトコルのバージョン(通常は 13 ) |
5 | Sec-WebSocket-Protocol | クライアント → サーバー | - | サブプロトコルの指定(オプション) |
6 | Sec-WebSocket-Extensions | クライアント → サーバー | - | 拡張機能の指定(オプション) |
7 | Sec-WebSocket-Accept | サーバー → クライアント | ✓ | Sec-WebSocket-Key をもとに計算した値 |
各ヘッダーの詳細
それぞれのヘッダーについて、詳しく見ていきましょう。
Upgrade ヘッダー
Upgrade
ヘッダーは、HTTP 接続を WebSocket に切り替えることをサーバーに伝える役割を持ちます。
httpUpgrade: websocket
このヘッダーがないと、サーバーは通常の HTTP リクエストとして処理してしまい、WebSocket 接続は確立されません。
Connection ヘッダー
Connection
ヘッダーは、Upgrade
ヘッダーと連動して、接続の昇格を指示します。
httpConnection: Upgrade
Connection: keep-alive
や Connection: close
とは異なり、Upgrade
を指定することで、プロトコルの切り替えを明示的に要求します。
Sec-WebSocket-Key ヘッダー
Sec-WebSocket-Key
は、クライアントが生成するランダムな 16 バイトの値を Base64 エンコードしたものです。
httpSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
この値は、サーバー側で Sec-WebSocket-Accept
を計算する際に使用されます。ランダムな値を使うことで、意図しないプロキシやキャッシュサーバーが WebSocket 通信を誤って処理しないようにしています。
Sec-WebSocket-Version ヘッダー
Sec-WebSocket-Version
は、WebSocket プロトコルのバージョンを指定します。現在の標準バージョンは 13
です。
httpSec-WebSocket-Version: 13
サーバーが対応していないバージョンを指定すると、426 Upgrade Required
エラーが返されます。
Sec-WebSocket-Protocol ヘッダー(オプション)
Sec-WebSocket-Protocol
は、WebSocket 上で使用するサブプロトコルを指定します。複数のプロトコルをカンマ区切りで指定できます。
httpSec-WebSocket-Protocol: chat, superchat
サーバーは、対応しているプロトコルの中から 1 つを選択して、レスポンスに含めます。
Sec-WebSocket-Extensions ヘッダー(オプション)
Sec-WebSocket-Extensions
は、圧縮などの拡張機能を指定します。
httpSec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
サーバーが対応している拡張機能を選択して、レスポンスに含めます。
Sec-WebSocket-Accept ヘッダー
Sec-WebSocket-Accept
は、サーバーがクライアントの Sec-WebSocket-Key
をもとに計算した値です。
httpSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
この値は、以下の手順で計算されます。
Sec-WebSocket-Key
の値に固定文字列258EAFA5-E914-47DA-95CA-C5AB0DC85B11
を結合- SHA-1 ハッシュ値を計算
- Base64 エンコード
クライアントは、この値を検証することで、サーバーが正しく WebSocket プロトコルを理解していることを確認します。
Sec-WebSocket-Accept の計算方法
Sec-WebSocket-Accept
の計算方法を、TypeScript のコードで示します。
typescriptimport { createHash } from 'crypto';
// Sec-WebSocket-Key の値
const key = 'dGhlIHNhbXBsZSBub25jZQ==';
次に、固定の GUID(Globally Unique Identifier)を定義します。この値は RFC 6455 で規定されています。
typescript// RFC 6455 で規定された固定 GUID
const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
Sec-WebSocket-Key
と GUID を結合します。
typescript// Key と GUID を結合
const combined = key + GUID;
// 結果: 'dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
SHA-1 ハッシュ値を計算します。
typescript// SHA-1 ハッシュを計算
const hash = createHash('sha1').update(combined).digest();
// 結果は Buffer 型のバイナリデータ
最後に、Base64 エンコードして Sec-WebSocket-Accept
の値を生成します。
typescript// Base64 エンコード
const accept = hash.toString('base64');
// 結果: 's3pPLMBiTxaQ9kYGzzhZRbK+xOo='
console.log(accept);
この計算ロジックは、サーバー側で実装する必要があります。クライアント側は、サーバーから返された Sec-WebSocket-Accept
の値が正しいかどうかを検証します。
具体例
クライアントからのリクエスト例
実際の WebSocket ハンドシェイクリクエストを見てみましょう。以下は、ブラウザから送信される典型的なリクエストです。
httpGET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Sec-WebSocket-Protocol: chat
Origin: https://example.com
各行の役割
GET /chat HTTP/1.1
:WebSocket エンドポイントへの HTTP GET リクエストHost: example.com
:接続先のホスト名Upgrade: websocket
:WebSocket への昇格を要求Connection: Upgrade
:接続の昇格を指示Sec-WebSocket-Key
:ランダムな Base64 エンコード値Sec-WebSocket-Version: 13
:WebSocket プロトコルバージョンSec-WebSocket-Protocol: chat
:使用するサブプロトコルOrigin
:リクエスト元のオリジン(CORS 検証に使用)
サーバーからのレスポンス例
サーバーが WebSocket 接続を承認する場合、以下のようなレスポンスを返します。
httpHTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat
各行の役割
HTTP/1.1 101 Switching Protocols
:プロトコル切り替えを承認Upgrade: websocket
:WebSocket に切り替えConnection: Upgrade
:接続の昇格を確認Sec-WebSocket-Accept
:Key から計算した検証値Sec-WebSocket-Protocol: chat
:選択したサブプロトコル
このレスポンスを受け取った後、クライアントとサーバーは WebSocket フレームを使ってデータをやり取りします。
Node.js でのサーバー実装例
Node.js で WebSocket サーバーを実装する場合、ws
ライブラリを使うのが一般的です。以下は、基本的な実装例です。
まず、必要なパッケージをインストールします。
bashyarn add ws
yarn add -D @types/ws
次に、WebSocket サーバーを作成します。
typescriptimport { WebSocketServer } from 'ws';
// WebSocket サーバーを作成(ポート 8080 で待ち受け)
const wss = new WebSocketServer({ port: 8080 });
console.log(
'WebSocket サーバーが起動しました(ポート 8080)'
);
クライアントからの接続を処理します。
typescript// クライアントが接続したときの処理
wss.on('connection', (ws) => {
console.log('新しいクライアントが接続しました');
// クライアントからメッセージを受信したときの処理
ws.on('message', (data) => {
console.log('受信:', data.toString());
// クライアントにメッセージを送信
ws.send(`サーバーからの返信: ${data}`);
});
});
クライアントが切断したときの処理も追加します。
typescript // クライアントが切断したときの処理
ws.on('close', () => {
console.log('クライアントが切断しました');
});
// エラー発生時の処理
ws.on('error', (error) => {
console.error('エラー:', error);
});
});
ws
ライブラリは、内部で Sec-WebSocket-Accept
の計算やハンドシェイクの検証を自動的に行ってくれるため、開発者はビジネスロジックに集中できます。
ブラウザでのクライアント実装例
ブラウザで WebSocket クライアントを実装する場合、標準の WebSocket
API を使用します。
javascript// WebSocket 接続を作成
const ws = new WebSocket('ws://example.com/chat', 'chat');
console.log('WebSocket 接続を開始します...');
接続が確立されたときの処理を定義します。
javascript// 接続が確立されたときの処理
ws.addEventListener('open', (event) => {
console.log('WebSocket 接続が確立されました');
// サーバーにメッセージを送信
ws.send('こんにちは、サーバー!');
});
サーバーからメッセージを受信したときの処理を定義します。
javascript// サーバーからメッセージを受信したときの処理
ws.addEventListener('message', (event) => {
console.log('サーバーからのメッセージ:', event.data);
});
エラーや切断時の処理も追加します。
javascript// エラー発生時の処理
ws.addEventListener('error', (error) => {
console.error('WebSocket エラー:', error);
});
// 接続が閉じられたときの処理
ws.addEventListener('close', (event) => {
console.log('WebSocket 接続が閉じられました');
console.log('コード:', event.code);
console.log('理由:', event.reason);
});
ブラウザの WebSocket
API は、ハンドシェイクを自動的に処理してくれるため、開発者は Sec-WebSocket-Key
や Sec-WebSocket-Accept
を意識する必要はありません。
ハンドシェイクの流れを図解
WebSocket ハンドシェイクの全体的な流れを、以下の図で確認してみましょう。
mermaidsequenceDiagram
participant Browser as ブラウザ
participant Server as WebSocket サーバー
Browser->>Server: HTTP リクエスト<br/>(Upgrade, Sec-WebSocket-Key)
Note over Server: Key から Accept を計算<br/>(SHA-1 + Base64)
Server->>Browser: HTTP レスポンス<br/>(101, Sec-WebSocket-Accept)
Note over Browser: Accept の値を検証
Note over Browser,Server: WebSocket 接続確立
Browser->>Server: WebSocket フレーム<br/>(テキストメッセージ)
Server->>Browser: WebSocket フレーム<br/>(テキストメッセージ)
図で理解できる要点
- クライアントが
Sec-WebSocket-Key
を送信 - サーバーが Key から
Sec-WebSocket-Accept
を計算 - クライアントが Accept 値を検証して接続確立
- 以降は WebSocket フレームでメッセージ交換
エラーケースとデバッグ
WebSocket 接続が失敗する主なエラーケースと、その対処方法を表にまとめました。
# | エラーコード | 原因 | 解決方法 |
---|---|---|---|
1 | 400 Bad Request | 必須ヘッダーが不足 | Upgrade , Connection , Sec-WebSocket-Key , Sec-WebSocket-Version を確認 |
2 | 426 Upgrade Required | バージョン不一致 | Sec-WebSocket-Version: 13 を指定 |
3 | 403 Forbidden | Origin が許可されていない | サーバー側の CORS 設定を確認 |
4 | 404 Not Found | エンドポイントが存在しない | WebSocket エンドポイントの URL を確認 |
5 | 500 Internal Server Error | サーバー側の処理エラー | サーバーログを確認し、Sec-WebSocket-Accept の計算ロジックを検証 |
エラーメッセージ例(400 Bad Request)
vbnetHTTP/1.1 400 Bad Request
Content-Type: text/plain
Missing Sec-WebSocket-Key header
エラー発生条件
- クライアントが
Sec-WebSocket-Key
ヘッダーを送信していない
解決方法
- クライアント側のコードを確認し、
Sec-WebSocket-Key
が正しく設定されているか確認 - ブラウザの開発者ツールで、ネットワークタブから実際のリクエストヘッダーを確認
- WebSocket ライブラリを使用している場合、ライブラリのバージョンを確認
エラーメッセージ例(426 Upgrade Required)
makefileHTTP/1.1 426 Upgrade Required
Sec-WebSocket-Version: 13
Unsupported WebSocket version
エラー発生条件
- クライアントが
Sec-WebSocket-Version: 13
以外の値を送信している
解決方法
Sec-WebSocket-Version: 13
を明示的に指定- 古い WebSocket ライブラリを使用している場合、最新版にアップデート
curl でのハンドシェイク確認
curl コマンドを使って、WebSocket ハンドシェイクを手動で確認できます。
bashcurl -i -N \
-H "Connection: Upgrade" \
-H "Upgrade: websocket" \
-H "Sec-WebSocket-Version: 13" \
-H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
http://example.com/chat
オプションの説明
-i
:レスポンスヘッダーを表示-N
:バッファリングを無効化-H
:カスタムヘッダーを指定
期待されるレスポンス
makefileHTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
このコマンドで正しいレスポンスが返ってくれば、サーバー側のハンドシェイク処理は正常に動作していることが確認できます。
まとめ
WebSocket のハンドシェイクは、HTTP リクエストから始まり、Upgrade
ヘッダーでプロトコルを切り替えることで双方向通信を実現します。特に、Sec-WebSocket-Key
と Sec-WebSocket-Accept
の仕組みは、セキュリティと互換性を保つために重要な役割を果たしています。
この記事で紹介したヘッダーの一覧と役割、計算方法を理解しておくことで、WebSocket の実装やデバッグがスムーズに進められるようになるでしょう。エラーが発生した際も、各ヘッダーの意味を知っていれば、問題の切り分けが容易になります。
WebSocket を使ったリアルタイム通信の実装に、ぜひこのチートシートを活用してみてください。
関連リンク
- article
WebSocket ハンドシェイク&ヘッダー チートシート:Upgrade/Sec-WebSocket-Key/Accept 一覧
- article
WebSocket を NGINX/HAProxy で終端する設定例:アップグレードヘッダーとタイムアウト完全ガイド
- article
WebSocket vs WebTransport vs SSE 徹底比較:遅延・帯域・安定性を実測レビュー
- article
WebSocket 導入判断ガイド:SSE・WebTransport・長輪講ポーリングとの適材適所を徹底解説
- article
WebSocket 技術の全体設計図:フレーム構造・サブプロトコル・拡張の要点を一気に理解
- article
WebSocket と HTTP/2・HTTP/3 の違いを徹底比較
- article
WebSocket ハンドシェイク&ヘッダー チートシート:Upgrade/Sec-WebSocket-Key/Accept 一覧
- article
WebRTC で E2EE ビデオ会議:Insertable Streams と鍵交換を実装する手順
- article
Python 正規表現チートシート:re/regex で高精度パターン 50 連発
- article
Vitest `vi` API 技術チートシート:`mock` / `fn` / `spyOn` / `advanceTimersByTime` 一覧
- article
Pinia ストア分割テンプレ集:domain/ui/session の三層パターン
- article
Obsidian Markdown 拡張チートシート:Callout/埋め込み/内部リンク完全網羅
- blog
iPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
- blog
Googleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
- blog
【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
- blog
Googleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
- blog
Pixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
- blog
フロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
- review
今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
- review
ついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
- review
愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
- review
週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来