diff --git a/tools/node-tls-proxy/proxy.js b/tools/node-tls-proxy/proxy.js index 5d365275..79d29740 100644 --- a/tools/node-tls-proxy/proxy.js +++ b/tools/node-tls-proxy/proxy.js @@ -2,8 +2,6 @@ const http = require('http'); const https = require('https'); -const http2 = require('http2'); -const tls = require('tls'); const net = require('net'); // ─── 配置 ─────────────────────────────────────────────── @@ -22,34 +20,6 @@ const log = (level, msg, extra = {}) => { const HEALTH_PATH = '/__health'; -// ─── HTTP/2 会话缓存 ───────────────────────────────────── -// 按 host 缓存 h2 session,避免每个请求都建新连接 -const h2Sessions = new Map(); - -function getH2Session(host) { - const existing = h2Sessions.get(host); - if (existing && !existing.closed && !existing.destroyed) { - return existing; - } - const session = http2.connect(`https://${host}`, { - // 不设置自定义 TLS 选项 → 用 Node.js 默认 TLS stack - }); - session.on('error', (err) => { - log('warn', 'h2 session error', { host, error: err.message }); - h2Sessions.delete(host); - }); - session.on('close', () => { - h2Sessions.delete(host); - }); - // 空闲超时自动关闭 - session.setTimeout(IDLE_TIMEOUT, () => { - session.close(); - h2Sessions.delete(host); - }); - h2Sessions.set(host, session); - return session; -} - // ─── 通过 HTTP 代理建立 CONNECT 隧道 ────────────────────── function connectViaProxy(proxyUrl, targetHost, targetPort) { return new Promise((resolve, reject) => { @@ -101,94 +71,14 @@ function connectViaProxy(proxyUrl, targetHost, targetPort) { }); } -// ─── TLS + ALPN 探测:判断上游支持 h2 还是 http/1.1 ───────── -const alpnCache = new Map(); +// ─── 代理请求 ─────────────────────────────────────────── +async function proxyRequest(req, res) { + // 动态确定上游主机 + const targetHost = req.headers['x-forwarded-host'] || UPSTREAM_HOST; -function probeALPN(host) { - const cached = alpnCache.get(host); - if (cached) return Promise.resolve(cached); + log('info', 'proxy_request', { host: targetHost, method: req.method, path: req.url }); - return new Promise((resolve) => { - const socket = tls.connect(443, host, { - ALPNProtocols: ['h2', 'http/1.1'], - servername: host, - timeout: 5000, - }); - socket.once('secureConnect', () => { - const proto = socket.alpnProtocol || 'http/1.1'; - alpnCache.set(host, proto); - socket.destroy(); - resolve(proto); - }); - socket.once('error', () => { - alpnCache.set(host, 'http/1.1'); - socket.destroy(); - resolve('http/1.1'); - }); - socket.once('timeout', () => { - alpnCache.set(host, 'http/1.1'); - socket.destroy(); - resolve('http/1.1'); - }); - }); -} - -// ─── HTTP/2 代理请求 ───────────────────────────────────── -function proxyViaH2(targetHost, req, res) { - const session = getH2Session(targetHost); - - // 构建 h2 请求头 - const headers = { ...req.headers }; - headers[':method'] = req.method; - headers[':path'] = req.url; - headers[':authority'] = targetHost; - headers[':scheme'] = 'https'; - // 移除 HTTP/1.1 专用头 - delete headers['host']; - delete headers['connection']; - delete headers['keep-alive']; - delete headers['proxy-connection']; - delete headers['transfer-encoding']; - delete headers['x-forwarded-host']; - - const h2Req = session.request(headers); - - h2Req.on('response', (h2Headers) => { - const status = h2Headers[':status'] || 502; - // 过滤 h2 伪头 - const respHeaders = {}; - for (const [k, v] of Object.entries(h2Headers)) { - if (!k.startsWith(':')) { - respHeaders[k] = v; - } - } - res.writeHead(status, respHeaders); - h2Req.pipe(res, { end: true }); - }); - - h2Req.on('error', (err) => { - log('error', 'h2 upstream error', { error: err.message, host: targetHost, path: req.url }); - // h2 session 可能坏了,清理缓存 - h2Sessions.delete(targetHost); - if (!res.headersSent) { - res.writeHead(502, { 'content-type': 'application/json' }); - res.end(JSON.stringify({ error: 'h2_upstream_error', message: err.message })); - } - }); - - h2Req.setTimeout(CONNECT_TIMEOUT, () => { - h2Req.close(); - }); - - req.on('close', () => { - if (!h2Req.destroyed) h2Req.close(); - }); - - req.pipe(h2Req, { end: true }); -} - -// ─── HTTP/1.1 代理请求 ──────────────────────────────────── -async function proxyViaH1(targetHost, req, res) { + // 构建上游请求头 const headers = { ...req.headers }; headers.host = targetHost; delete headers['x-forwarded-host']; @@ -205,6 +95,7 @@ async function proxyViaH1(targetHost, req, res) { headers, servername: targetHost, timeout: CONNECT_TIMEOUT, + // 不设置任何自定义 TLS 选项 → Node.js 默认 TLS stack → JA3/JA4 天然匹配 }; let proxyReq; @@ -227,11 +118,17 @@ async function proxyViaH1(targetHost, req, res) { proxyReq = https.request(opts); } + // 上游响应 proxyReq.on('response', (proxyRes) => { const responseHeaders = { ...proxyRes.headers }; delete responseHeaders['connection']; delete responseHeaders['keep-alive']; - delete responseHeaders['transfer-encoding']; + + log('info', 'proxy_response', { + host: targetHost, + status: proxyRes.statusCode, + path: req.url, + }); res.writeHead(proxyRes.statusCode, responseHeaders); proxyRes.pipe(res, { end: true }); @@ -242,8 +139,14 @@ async function proxyViaH1(targetHost, req, res) { }); }); + // 上游连接错误 proxyReq.on('error', (err) => { - log('error', 'h1 upstream error', { error: err.message, host: targetHost, path: req.url, method: req.method }); + log('error', 'upstream request error', { + error: err.message, + host: targetHost, + path: req.url, + method: req.method, + }); if (!res.headersSent) { res.writeHead(502, { 'content-type': 'application/json' }); res.end(JSON.stringify({ error: 'upstream_error', message: err.message })); @@ -262,22 +165,6 @@ async function proxyViaH1(targetHost, req, res) { req.pipe(proxyReq, { end: true }); } -// ─── 代理请求入口 ───────────────────────────────────────── -async function proxyRequest(req, res) { - const targetHost = req.headers['x-forwarded-host'] || UPSTREAM_HOST; - - log('info', 'proxy_request', { host: targetHost, method: req.method, path: req.url }); - - // 探测上游支持的协议,选择 h2 或 h1 - const proto = await probeALPN(targetHost); - - if (proto === 'h2') { - proxyViaH2(targetHost, req, res); - } else { - await proxyViaH1(targetHost, req, res); - } -} - // ─── HTTP 服务器 ───────────────────────────────────────── const server = http.createServer((req, res) => { if (req.url === HEALTH_PATH) { @@ -288,8 +175,6 @@ const server = http.createServer((req, res) => { node: process.version, openssl: process.versions.openssl, uptime: process.uptime(), - h2Sessions: h2Sessions.size, - alpnCache: Object.fromEntries(alpnCache), })); return; } @@ -314,7 +199,6 @@ server.listen(LISTEN_PORT, LISTEN_HOST, () => { proxy: UPSTREAM_PROXY || '(direct)', node: process.version, openssl: process.versions.openssl, - features: ['dynamic-host', 'h2-auto', 'alpn-probe'], }); }); @@ -324,11 +208,6 @@ function shutdown(signal) { if (shuttingDown) return; shuttingDown = true; log('info', `received ${signal}, shutting down`); - // 关闭所有 h2 session - for (const [host, session] of h2Sessions) { - session.close(); - } - h2Sessions.clear(); server.close(() => { log('info', 'server closed'); process.exit(0);