jpskill.com
📦 その他 コミュニティ

debugging-websocket-issues

WebSocket通信で「無効なフレームヘッダー」などのエラーが発生した場合に、サーバーの競合や圧縮の問題を特定し、フレームを直接確認して原因を突き止めるSkill。

📜 元の英語説明(参考)

Use when seeing WebSocket errors like "Invalid frame header", "RSV1 must be clear", or "WS_ERR_UNEXPECTED_RSV_1" - covers multiple WebSocketServer conflicts, compression issues, and raw frame debugging techniques

🇯🇵 日本人クリエイター向け解説

一言でいうと

WebSocket通信で「無効なフレームヘッダー」などのエラーが発生した場合に、サーバーの競合や圧縮の問題を特定し、フレームを直接確認して原因を突き止めるSkill。

※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。

⚡ おすすめ: コマンド1行でインストール(60秒)

下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。

🍎 Mac / 🐧 Linux
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o debugging-websocket-issues.zip https://jpskill.com/download/17076.zip && unzip -o debugging-websocket-issues.zip && rm debugging-websocket-issues.zip
🪟 Windows (PowerShell)
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/17076.zip -OutFile "$d\debugging-websocket-issues.zip"; Expand-Archive "$d\debugging-websocket-issues.zip" -DestinationPath $d -Force; ri "$d\debugging-websocket-issues.zip"

完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。

💾 手動でダウンロードしたい(コマンドが難しい人向け)
  1. 1. 下の青いボタンを押して debugging-websocket-issues.zip をダウンロード
  2. 2. ZIPファイルをダブルクリックで解凍 → debugging-websocket-issues フォルダができる
  3. 3. そのフォルダを C:\Users\あなたの名前\.claude\skills\(Win)または ~/.claude/skills/(Mac)へ移動
  4. 4. Claude Code を再起動

⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。

🎯 このSkillでできること

下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。

📦 インストール方法 (3ステップ)

  1. 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
  2. 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
  3. 3. 展開してできたフォルダを、ホームフォルダの .claude/skills/ に置く
    • · macOS / Linux: ~/.claude/skills/
    • · Windows: %USERPROFILE%\.claude\skills\

Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。

詳しい使い方ガイドを見る →
最終更新
2026-05-18
取得日時
2026-05-18
同梱ファイル
1

📖 Skill本文(日本語訳)

※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。

WebSocketの問題のデバッグ

概要

WebSocketの「invalid frame header」エラーは、多くの場合、実際のフレームの破損ではなく、アップグレードされたソケットに生のHTTPが書き込まれることに起因します。最も一般的な原因は、複数の WebSocketServer インスタンスが同じHTTPサーバー上で競合することです。

使用するタイミング

  • エラー: Invalid WebSocket frame: RSV1 must be clear
  • エラー: WS_ERR_UNEXPECTED_RSV_1
  • エラー: Invalid frame header
  • WebSocketが接続後、コード1006ですぐに切断される
  • サーバーのログは成功しているが、クライアントがガベージデータを受信する

クイックリファレンス

症状 考えられる原因 解決策
RSV1 must be clear 同じサーバー上に複数のWSSがある、または圧縮の不一致 noServer: true モードを使用する
Hexが 48545450 で始まる WebSocket上の生のHTTP (0x48='H') 競合するアップグレードハンドラーを確認する
コード1006、理由なし 異常なクローズ、多くの場合サーバー側の異常終了 abortHandshake の呼び出しを確認する
単独では動作するが、アプリでは失敗する 他の何かがソケットに書き込んでいる すべてのアップグレードリスナーを監査する

複数のWebSocketServerのバグ

問題

server オプションを使用して、複数の WebSocketServer インスタンスを同じHTTPサーバーにアタッチする場合:

// ❌ BAD - 両方のサーバーがアップグレードリスナーを追加し、競合を引き起こす
const wss1 = new WebSocketServer({ server, path: '/ws' });
const wss2 = new WebSocketServer({ server, path: '/ws/other' });

何が起こるか:

  1. クライアントが /ws に接続する
  2. 両方のアップグレードハンドラーが発火する (Node.js EventEmitterはすべてのリスナーを呼び出す)
  3. wss1 がパスに一致し、アップグレードを正常に処理する
  4. wss2 が一致せず、abortHandshake(socket, 400) を呼び出す
  5. 生の HTTP/1.1 400 Bad Request がWebSocketソケットに書き込まれる
  6. クライアントがHTTPテキストをWebSocketフレームデータとして受信する
  7. 最初のバイト 0x48 ('H') が次のように解釈される: RSV1=1, opcode=8 → 無効なフレーム

解決策

noServer: true を使用し、手動でアップグレードをルーティングする:

// ✅ GOOD - 単一のアップグレードハンドラーが正しいサーバーにルーティングする
const wss1 = new WebSocketServer({ noServer: true, perMessageDeflate: false });
const wss2 = new WebSocketServer({ noServer: true, perMessageDeflate: false });

server.on('upgrade', (request, socket, head) => {
  const pathname = new URL(request.url || '', `http://${request.headers.host}`).pathname;

  if (pathname === '/ws') {
    wss1.handleUpgrade(request, socket, head, (ws) => {
      wss1.emit('connection', ws, request);
    });
  } else if (pathname === '/ws/other') {
    wss2.handleUpgrade(request, socket, head, (ws) => {
      wss2.emit('connection', ws, request);
    });
  } else {
    socket.destroy();
  }
});

デバッグテクニック

生のフレームの検査

ソケットにフックして、実際に受信したバイトを確認する:

ws.on('open', () => {
  const socket = ws._socket;
  const originalPush = socket.push.bind(socket);

  socket.push = function(chunk, encoding) {
    if (chunk) {
      console.log('First 20 bytes (hex):', chunk.slice(0, 20).toString('hex'));
      const byte0 = chunk[0];
      console.log(`FIN: ${!!(byte0 & 0x80)}, RSV1: ${!!(byte0 & 0x40)}, Opcode: ${byte0 & 0x0f}`);

      // Check if it's actually HTTP text
      if (chunk.slice(0, 4).toString() === 'HTTP') {
        console.log('*** RECEIVED RAW HTTP ON WEBSOCKET ***');
      }
    }
    return originalPush(chunk, encoding);
  };
});

主要な16進数のパターン

  • 81 = FIN + テキストフレーム (正常)
  • 82 = FIN + バイナリフレーム (正常)
  • 88 = FIN + クローズフレーム (正常)
  • 48545450 = "HTTP" - WebSocket上の生のHTTP (バグ!)
  • c1 またはビット6が設定された同様のもの = 圧縮されたフレーム (RSV1=1)

よくある間違い

間違い 結果 解決策
server オプションを持つ複数のWSS HTTP 400 がソケットに書き込まれる noServer: true を使用する
perMessageDeflate: true (古い ws のデフォルト) フレームに RSV1 が設定される 明示的に perMessageDeflate: false を設定する
アップグレードヘッダーをチェックしない 圧縮ネゴシエーションを見逃す sec-websocket-extensions ヘッダーをログに記録する
RSV1 エラー = 圧縮と仮定する 生のHTTPの可能性がある バイトがASCII "HTTP"としてデコードされるかどうかを確認する

検証チェックリスト

修正後、以下を確認する:

  • [ ] フレーム検査で RSV1: false であること
  • [ ] アップグレード応答で Extensions header: NONE であること
  • [ ] 生のフレームデータに HTTP/1.1 がないこと
  • [ ] 受信したメッセージが送信されたペイロードサイズと一致すること
  • [ ] 複数のブロードキャストが動作すること (テスト間隔送信)
📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開

Debugging WebSocket Issues

Overview

WebSocket "invalid frame header" errors often stem from raw HTTP being written to an upgraded socket, not actual frame corruption. The most common cause is multiple WebSocketServer instances conflicting on the same HTTP server.

When to Use

  • Error: Invalid WebSocket frame: RSV1 must be clear
  • Error: WS_ERR_UNEXPECTED_RSV_1
  • Error: Invalid frame header
  • WebSocket connects then immediately disconnects with code 1006
  • Server logs success but client receives garbage data

Quick Reference

Symptom Likely Cause Fix
RSV1 must be clear Multiple WSS on same server OR compression mismatch Use noServer: true mode
Hex starts with 48545450 Raw HTTP on WebSocket (0x48='H') Check for conflicting upgrade handlers
Code 1006, no reason Abnormal closure, often server-side abort Check abortHandshake calls
Works isolated, fails in app Something else writing to socket Audit all upgrade listeners

The Multiple WebSocketServer Bug

Problem

When attaching multiple WebSocketServer instances to the same HTTP server using the server option:

// ❌ BAD - Both servers add upgrade listeners, causing conflicts
const wss1 = new WebSocketServer({ server, path: '/ws' });
const wss2 = new WebSocketServer({ server, path: '/ws/other' });

What happens:

  1. Client connects to /ws
  2. BOTH upgrade handlers fire (Node.js EventEmitter calls all listeners)
  3. wss1 matches path, handles upgrade successfully
  4. wss2 doesn't match, calls abortHandshake(socket, 400)
  5. Raw HTTP/1.1 400 Bad Request written to the now-WebSocket socket
  6. Client receives HTTP text as WebSocket frame data
  7. First byte 0x48 ('H') interpreted as: RSV1=1, opcode=8 → invalid frame

Solution

Use noServer: true and manually route upgrades:

// ✅ GOOD - Single upgrade handler routes to correct server
const wss1 = new WebSocketServer({ noServer: true, perMessageDeflate: false });
const wss2 = new WebSocketServer({ noServer: true, perMessageDeflate: false });

server.on('upgrade', (request, socket, head) => {
  const pathname = new URL(request.url || '', `http://${request.headers.host}`).pathname;

  if (pathname === '/ws') {
    wss1.handleUpgrade(request, socket, head, (ws) => {
      wss1.emit('connection', ws, request);
    });
  } else if (pathname === '/ws/other') {
    wss2.handleUpgrade(request, socket, head, (ws) => {
      wss2.emit('connection', ws, request);
    });
  } else {
    socket.destroy();
  }
});

Debugging Techniques

Raw Frame Inspection

Hook into the socket to see actual bytes received:

ws.on('open', () => {
  const socket = ws._socket;
  const originalPush = socket.push.bind(socket);

  socket.push = function(chunk, encoding) {
    if (chunk) {
      console.log('First 20 bytes (hex):', chunk.slice(0, 20).toString('hex'));
      const byte0 = chunk[0];
      console.log(`FIN: ${!!(byte0 & 0x80)}, RSV1: ${!!(byte0 & 0x40)}, Opcode: ${byte0 & 0x0f}`);

      // Check if it's actually HTTP text
      if (chunk.slice(0, 4).toString() === 'HTTP') {
        console.log('*** RECEIVED RAW HTTP ON WEBSOCKET ***');
      }
    }
    return originalPush(chunk, encoding);
  };
});

Key Hex Patterns

  • 81 = FIN + text frame (normal)
  • 82 = FIN + binary frame (normal)
  • 88 = FIN + close frame (normal)
  • 48545450 = "HTTP" - raw HTTP on WebSocket (bug!)
  • c1 or similar with bit 6 set = compressed frame (RSV1=1)

Common Mistakes

Mistake Result Fix
Multiple WSS with server option HTTP 400 written to socket Use noServer: true
perMessageDeflate: true (default in older ws) RSV1 set on frames Explicitly set perMessageDeflate: false
Not checking upgrade headers Miss compression negotiation Log sec-websocket-extensions header
Assuming RSV1 error = compression Could be raw HTTP Check if bytes decode as ASCII "HTTP"

Verification Checklist

After fixing, verify:

  • [ ] RSV1: false in frame inspection
  • [ ] Extensions header: NONE in upgrade response
  • [ ] No HTTP/1.1 in raw frame data
  • [ ] Messages received match sent payload size
  • [ ] Multiple broadcasts work (test interval sends)