'use strict'; const http = require('http'); const https = require('https'); const net = require('net'); // ─── 配置 ─────────────────────────────────────────────── const UPSTREAM_HOST = process.env.UPSTREAM_HOST || 'api.anthropic.com'; const LISTEN_PORT = parseInt(process.env.PROXY_PORT || '3456', 10); const LISTEN_HOST = process.env.PROXY_HOST || '127.0.0.1'; // 可选:上游 HTTPS 代理(仅支持 HTTP CONNECT 隧道) const UPSTREAM_PROXY = process.env.UPSTREAM_PROXY || ''; // 连接超时(ms) const CONNECT_TIMEOUT = parseInt(process.env.CONNECT_TIMEOUT || '30000', 10); // 空闲超时(ms)— 对 SSE 长连接要足够大 const IDLE_TIMEOUT = parseInt(process.env.IDLE_TIMEOUT || '600000', 10); // ─── 日志 ─────────────────────────────────────────────── const log = (level, msg, extra = {}) => { const entry = { time: new Date().toISOString(), level, msg, ...extra, }; process.stderr.write(JSON.stringify(entry) + '\n'); }; // ─── 健康检查路径 ───────────────────────────────────────── const HEALTH_PATH = '/__health'; // ─── 通过 HTTP 代理建立 CONNECT 隧道 ────────────────────── function connectViaProxy(proxyUrl, targetHost, targetPort) { return new Promise((resolve, reject) => { const proxy = new URL(proxyUrl); const proxyPort = parseInt(proxy.port || '80', 10); const conn = net.connect(proxyPort, proxy.hostname, () => { const connectReq = `CONNECT ${targetHost}:${targetPort} HTTP/1.1\r\n` + `Host: ${targetHost}:${targetPort}\r\n`; // 代理认证 const auth = proxy.username ? `Proxy-Authorization: Basic ${Buffer.from( `${decodeURIComponent(proxy.username)}:${decodeURIComponent(proxy.password || '')}` ).toString('base64')}\r\n` : ''; conn.write(connectReq + auth + '\r\n'); }); conn.once('error', reject); conn.setTimeout(CONNECT_TIMEOUT, () => { conn.destroy(new Error('proxy CONNECT timeout')); }); // 读取代理响应 let buf = ''; const onData = (chunk) => { buf += chunk.toString(); const idx = buf.indexOf('\r\n\r\n'); if (idx === -1) return; // 头还没完整 conn.removeListener('data', onData); const statusLine = buf.slice(0, buf.indexOf('\r\n')); const statusCode = parseInt(statusLine.split(' ')[1], 10); if (statusCode === 200) { conn.setTimeout(0); // 清除超时 // 如果头之后有残余数据,先 unshift 回去 const remainder = buf.slice(idx + 4); if (remainder.length > 0) { conn.unshift(Buffer.from(remainder)); } resolve(conn); } else { conn.destroy(); reject(new Error(`proxy CONNECT failed: ${statusLine}`)); } }; conn.on('data', onData); }); } // ─── 构建上游请求选项 ────────────────────────────────────── function buildUpstreamOptions(req) { // 动态确定上游主机:优先使用 X-Forwarded-Host,回退到 UPSTREAM_HOST 配置 const targetHost = req.headers['x-forwarded-host'] || UPSTREAM_HOST; // 复制头,重写 host 为实际目标 const headers = { ...req.headers }; headers.host = targetHost; // 移除内部头和 hop-by-hop 头 delete headers['x-forwarded-host']; delete headers['connection']; delete headers['keep-alive']; delete headers['proxy-connection']; delete headers['transfer-encoding']; return { hostname: targetHost, port: 443, path: req.url, method: req.method, headers, // 关键:不设置任何自定义 TLS 选项 // 让 Node.js 使用默认 TLS stack → JA3/JA4 天然匹配 servername: targetHost, // SNI timeout: CONNECT_TIMEOUT, }; } // ─── 代理请求 ─────────────────────────────────────────── async function proxyRequest(req, res) { const opts = buildUpstreamOptions(req); let proxyReq; if (UPSTREAM_PROXY) { // 通过代理建立隧道,然后 TLS 握手 try { const socket = await connectViaProxy(UPSTREAM_PROXY, UPSTREAM_HOST, 443); opts.socket = socket; opts.agent = false; // 使用自定义 socket,不走连接池 proxyReq = https.request(opts); } catch (err) { log('error', 'proxy tunnel failed', { error: err.message }); if (!res.headersSent) { res.writeHead(502, { 'content-type': 'application/json' }); res.end(JSON.stringify({ error: 'proxy_tunnel_error', message: err.message })); } return; } } else { proxyReq = https.request(opts); } // 上游响应 proxyReq.on('response', (proxyRes) => { // 过滤 hop-by-hop 头 const responseHeaders = { ...proxyRes.headers }; delete responseHeaders['connection']; delete responseHeaders['keep-alive']; delete responseHeaders['transfer-encoding']; // Node.js 会自动处理 res.writeHead(proxyRes.statusCode, responseHeaders); // SSE / 流式透传:逐块 pipe proxyRes.pipe(res, { end: true }); proxyRes.on('error', (err) => { log('error', 'upstream response error', { error: err.message }); res.end(); }); }); // 上游连接错误 proxyReq.on('error', (err) => { log('error', 'upstream request error', { error: err.message, 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 })); } }); // 超时 proxyReq.on('timeout', () => { log('warn', 'upstream request timeout', { path: req.url }); proxyReq.destroy(new Error('upstream timeout')); }); // 客户端断开时中止上游请求 req.on('close', () => { if (!proxyReq.destroyed) { proxyReq.destroy(); } }); // 将客户端请求体 pipe 到上游 req.pipe(proxyReq, { end: true }); } // ─── HTTP 服务器 ───────────────────────────────────────── const server = http.createServer((req, res) => { // 健康检查 if (req.url === HEALTH_PATH) { res.writeHead(200, { 'content-type': 'application/json' }); res.end(JSON.stringify({ status: 'ok', upstream: UPSTREAM_HOST, node: process.version, openssl: process.versions.openssl, uptime: process.uptime(), })); return; } proxyRequest(req, res).catch((err) => { log('error', 'unhandled proxy error', { error: err.message }); if (!res.headersSent) { res.writeHead(500, { 'content-type': 'application/json' }); res.end(JSON.stringify({ error: 'internal_error' })); } }); }); // SSE 长连接:禁用 server 级超时(由上游控制) server.timeout = 0; server.keepAliveTimeout = IDLE_TIMEOUT; server.headersTimeout = 60000; server.listen(LISTEN_PORT, LISTEN_HOST, () => { log('info', 'node-tls-proxy started', { listen: `${LISTEN_HOST}:${LISTEN_PORT}`, upstream: `${UPSTREAM_HOST}:443`, proxy: UPSTREAM_PROXY || '(direct)', node: process.version, openssl: process.versions.openssl, }); }); // ─── 优雅关闭 ───────────────────────────────────────────── let shuttingDown = false; function shutdown(signal) { if (shuttingDown) return; shuttingDown = true; log('info', `received ${signal}, shutting down`); server.close(() => { log('info', 'server closed'); process.exit(0); }); // 强制退出兜底 setTimeout(() => process.exit(1), 5000); } process.on('SIGTERM', () => shutdown('SIGTERM')); process.on('SIGINT', () => shutdown('SIGINT')); // 未捕获异常不要崩进程 process.on('uncaughtException', (err) => { log('error', 'uncaught exception', { error: err.message, stack: err.stack }); }); process.on('unhandledRejection', (reason) => { log('error', 'unhandled rejection', { error: String(reason) }); });