'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'; const UPSTREAM_PROXY = process.env.UPSTREAM_PROXY || ''; const CONNECT_TIMEOUT = parseInt(process.env.CONNECT_TIMEOUT || '30000', 10); 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); 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); }); } // ─── 代理请求 ─────────────────────────────────────────── 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 }); // 构建上游请求头 const headers = { ...req.headers }; headers.host = targetHost; delete headers['x-forwarded-host']; delete headers['connection']; delete headers['keep-alive']; delete headers['proxy-connection']; delete headers['transfer-encoding']; const opts = { hostname: targetHost, port: 443, path: req.url, method: req.method, headers, servername: targetHost, timeout: CONNECT_TIMEOUT, // 不设置任何自定义 TLS 选项 → Node.js 默认 TLS stack → JA3/JA4 天然匹配 }; let proxyReq; if (UPSTREAM_PROXY) { try { const socket = await connectViaProxy(UPSTREAM_PROXY, targetHost, 443); opts.socket = socket; opts.agent = false; proxyReq = https.request(opts); } catch (err) { log('error', 'proxy tunnel failed', { error: err.message, host: targetHost }); 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) => { const responseHeaders = { ...proxyRes.headers }; delete responseHeaders['connection']; delete responseHeaders['keep-alive']; log('info', 'proxy_response', { host: targetHost, status: proxyRes.statusCode, path: req.url, }); res.writeHead(proxyRes.statusCode, responseHeaders); proxyRes.pipe(res, { end: true }); proxyRes.on('error', (err) => { log('error', 'upstream response error', { error: err.message, host: targetHost }); res.end(); }); }); // 上游连接错误 proxyReq.on('error', (err) => { 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 })); } }); proxyReq.on('timeout', () => { log('warn', 'upstream request timeout', { host: targetHost, path: req.url }); proxyReq.destroy(new Error('upstream timeout')); }); req.on('close', () => { if (!proxyReq.destroyed) proxyReq.destroy(); }); 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' })); } }); }); 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) }); });