From 2392e7cf99662af2a68870bd2f7d9789fff62d53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=9F=E8=A5=BF=E5=B0=8F=E5=BE=90?= <7836246@qq.com> Date: Thu, 18 Dec 2025 18:14:20 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=B9=B6=E5=8F=91?= =?UTF-8?q?=E8=AF=B7=E6=B1=82=E6=97=B6=E5=85=B1=E4=BA=ABhttpClient.Transpo?= =?UTF-8?q?rt=E5=AF=BC=E8=87=B4=E7=9A=84=E7=AB=9E=E6=80=81=E6=9D=A1?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题描述: 当多个请求并发执行且使用不同代理配置时,它们会同时修改共享的 s.httpClient.Transport,导致请求可能使用错误的代理(数据泄露风险) 或意外失败。 修复方案: 为需要代理的请求创建独立的http.Client,而不是修改共享的httpClient.Transport。 改动内容: - 新增 buildUpstreamRequestResult 结构体,返回请求和可选的独立client - 修改 buildUpstreamRequest 方法,配置代理时创建独立client - 更新 Forward 方法,根据是否有代理选择合适的client --- backend/internal/service/gateway_service.go | 76 ++++++++++++++------- 1 file changed, 50 insertions(+), 26 deletions(-) diff --git a/backend/internal/service/gateway_service.go b/backend/internal/service/gateway_service.go index 02fe6f21..24f4a9ed 100644 --- a/backend/internal/service/gateway_service.go +++ b/backend/internal/service/gateway_service.go @@ -35,25 +35,25 @@ const ( // allowedHeaders 白名单headers(参考CRS项目) var allowedHeaders = map[string]bool{ - "accept": true, - "x-stainless-retry-count": true, - "x-stainless-timeout": true, - "x-stainless-lang": true, - "x-stainless-package-version": true, - "x-stainless-os": true, - "x-stainless-arch": true, - "x-stainless-runtime": true, - "x-stainless-runtime-version": true, - "x-stainless-helper-method": true, + "accept": true, + "x-stainless-retry-count": true, + "x-stainless-timeout": true, + "x-stainless-lang": true, + "x-stainless-package-version": true, + "x-stainless-os": true, + "x-stainless-arch": true, + "x-stainless-runtime": true, + "x-stainless-runtime-version": true, + "x-stainless-helper-method": true, "anthropic-dangerous-direct-browser-access": true, - "anthropic-version": true, - "x-app": true, - "anthropic-beta": true, - "accept-language": true, - "sec-fetch-mode": true, - "accept-encoding": true, - "user-agent": true, - "content-type": true, + "anthropic-version": true, + "x-app": true, + "anthropic-beta": true, + "accept-language": true, + "sec-fetch-mode": true, + "accept-encoding": true, + "user-agent": true, + "content-type": true, } // ClaudeUsage 表示Claude API返回的usage信息 @@ -418,13 +418,19 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *m } // 构建上游请求 - upstreamReq, err := s.buildUpstreamRequest(ctx, c, account, body, token, tokenType) + upstreamResult, err := s.buildUpstreamRequest(ctx, c, account, body, token, tokenType) if err != nil { return nil, err } + // 选择使用的client:如果有代理则使用独立的client,否则使用共享的httpClient + httpClient := s.httpClient + if upstreamResult.Client != nil { + httpClient = upstreamResult.Client + } + // 发送请求 - resp, err := s.httpClient.Do(upstreamReq) + resp, err := httpClient.Do(upstreamResult.Request) if err != nil { return nil, fmt.Errorf("upstream request failed: %w", err) } @@ -437,11 +443,16 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *m if err != nil { return nil, fmt.Errorf("token refresh failed: %w", err) } - upstreamReq, err = s.buildUpstreamRequest(ctx, c, account, body, token, tokenType) + upstreamResult, err = s.buildUpstreamRequest(ctx, c, account, body, token, tokenType) if err != nil { return nil, err } - resp, err = s.httpClient.Do(upstreamReq) + // 重试时也需要使用正确的client + httpClient = s.httpClient + if upstreamResult.Client != nil { + httpClient = upstreamResult.Client + } + resp, err = httpClient.Do(upstreamResult.Request) if err != nil { return nil, fmt.Errorf("retry request failed: %w", err) } @@ -480,7 +491,13 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *m }, nil } -func (s *GatewayService) buildUpstreamRequest(ctx context.Context, c *gin.Context, account *model.Account, body []byte, token, tokenType string) (*http.Request, error) { +// buildUpstreamRequestResult contains the request and optional custom client for proxy +type buildUpstreamRequestResult struct { + Request *http.Request + Client *http.Client // nil means use default s.httpClient +} + +func (s *GatewayService) buildUpstreamRequest(ctx context.Context, c *gin.Context, account *model.Account, body []byte, token, tokenType string) (*buildUpstreamRequestResult, error) { // 确定目标URL targetURL := claudeAPIURL if account.Type == model.AccountTypeApiKey { @@ -549,7 +566,8 @@ func (s *GatewayService) buildUpstreamRequest(ctx context.Context, c *gin.Contex req.Header.Set("anthropic-beta", s.getBetaHeader(body, c.GetHeader("anthropic-beta"))) } - // 配置代理 + // 配置代理 - 创建独立的client避免并发修改共享httpClient + var customClient *http.Client if account.ProxyID != nil && account.Proxy != nil { proxyURL := account.Proxy.URL() if proxyURL != "" { @@ -566,12 +584,18 @@ func (s *GatewayService) buildUpstreamRequest(ctx context.Context, c *gin.Contex IdleConnTimeout: 90 * time.Second, ResponseHeaderTimeout: responseHeaderTimeout, } - s.httpClient.Transport = transport + // 创建独立的client,避免并发时修改共享的s.httpClient.Transport + customClient = &http.Client{ + Transport: transport, + } } } } - return req, nil + return &buildUpstreamRequestResult{ + Request: req, + Client: customClient, + }, nil } // getBetaHeader 处理anthropic-beta header