Node.js中Socket.IO v2.5.0客户端WebSocket 403错误的原因与解决
Node.js Socket.IO客户端WebSocket 403错误排查与解决
问题描述
使用Node.js运行基于Socket.IO v2.5.0和socket.io-wildcard库的客户端代码时,终端返回Unexpected server response: 403错误,但相同逻辑在浏览器中运行正常,需排查错误成因并找到解决方案。
终端报错信息
Error: websocket error at Transport.onError (/home/BoMbO/node_modules/engine.io-client/lib/transport.js:68:13) at ws.onerror (/home/BoMbO/node_modules/engine.io-client/lib/transports/websocket.js:163:10) at WebSocket.onError (/home/BoMbO/node_modules/ws/lib/event-target.js:140:16) at WebSocket.emit (node:events:513:28) at abortHandshake (/home/BoMbO/node_modules/ws/lib/websocket.js:731:15) at ClientRequest.<anonymous> (/home/BoMbO/node_modules/ws/lib/websocket.js:604:7) at ClientRequest.emit (node:events:513:28) at HTTPParser.parserOnIncomingClient [as onIncoming] (node:_http_client:701:27) at HTTPParser.parserOnHeadersComplete (node:_http_common:119:17) at TLSSocket.socketOnData (node:_http_client:542:22) { type: 'TransportError', description: ErrorEvent { target: WebSocket { _events: [Object: null prototype], _eventsCount: 4, _maxListeners: undefined, _binaryType: 'arraybuffer', _closeCode: 1006, _closeFrameReceived: false, _closeFrameSent: false, _closeMessage: '', _closeTimer: null, _extensions: {}, _protocol: '', _readyState: 2, _receiver: null, _sender: null, _socket: null, _bufferedAmount: 0, _isServer: false, _redirects: 0, _url: 'wss://ws2.basqu.com/socket.io/?EIO=3&transport=websocket', _req: [ClientRequest], [Symbol(kCapture)]: false }, type: 'error', message: 'Unexpected server response: 403', error: Error: Unexpected server response: 403 at ClientRequest.<anonymous> (/home/BoMbO/node_modules/ws/lib/websocket.js:604:7) at ClientRequest.emit (node:events:513:28) at HTTPParser.parserOnIncomingClient [as onIncoming] (node:_http_client:701:27) at HTTPParser.parserOnHeadersComplete (node:_http_common:119:17) at TLSSocket.socketOnData (node:_http_client:542:22) at TLSSocket.emit (node:events:513:28) at addChunk (node:internal/streams/readable:324:12) at readableAddChunk (node:internal/streams/readable:297:9) at Readable.push (node:internal/streams/readable:234:10) at TLSWrap.onStreamRead (node:internal/stream_base_commons:190:23) } }
Node.js客户端代码
const socket = require('socket.io-client')('wss://ws2.basqu.com/', { path: '/socket.io', reconnection: true, // options of the Engine.IO client transports: ['websocket'], transportOptions: { websocket: { extraHeaders: { origin: 'https://basqu.com' } } }, upgrade: false, rememberUpgrade: true, // options for Node.js rejectUnauthorized: false, extraHeaders: { origin: 'https://basqu.com' } }); socket.on('connect', () =>{ const sid = socket.id; console.log(`Connected with session ID: ${sid}`) socket.emit("authorization",{"session":"FlaqiaG...iiii..loveee.you....OX","isDemo":1}); socket.on("instruments/list", () => { console.log('received: instruments/list') }); socket.on("s_authorization",() => { console.log('received: s_authorization') }); let lastTickTime = 0; // track the last time "tick" was sent function sendTick() { const now = Date.now(); const elapsedTime = now - lastTickTime; if (elapsedTime >= 60000) { // send "tick" if at least 1 minute has elapsed socket.emit('tick'); lastTickTime = now; } } setInterval(sendTick, 500); socket.on("orders/closed/list",() => { console.log('received orders/closed/list') }); socket.on("orders/opened/list",() => { console.log('received order/opened/list') }); socket.emit("indicator/list"); socket.emit("drawing/load"); socket.emit("pending/list"); socket.emit("instruments/update",{ "asset":"AUDCAD_otc", "period":60 }); socket.on("s_drawing/load", () => { console.log('received: s_drawing/load') }); socket.on("settings/list",() => { console.log('received: settings/list') }); socket.on("s_balance/list",() => { console.log('received: s_balance/list') }); socket.on("s_indicator/list",() => { console.log('received: s_indicator/list') }); socket.on("s_chart_notification/get",() => { console.log('received: s_chart_notification/get') }); socket.on("pending/list",() => { console.log('received: pending/list') }); socket.on("history/list/v2",() => { console.log('received: history/list/v2') }); socket.on("quotes/stream",(data) => { console.log(`received quotes/stream event with data: ${data}`); const utf8Data = new TextDecoder().decode(data); console.log("Received UTF-8 encoded data: ", utf8Data); }); socket.on("depth/change",() => { console.log('received: depth/change') }); socket.emit("chart_notification/get"); socket.emit("depth/follow","AUDCAD_otc"); socket.emit("settings/store",{"chartId":"graph","settings":{"chartId":"graph","chartType":2,"currentExpirationTime":0,"isFastOption":false,"isFastAmountOption":false,"isIndicatorsMinimized":false,"isIndicatorsShowing":true,"isShortBetElement":false,"chartPeriod":4,"currentAsset":{"symbol":"AUDCAD_otc"},"dealValue":1000,"dealPercentValue":1,"isVisible":true,"timePeriod":60,"gridOpacity":8,"isAutoScrolling":1,"isOneClickTrade":true,"upColor":"#0FAF59","downColor":"#FF6251"}}); }); socket.on('*',(event,data) => { console.log('Received event: ${event}, with data: ${data}') }); socket.on('disconnect', (reason) => { console.log(reason) }); socket.on('connect_error',(err) =>{ console.log(err) })
错误成因分析
- 请求头差异:浏览器发起WebSocket请求时会自动附带
User-Agent、Referer等默认请求头,Node.js客户端默认不包含这些头,服务器可能以此识别非浏览器请求并返回403拦截。 - 传输方式强制限制:代码中设置
transports: ['websocket']并禁用升级(upgrade: false),而浏览器通常先通过HTTP Polling建立连接再升级到WebSocket,服务器可能对直接WebSocket握手有额外验证逻辑。 - 跨域验证逻辑:服务器的CORS验证可能不仅检查
Origin头,还会结合其他请求头或会话信息,Node.js客户端未完全模拟浏览器的请求头组合。 - 握手阶段凭证缺失:服务器可能在WebSocket握手阶段就需要验证会话凭证(如Cookie),但当前代码仅在连接成功后发送
authorization事件,未在握手时传递必要凭证。
解决方案
1. 补充完整请求头
在客户端配置的extraHeaders中添加浏览器常用的请求头,模拟浏览器请求:
extraHeaders: { 'Origin': 'https://basqu.com', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36', 'Referer': 'https://basqu.com/' }
注意:全局extraHeaders会覆盖transportOptions.websocket.extraHeaders,只需设置一处即可。
2. 恢复默认传输升级逻辑
去掉upgrade: false,允许客户端先通过HTTP Polling建立连接,再升级到WebSocket,和浏览器行为保持一致:
// 移除或注释掉这行 // upgrade: false,
3. 传递握手阶段所需凭证
如果服务器在握手时需要Cookie或其他凭证,可在extraHeaders中添加对应的头,例如:
extraHeaders: { 'Origin': 'https://basqu.com', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36', 'Cookie': 'your_session_cookie_here' }
4. 调试请求差异
使用抓包工具(如Charles、Wireshark)对比浏览器和Node.js客户端的WebSocket握手请求,找出服务器验证所需的关键请求头,补充到客户端配置中。
5. 确认版本兼容性
确保客户端使用的Socket.IO版本(v2.5.0)与服务器端版本完全兼容,版本不匹配可能导致握手协议不一致,引发403错误。
内容的提问来源于stack exchange,提问作者Exboy
相关产品推荐
相关产品推荐

