feat: IP管理代理与 node-tls-proxy 指纹伪装共存
Some checks failed
CI / test (push) Failing after 6s
CI / golangci-lint (push) Failing after 6s
Security Scan / backend-security (push) Failing after 6s
Security Scan / frontend-security (push) Failing after 6s

- Do()/DoWithTLS() 移除 proxyURL=="" 条件,绑了代理也走 node-tls-proxy
- doViaNodeTLSProxy 通过 X-Upstream-Proxy header 传递账号代理给 node-tls-proxy
- node-tls-proxy 支持 per-request 动态上游代理,优先 X-Upstream-Proxy,回退全局 UPSTREAM_PROXY
- 效果:IP管理 = 落地机网络,账号绑代理后指纹伪装仍然生效
This commit is contained in:
win 2026-03-26 14:00:17 +08:00
parent e1de3a7b21
commit 8c6e578a84
3 changed files with 21 additions and 7 deletions

View File

@ -573,10 +573,15 @@ function sendViaH1(targetHost, method, path, reqHeaders, body, res, savedHeaders
proxyReq.end(body);
};
if (UPSTREAM_PROXY) {
connectViaProxy(UPSTREAM_PROXY, targetHost, 443)
// 动态上游代理:优先使用 per-request 的 X-Upstream-Proxy回退到全局 UPSTREAM_PROXY
const upstreamProxy = reqHeaders['x-upstream-proxy'] || UPSTREAM_PROXY;
// 清除内部 header不传给上游
delete headers['x-upstream-proxy'];
if (upstreamProxy) {
connectViaProxy(upstreamProxy, targetHost, 443)
.then((socket) => { opts.socket = socket; opts.agent = false; finish(opts); })
.catch((err) => { log('error', 'tunnel_failed', { error: err.message }); if (!res.headersSent) { res.writeHead(502); res.end('tunnel error'); } resolve('error'); });
.catch((err) => { log('error', 'tunnel_failed', { error: err.message, proxy: upstreamProxy }); if (!res.headersSent) { res.writeHead(502); res.end('tunnel error'); } resolve('error'); });
} else {
finish(opts);
}

View File

@ -124,8 +124,10 @@ func NewHTTPUpstream(cfg *config.Config) service.HTTPUpstream {
// - 调用方必须关闭 resp.Body否则会导致 inFlight 计数泄漏
// - inFlight > 0 的客户端不会被淘汰,确保活跃请求不被中断
func (s *httpUpstreamService) Do(req *http.Request, proxyURL string, accountID int64, accountConcurrency int) (*http.Response, error) {
// Node.js TLS 代理Anthropic + Google APIs无 per-account 代理时)
if s.isNodeTLSProxyEnabled() && proxyURL == "" && req != nil && req.URL != nil && req.URL.Scheme == "https" {
// Node.js TLS 代理Anthropic + Google APIs
// 无论是否绑定 per-account 代理,都走 node-tls-proxy指纹伪装
// proxyURL 通过 X-Upstream-Proxy header 传递给 node-tls-proxy 动态选择出口
if s.isNodeTLSProxyEnabled() && req != nil && req.URL != nil && req.URL.Scheme == "https" {
host := req.URL.Hostname()
if host == "api.anthropic.com" ||
strings.HasSuffix(host, ".googleapis.com") {
@ -184,8 +186,9 @@ func (s *httpUpstreamService) DoWithTLS(req *http.Request, proxyURL string, acco
return s.Do(req, proxyURL, accountID, accountConcurrency)
}
// 优先使用 Node.js TLS 代理模式Anthropic + Google APIs无 per-account 代理)
if s.isNodeTLSProxyEnabled() && proxyURL == "" && req != nil && req.URL != nil {
// 优先使用 Node.js TLS 代理模式Anthropic + Google APIs
// 无论是否绑定 per-account 代理,都走 node-tls-proxy指纹伪装
if s.isNodeTLSProxyEnabled() && req != nil && req.URL != nil {
host := req.URL.Hostname()
if host == "api.anthropic.com" || strings.HasSuffix(host, ".googleapis.com") {
return s.doViaNodeTLSProxy(req, proxyURL, accountID, accountConcurrency)

View File

@ -79,6 +79,12 @@ func (s *httpUpstreamService) doViaNodeTLSProxy(req *http.Request, proxyURL stri
originalHost := req.URL.Host
proxyReq.Header.Set("X-Forwarded-Host", originalHost)
// 如果账号绑定了代理(落地机 GOST通过 header 传递给 node-tls-proxy
// node-tls-proxy 会用此代理作为上游出口,实现动态路由
if proxyURL != "" {
proxyReq.Header.Set("X-Upstream-Proxy", proxyURL)
}
// 重写请求 URLhttps://api.anthropic.com/v1/... → http://127.0.0.1:3456/v1/...
proxyReq.URL.Scheme = "http"
proxyReq.URL.Host = fmt.Sprintf("%s:%d", listenHost, listenPort)