Hướng Dẫn Sử Dụng
使用 Node.js 测试 wss:// WebSocket 代理并诊断错误

目录
为什么需要单独测试 WebSocket 代理?
代理能够访问 HTTPS 网站,并不意味着它能稳定维持安全 WebSocket 连接。对于聊天、通知或实时仪表盘应用,要求不仅是成功建立连接,还要能够长时间稳定地交换数据。
要测试代理是否支持 WebSocket,需要验证代理隧道、TLS 协商、WebSocket 握手以及连接建立后的数据传输能力。本文使用 Node.js 将这些阶段分开,帮助准确诊断原因,而不是将所有错误都归咎于代理。
代理如何处理 wss:// 连接?
wss:// 是运行在 TLS 之上的 WebSocket。在通过 HTTP 代理的常见流程中,客户端使用 CONNECT 方法请求代理建立通往目标服务器的隧道。隧道获准建立后,客户端与服务器进行 TLS 协商,然后在加密连接内发送 WebSocket 握手请求。
因此,HTTP 代理不一定需要理解 WebSocket 内容才能转发 wss://。不过,代理的目标访问策略、身份验证、超时和连接限制仍可能产生影响。
- HTTP 代理:通常使用 CONNECT 建立通往 wss:// 目标的隧道。
- HTTPS 代理:客户端到代理的连接也受到 TLS 保护;这一层与通往目标服务器的 TLS 是独立的。
- SOCKS 代理:使用 SOCKS 协议转发 TCP 连接,而不是 HTTP 的 CONNECT 方法。
- 反向代理:位于应用服务器前端;即使客户端侧的代理正常工作,这里的 WebSocket 转发配置仍可能出错。
准备 Node.js wss 代理测试环境
使用以下命令安装所需的库:
npm install ws https-proxy-agent socks-proxy-agent
示例使用 check-wss.mjs 文件,对应 ES modules 语法。请使用由你管理或已获准测试的 WebSocket 端点,确保其证书有效,并且符合应用的协议要求。
程序通过环境变量接收配置:
- WSS_URL:wss:// 地址,包含正确的路径和必要参数。
- PROXY_URL:代理 URL,包含协议、主机、端口以及身份验证信息(如有)。
- TEST_TIMEOUT_MS:由操作人员选择的超时时间,单位为毫秒。
不要将代理密码保存在源代码中。如果用户名或密码包含特殊字符,构建 URL 时应对各组成部分分别进行正确编码。记录日志时,需要隐藏身份验证信息、令牌和敏感参数。
用于测试连接和 Pong 响应的 Node.js 代码
以下示例适用于 HTTP 或 HTTPS 代理。每段代码均单独列出;请按顺序复制到同一个 check-wss.mjs 文件中。
import WebSocket from 'ws'; import { HttpsProxyAgent } from 'https-proxy-agent'; import { randomUUID } from 'node:crypto';
const target = new URL(process.env.WSS_URL); const proxy = new URL(process.env.PROXY_URL); const timeout = Number(process.env.TEST_TIMEOUT_MS); if (target.protocol !== 'wss:') throw new Error('WSS_URL 必须使用 wss://'); if (!['http:', 'https:'].includes(proxy.protocol)) throw new Error('此示例要求使用 HTTP 或 HTTPS 代理'); if (!Number.isSafeInteger(timeout) || timeout <= 0) throw new Error('TEST_TIMEOUT_MS 必须为正整数');
const agent = new HttpsProxyAgent(proxy); const token = randomUUID(); let stage = 'connecting'; let finished = false; let timer; const ws = new WebSocket(target, { agent, handshakeTimeout: timeout });
function finish(ok, message) { if (finished) return; finished = true; clearTimeout(timer); console.log(JSON.stringify({ ok, stage, message })); process.exitCode = ok ? 0 : 1; ws.terminate(); agent.destroy(); }
timer = setTimeout(() => finish(false, '已达到操作人员配置的超时时间'), timeout); ws.on('open', () => { stage = 'waiting-pong'; console.log('WebSocket 握手已完成'); ws.ping(token); }); ws.on('pong', data => { if (data.toString() === token) finish(true, '已收到与通过连接发送的 Ping 相匹配的 Pong'); });
ws.on('unexpected-response', (request, response) => { finish(false, 'HTTP 响应未完成 WebSocket 握手:' + response.statusCode); response.destroy(); }); ws.on('error', error => finish(false, [error.code, error.message].filter(Boolean).join(': '))); ws.on('close', (code, reason) => { if (!finished) finish(false, '连接已关闭:' + code + ' ' + reason.toString()); });
在 shell 或进程管理工具中设置环境变量后,运行 node check-wss.mjs。不要将包含密码的完整命令写入共享 shell 历史记录。
如何解读结果
open 事件确认 WebSocket 握手已完成。内容与 Ping 匹配的 Pong 进一步确认控制帧已通过该连接完成往返。代码使用 terminate() 结束短时测试,这并不是生产应用正常关闭连接的范例。
Ping/Pong 不能替代业务测试。测试成功并不能证明登录、频道订阅或事件接收正常工作。反过来,未收到 Pong 也不足以断定代理不支持 WebSocket;还需要检查服务器、系统负载和所选超时时间。
如果使用 SOCKS 代理
将 agent 的导入和初始化部分替换为 import { SocksProxyAgent } from 'socks-proxy-agent'; 和 const agent = new SocksProxyAgent(proxy);。同时修改协议检查,使其接受相应的 SOCKS 类型。
在此库中,socks5:// 在客户端解析目标域名,而 socks5h:// 将目标域名交给代理解析。当两个环境的 DNS 或网络访问能力不同时,这一区别尤为重要。
验证连接确实经过代理
不应仅因设置了操作系统环境变量,就默认库会使用代理。在上述示例中,agent 被直接传入 WebSocket,以明确连接路径。
- 如果有访问权限,核对代理上的 CONNECT 日志或连接会话日志。
- 检查目标服务器基础设施观察到的源地址。
- 使用受控端点,通过关联 ID 将客户端和服务器日志对应起来。
- 在测试环境中尝试配置一个不可用的代理:程序应报错,而不是自动回退为直接连接。
如果服务器位于反向代理或负载均衡器之后,应用看到的源地址可能是中间层的地址。这时,需要在实际接收连接的位置核对日志。
按阶段诊断通过代理连接 WebSocket 时的错误
连接代理时出错
ENOTFOUND 通常与域名解析有关;ECONNREFUSED 表示连接被拒绝;ETIMEDOUT 表示某项网络操作超时。处理前,需要先确定错误发生在客户端到代理,还是代理到目标的链路上。
检查主机名、端口、协议、网络访问权限和代理服务状态。混淆 HTTP、HTTPS 和 SOCKS 协议可能导致连接一开始就失败。
身份验证或隧道建立错误
如果代理要求身份验证,请检查账号、密码、URL 编码方式以及 agent 支持的身份验证方法。企业环境中的代理可能要求使用某种验证机制,仅靠 URL 中的基本登录凭据无法满足要求。
隧道也可能因目标访问策略或账号限制而被拒绝。应核对代理日志,而不是直接认定整个服务都不支持 wss://。
TLS 证书错误
TLS 错误可能出现在连接 HTTPS 代理时,也可能出现在验证 WebSocket 服务器时。常见原因包括证书链不完整、主机名不匹配、证书过期,或内部 CA 尚未被信任。
不要通过关闭证书验证来将测试视为成功。应修正证书配置,或添加经管理员批准的 CA。如果使用 HTTPS 代理,请准确识别发生错误的是哪一层 TLS。
收到 HTTP 响应,但 WebSocket 未建立
unexpected-response 事件表示收到了未能完成 WebSocket 握手的 HTTP 响应。请根据服务器文档检查 URL、路径、重定向、登录会话、Origin 和子协议。
响应可能来自代理、反向代理或目标应用。不要仅凭状态码推断响应来源;需要核对日志和响应信息。如果应用要求身份验证请求头,请将其正确添加到连接选项中,同时不要将秘密值写入日志。
连接建立后关闭或停止接收数据
这类错误通常与空闲超时、心跳、身份验证会话过期、连接限制或应用主动关闭会话有关。应记录连接建立时间、最后一次接收数据的时间、关闭码和关闭原因。
表示异常终止的关闭码并不能确定是哪台设备导致了错误。应对照客户端、代理和服务器在同一时间点的日志,找出发生中断的链路。
实时应用的稳定性测试
完成 Ping/Pong 测试后,使用测试账号补充业务流程:进行身份验证、订阅频道,并接收一条带有关联 ID 的事件。在不了解应用协议之前,不要随意发送消息。
- 获取对照结果:如果网络策略允许,在同一端点上分别测试直接连接和通过代理连接。
- 测试空闲状态:根据实际需求维持会话,以观察连接关闭情况。
- 测试心跳:区分 WebSocket Ping/Pong 与应用层心跳消息。
- 测试恢复能力:验证中断后的重新连接、重新身份验证和重新订阅频道。
- 进行受控负载测试:仅在获准范围内增加连接数,并监控两端资源。
不要试图通过设置过于频繁的心跳来解决所有错误。合适的间隔应根据服务器策略、中间层超时和运行成本来确定。
结论
可靠的 WebSocket 代理测试必须确认连接路径、完成 TLS 协商、建立 WebSocket 连接并验证双向数据传输。对于实际应用,还需要进一步测试业务功能和会话维持能力。
遇到通过代理连接 WebSocket 时的错误,应按阶段分类,而不是同时修改多项配置。这种方法有助于准确判断问题出在代理、证书、反向代理还是应用,并保留必要的安全要求。