fix(relay): 上游握手整包透传客户端请求头并镜像子协议,对齐浏览器直连

- 除 Host/Connection/Upgrade/Sec-WebSocket-* 外,客户端握手头全部原样带上(Cookie / Origin / User-Agent / Referer / Accept-Language / Sec-Fetch-* 等)

- Sec-WebSocket-Protocol 改走 ws 的 protocols 选项,由 ws 负责协商校验
This commit is contained in:
TermiNexus committed 2026-10-11 19:54:22 +08:00
1 parent 5dd5560090
commit 8c7f87b58f
1 file changed
+19 -9
+19 -9
View File
@@ -511,17 +511,27 @@ WsServer.on('connection', (Client, Req) => {
let Upstream = null;
const Pending = []; // 上游未就绪时暂存竞态帧(握手帧常比上游 TLS 建连更早到达),一连上立即按原序补发
// 握手头:把客户端**真实的** Origin / User-Agent / Referer 透传给上游(与浏览器直连时一致)。
// 不能用写死的 origin:真实服务器可能按 Origin 选择构建/配置,写死会让响应与本地构建对不上,
// 表现为客户端握手失败自行断开(close code 1005)。
const UpHeaders = {
'user-agent': Req.headers['user-agent'] || UPSTREAM_HEADERS['user-agent'],
origin: Req.headers['origin'] || UPSTREAM_HEADERS.origin,
referer: Req.headers['referer'] || UPSTREAM_HEADERS.referer,
};
// 握手头:把客户端**真实**的握手请求头整包透传给上游(Cookie / Origin / User-Agent / Referer /
// Accept-Language / Sec-Fetch-* …),让上游看到的握手与「浏览器直连」完全一致。
// 仅排除必须由 ws 自己生成/协商的:Host / Connection / Upgrade / Sec-WebSocket-*
// (其中子协议改走 protocols 选项,由 ws 负责协商与校验)。
const UpHeaders = {};
for (const K of Object.keys(Req.headers)) {
const L = K.toLowerCase();
if (L === 'host' || L === 'connection' || L === 'upgrade') continue;
if (L.indexOf('sec-websocket-') === 0) continue;
UpHeaders[L] = Req.headers[K];
}
if (!UpHeaders['user-agent']) UpHeaders['user-agent'] = UPSTREAM_HEADERS['user-agent'];
if (!UpHeaders['origin']) UpHeaders['origin'] = UPSTREAM_HEADERS.origin;
if (!UpHeaders['referer']) UpHeaders['referer'] = UPSTREAM_HEADERS.referer;
const ClientProto = Req.headers['sec-websocket-protocol'];
const UpProtocols = ClientProto
? String(ClientProto).split(',').map(function (S) { return S.trim(); }).filter(Boolean)
: undefined;
// 转发:客户端帧**原样直发**;上游未就绪时暂存,open 后按原序补发(不重放、不改字节)。
Upstream = new Ws(UPSTREAM, { headers: UpHeaders, rejectUnauthorized: false });
Upstream = new Ws(UPSTREAM, { headers: UpHeaders, protocols: UpProtocols, rejectUnauthorized: false });
Upstream.binaryType = 'nodebuffer';
Upstream.on('open', () => {
State.Log('上游已连接: ' + UPSTREAM + ',补发暂存 ' + Pending.length + ' 帧');