You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Docker容器间WebSocket连接失败:4406子协议不兼容问题排查

问题

开发团队GraphQL订阅功能时,搭建API Gateway WebSocket API并通过VPC Link转发请求至ECS集群后端服务,但VPC Link会将WebSocket请求降级为HTTP。为此用ExpressJS搭建HTTP代理服务器,尝试连接基于graphql-ws的WebSocket后端,本地Docker容器(代理与后端)间无法建立WebSocket连接,触发4406错误:Subprotocol not acceptable。直接连接后端服务可正常工作,问题出在代理端。已尝试在WebSocket构造函数中指定/不指定graphql-transport-ws协议、使用自定义Docker网络,均无法解决问题。

代理端TypeScript代码:

import express from 'express';
import WebSocket from 'ws';

const app = express();

app.put('/', (req, res) => {
    const body = req.body ? req.body : {};
    const socket = new WebSocket(wsEndpoint, {
      headers: {
        'Sec-WebSocket-Version': 13,
        'Sec-WebSocket-Key': 'Ab2SMOa5hg0YUyC1OELtyQ==',
        Connection: 'Upgrade',
        Upgrade: 'websocket',
        'Sec-WebSocket-Extensions':
          'permessage-deflate; client_max_window_bits',
        Host: 'localhost',
      },
      protocol: 'graphql-transport-ws',
    });
    socket.on('error', (err) => {
      console.log(JSON.stringify(err));
    });
    socket.on('open', (s: any) => {
      console.log('socket open');
    });
    socket.on('close', (code, reason) => {
      console.log(`socket close: ${code}, reason: ${reason.toString()}`);
    });
    socket.on('error', (error) => {
      console.log('socket error');
      console.log(error);
    });
    socket.on('upgrade', (socket: any, request: any) => {
      console.log('socket upgrade');
      console.log(request);
    });
    res.status(200).json({
      message: 'Connection established to WebSocket server',
    });
  });

日志片段:

Proxy server listening on port 80 <-- app.listen() on port 80
socket upgrade <-- starts once I make the PUT request via Postman
socket open
socket close: 4406, reason: Subprotocol not acceptable
解决方案

问题根源

  1. 错误的请求处理逻辑:当前代理在PUT请求中创建WebSocket连接,不符合WebSocket的握手流程——WebSocket通过GET请求加Upgrade头触发握手,而非PUT请求。
  2. 硬编码的握手头:手动指定Sec-WebSocket-Key等头是错误的,该Key由发起请求的客户端生成,代理需直接转发客户端的原始头信息,不能硬写固定值。
  3. 子协议传递缺失:未正确转发客户端发送的Sec-WebSocket-Protocol头,后端graphql-ws服务需确认该子协议才能建立连接。

修正后的代理代码

import express from 'express';
import { createServer } from 'http';
import WebSocket from 'ws';

const app = express();
const server = createServer(app);
// Docker环境下用后端容器服务名代替localhost
const WS_BACKEND_URL = 'ws://backend-service:4000/graphql';

// 处理WebSocket升级请求
server.on('upgrade', (req, socket, head) => {
  // 提取客户端请求中的子协议列表
  const clientProtocols = req.headers['sec-websocket-protocol']?.toString().split(', ') || [];
  
  // 建立到后端的WebSocket连接,传递客户端原始头和子协议
  const ws = new WebSocket(WS_BACKEND_URL, {
    headers: {
      ...req.headers,
      // Docker环境下覆盖Host为后端服务地址
      Host: 'backend-service:4000'
    },
    // 优先匹配客户端指定的graphql-transport-ws子协议
    protocol: clientProtocols.includes('graphql-transport-ws') ? 'graphql-transport-ws' : undefined
  });

  // 后端连接建立后,完成客户端的握手响应
  ws.on('open', () => {
    socket.write(`HTTP/1.1 101 Switching Protocols\r\n` +
                 `Upgrade: websocket\r\n` +
                 `Connection: Upgrade\r\n` +
                 `Sec-WebSocket-Accept: ${req.headers['sec-websocket-accept']}\r\n` +
                 `Sec-WebSocket-Protocol: ${ws.protocol}\r\n\r\n`);
    
    // 建立客户端与后端的双向消息管道
    socket.pipe(ws);
    ws.pipe(socket);
  });

  ws.on('error', (err) => {
    console.error('后端WebSocket连接错误:', err);
    socket.destroy(err);
  });

  socket.on('error', (err) => {
    console.error('客户端Socket错误:', err);
    ws.close();
  });
});

// 保留基础路由(按需使用)
app.get('/', (req, res) => {
  res.send('WebSocket代理服务器运行中');
});

const PORT = 80;
server.listen(PORT, () => {
  console.log(`Proxy server listening on port ${PORT}`);
});

关键修改说明

  • 改用HTTP服务器的upgrade事件处理WebSocket握手,遵循标准代理流程。
  • 转发客户端所有原始请求头,确保握手信息完整传递。
  • 建立客户端与后端的双向消息管道,实现实时数据转发。
  • Docker环境下使用后端容器服务名代替localhost,保证容器间网络互通。

内容的提问来源于stack exchange,提问作者resync

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 06:10:35