T-CREATOR

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

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

WebSocket を使った開発をしていると、ハンドシェイクの仕組みやヘッダーの意味を正確に理解したいと思うことがありますよね。特に、UpgradeSec-WebSocket-KeySec-WebSocket-Accept といったヘッダーがどのように連携して接続を確立するのか、詳しく知っておくと実装やデバッグがスムーズになります。

この記事では、WebSocket ハンドシェイクで使用される主要なヘッダーを一覧形式で整理し、それぞれの役割や仕組みを段階的に解説します。具体的なリクエスト・レスポンス例も含めて、WebSocket 接続の流れを図解とともに理解できるようにまとめました。

WebSocket ハンドシェイク主要ヘッダー一覧

#ヘッダー名方向必須値の例役割
1Upgradeクライアント → サーバーwebsocketプロトコルを WebSocket に切り替えることを要求
2Connectionクライアント → サーバーUpgradeUpgrade ヘッダーと連動し、接続の昇格を指示
3Sec-WebSocket-Keyクライアント → サーバーdGhlIHNhbXBsZSBub25jZQ==ランダムな Base64 エンコード値(16 バイト)
4Sec-WebSocket-Versionクライアント → サーバー13WebSocket プロトコルのバージョン
5Sec-WebSocket-Protocolクライアント → サーバー-chat, superchatサブプロトコルの指定(オプション)
6Sec-WebSocket-Extensionsクライアント → サーバー-permessage-deflate拡張機能の指定(オプション)
7Sec-WebSocket-Acceptサーバー → クライアントs3pPLMBiTxaQ9kYGzzhZRbK+xOo=Sec-WebSocket-Key から計算した検証値
8Originクライアント → サーバー-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-AcceptSec-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-KeySec-WebSocket-Accept の仕組みは、単なる接続確認だけでなく、意図しないプロキシやキャッシュサーバーが WebSocket 通信を誤って処理しないようにする ためのものです。

これを理解せずに実装すると、セキュリティホールを生む可能性があります。例えば、サーバー側で Sec-WebSocket-Accept の検証を省略すると、不正なクライアントからの接続を許してしまうことになりかねません。

解決策

WebSocket ハンドシェイクの全体像

WebSocket 接続は、以下の手順で確立されます。

  1. クライアントが HTTP リクエストを送信Upgrade ヘッダーで WebSocket への切り替えを要求
  2. サーバーが HTTP レスポンスを返す101 Switching Protocols で承認
  3. WebSocket 接続が確立:以降は HTTP ではなく WebSocket フレームでデータをやり取り

この流れをスムーズに進めるためには、各ヘッダーの役割を正確に理解し、正しく設定することが重要です。

主要ヘッダーの役割一覧

WebSocket ハンドシェイクで使用される主要なヘッダーを、以下の表にまとめました。

#ヘッダー名方向必須役割
1Upgradeクライアント → サーバープロトコルを WebSocket に切り替えることを要求
2Connectionクライアント → サーバーUpgrade ヘッダーと連動し、接続の昇格を指示
3Sec-WebSocket-Keyクライアント → サーバーランダムな Base64 エンコード値(16 バイト)
4Sec-WebSocket-Versionクライアント → サーバーWebSocket プロトコルのバージョン(通常は 13
5Sec-WebSocket-Protocolクライアント → サーバー-サブプロトコルの指定(オプション)
6Sec-WebSocket-Extensionsクライアント → サーバー-拡張機能の指定(オプション)
7Sec-WebSocket-Acceptサーバー → クライアントSec-WebSocket-Key をもとに計算した値

各ヘッダーの詳細

それぞれのヘッダーについて、詳しく見ていきましょう。

Upgrade ヘッダー

Upgrade ヘッダーは、HTTP 接続を WebSocket に切り替えることをサーバーに伝える役割を持ちます。

httpUpgrade: websocket

このヘッダーがないと、サーバーは通常の HTTP リクエストとして処理してしまい、WebSocket 接続は確立されません。

Connection ヘッダー

Connection ヘッダーは、Upgrade ヘッダーと連動して、接続の昇格を指示します。

httpConnection: Upgrade

Connection: keep-aliveConnection: 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=

この値は、以下の手順で計算されます。

  1. Sec-WebSocket-Key の値に固定文字列 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 を結合
  2. SHA-1 ハッシュ値を計算
  3. 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-KeySec-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 接続が失敗する主なエラーケースと、その対処方法を表にまとめました。

#エラーコード原因解決方法
1400 Bad Request必須ヘッダーが不足Upgrade, Connection, Sec-WebSocket-Key, Sec-WebSocket-Version を確認
2426 Upgrade Requiredバージョン不一致Sec-WebSocket-Version: 13 を指定
3403 ForbiddenOrigin が許可されていないサーバー側の CORS 設定を確認
4404 Not Foundエンドポイントが存在しないWebSocket エンドポイントの URL を確認
5500 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 ヘッダーを送信していない

解決方法

  1. クライアント側のコードを確認し、Sec-WebSocket-Key が正しく設定されているか確認
  2. ブラウザの開発者ツールで、ネットワークタブから実際のリクエストヘッダーを確認
  3. WebSocket ライブラリを使用している場合、ライブラリのバージョンを確認

エラーメッセージ例(426 Upgrade Required)

makefileHTTP/1.1 426 Upgrade Required
Sec-WebSocket-Version: 13

Unsupported WebSocket version

エラー発生条件

  • クライアントが Sec-WebSocket-Version: 13 以外の値を送信している

解決方法

  1. Sec-WebSocket-Version: 13 を明示的に指定
  2. 古い 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-KeySec-WebSocket-Accept の仕組みは、セキュリティと互換性を保つために重要な役割を果たしています。

この記事で紹介したヘッダーの一覧と役割、計算方法を理解しておくことで、WebSocket の実装やデバッグがスムーズに進められるようになるでしょう。エラーが発生した際も、各ヘッダーの意味を知っていれば、問題の切り分けが容易になります。

WebSocket を使ったリアルタイム通信の実装に、ぜひこのチートシートを活用してみてください。

関連リンク