MeWrite Docs

WebSocket: Connection failed

WebSocket接続に失敗した場合のエラー

概要

WebSocket接続の確立に失敗した場合に発生するエラーです。ハンドシェイクの失敗、プロキシの問題、TLSの問題などが原因です。

エラーメッセージ

WebSocket connection to 'wss://example.com/ws' failed: Error during WebSocket handshake: Unexpected response code: 400

または

WebSocket is closed before the connection is established.

原因

  1. プロトコルの不一致: ws/wss の誤り
  2. プロキシ設定: WebSocketがブロックされている
  3. CORS問題: オリジンが許可されていない
  4. サーバー設定: WebSocketのアップグレードが無効

解決策

1. クライアント側の実装

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// 基本的な接続
const ws = new WebSocket('wss://example.com/ws');

ws.onopen = () => {
  console.log('Connected');
};

ws.onclose = (event) => {
  console.log('Closed:', event.code, event.reason);
};

ws.onerror = (error) => {
  console.error('Error:', error);
};

ws.onmessage = (event) => {
  console.log('Message:', event.data);
};

2. 再接続ロジック

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class ReconnectingWebSocket {
  constructor(url) {
    this.url = url;
    this.reconnectDelay = 1000;
    this.maxReconnectDelay = 30000;
    this.connect();
  }

  connect() {
    this.ws = new WebSocket(this.url);

    this.ws.onopen = () => {
      console.log('Connected');
      this.reconnectDelay = 1000;  // リセット
    };

    this.ws.onclose = (event) => {
      if (event.code !== 1000) {  // 正常終了以外
        this.scheduleReconnect();
      }
    };

    this.ws.onerror = () => {
      this.ws.close();
    };
  }

  scheduleReconnect() {
    console.log(`Reconnecting in ${this.reconnectDelay}ms...`);
    setTimeout(() => {
      this.reconnectDelay = Math.min(
        this.reconnectDelay * 2,
        this.maxReconnectDelay
      );
      this.connect();
    }, this.reconnectDelay);
  }
}

3. Nginx の WebSocket 設定

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
location /ws {
    proxy_pass http://backend;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;

    # タイムアウト設定
    proxy_read_timeout 86400s;
    proxy_send_timeout 86400s;
}

4. Node.js サーバー(ws)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const WebSocket = require('ws');
const http = require('http');

const server = http.createServer();
const wss = new WebSocket.Server({ server });

wss.on('connection', (ws, req) => {
  console.log('Client connected:', req.socket.remoteAddress);

  ws.on('message', (message) => {
    console.log('Received:', message);
    ws.send(`Echo: ${message}`);
  });

  ws.on('close', () => {
    console.log('Client disconnected');
  });

  // Keep-alive ping
  const pingInterval = setInterval(() => {
    if (ws.readyState === WebSocket.OPEN) {
      ws.ping();
    }
  }, 30000);

  ws.on('close', () => clearInterval(pingInterval));
});

server.listen(3000);

5. Socket.IO(フォールバック付き)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// サーバー
const io = require('socket.io')(server, {
  cors: {
    origin: 'https://myapp.com',
    methods: ['GET', 'POST']
  },
  transports: ['websocket', 'polling']
});

io.on('connection', (socket) => {
  console.log('Connected:', socket.id);

  socket.on('disconnect', (reason) => {
    console.log('Disconnected:', reason);
  });
});

// クライアント
import { io } from 'socket.io-client';

const socket = io('wss://example.com', {
  transports: ['websocket'],
  reconnection: true,
  reconnectionAttempts: 5,
  reconnectionDelay: 1000
});

6. AWS ALB での WebSocket

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# SAM template
LoadBalancer:
  Type: AWS::ElasticLoadBalancingV2::LoadBalancer
  Properties:
    Type: application

TargetGroup:
  Type: AWS::ElasticLoadBalancingV2::TargetGroup
  Properties:
    Protocol: HTTP
    TargetType: ip
    # スティッキーセッション有効
    TargetGroupAttributes:
      - Key: stickiness.enabled
        Value: "true"
      - Key: stickiness.type
        Value: lb_cookie

7. CloudFront での WebSocket

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
{
  "Origins": [
    {
      "DomainName": "backend.example.com",
      "CustomOriginConfig": {
        "OriginProtocolPolicy": "https-only"
      }
    }
  ],
  "CacheBehaviors": [
    {
      "PathPattern": "/ws/*",
      "ViewerProtocolPolicy": "https-only",
      "AllowedMethods": ["GET", "HEAD", "OPTIONS", "PUT", "POST", "PATCH", "DELETE"],
      "CachePolicyId": "4135ea2d-6df8-44a3-9df3-4b5a84be39ad"
    }
  ]
}

8. HTTPS と WSS

1
2
3
// HTTPS ページでは WSS を使用
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const ws = new WebSocket(`${protocol}//${window.location.host}/ws`);

9. デバッグ

1
2
3
4
5
6
7
8
9
// Chrome DevTools
// Network タブで WS をフィルタ
// Messages タブでフレームを確認

// ブラウザコンソール
const ws = new WebSocket('wss://example.com/ws');
ws.addEventListener('open', () => console.log('open'));
ws.addEventListener('close', e => console.log('close', e));
ws.addEventListener('error', e => console.log('error', e));

10. ハートビート

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// クライアント
setInterval(() => {
  if (ws.readyState === WebSocket.OPEN) {
    ws.send(JSON.stringify({ type: 'ping' }));
  }
}, 30000);

// サーバー
ws.on('message', (data) => {
  const message = JSON.parse(data);
  if (message.type === 'ping') {
    ws.send(JSON.stringify({ type: 'pong' }));
  }
});

よくある間違い

  • HTTP ページで WSS を使用(Mixed Content)
  • プロキシがConnection: upgradeをストリップしている
  • ロードバランサーのタイムアウトが短い
  • CORSの設定漏れ

WebSocket の他のエラー

最終更新: 2025-12-09