package repository import ( "net/http" "net/url" "strings" "sync" "time" "github.com/Wei-Shaw/sub2api/internal/config" "github.com/Wei-Shaw/sub2api/internal/service" ) // httpUpstreamService 通用 HTTP 上游服务 // 用于向任意 HTTP API(Claude、OpenAI 等)发送请求,支持可选代理 // // 性能优化: // 1. 使用 sync.Map 缓存代理客户端实例,避免每次请求都创建新的 http.Client // 2. 复用 Transport 连接池,减少 TCP 握手和 TLS 协商开销 // 3. 原实现每次请求都 new 一个 http.Client,导致连接无法复用 type httpUpstreamService struct { // defaultClient: 无代理时使用的默认客户端(单例复用) defaultClient *http.Client // proxyClients: 按代理 URL 缓存的客户端池,避免重复创建 proxyClients sync.Map cfg *config.Config } // NewHTTPUpstream 创建通用 HTTP 上游服务 // 使用配置中的连接池参数构建 Transport func NewHTTPUpstream(cfg *config.Config) service.HTTPUpstream { return &httpUpstreamService{ defaultClient: &http.Client{Transport: buildUpstreamTransport(cfg, nil)}, cfg: cfg, } } func (s *httpUpstreamService) Do(req *http.Request, proxyURL string) (*http.Response, error) { if strings.TrimSpace(proxyURL) == "" { return s.defaultClient.Do(req) } client := s.getOrCreateClient(proxyURL) return client.Do(req) } // getOrCreateClient 获取或创建代理客户端 // 性能优化:使用 sync.Map 实现无锁缓存,相同代理 URL 复用同一客户端 // LoadOrStore 保证并发安全,避免重复创建 func (s *httpUpstreamService) getOrCreateClient(proxyURL string) *http.Client { proxyURL = strings.TrimSpace(proxyURL) if proxyURL == "" { return s.defaultClient } // 优先从缓存获取,命中则直接返回 if cached, ok := s.proxyClients.Load(proxyURL); ok { return cached.(*http.Client) } parsedURL, err := url.Parse(proxyURL) if err != nil { return s.defaultClient } // 创建新客户端并缓存,LoadOrStore 保证只有一个实例被存储 client := &http.Client{Transport: buildUpstreamTransport(s.cfg, parsedURL)} actual, _ := s.proxyClients.LoadOrStore(proxyURL, client) return actual.(*http.Client) } // buildUpstreamTransport 构建上游请求的 Transport // 使用配置文件中的连接池参数,支持生产环境调优 func buildUpstreamTransport(cfg *config.Config, proxyURL *url.URL) *http.Transport { // 读取配置,使用合理的默认值 maxIdleConns := cfg.Gateway.MaxIdleConns if maxIdleConns <= 0 { maxIdleConns = 240 } maxIdleConnsPerHost := cfg.Gateway.MaxIdleConnsPerHost if maxIdleConnsPerHost <= 0 { maxIdleConnsPerHost = 120 } maxConnsPerHost := cfg.Gateway.MaxConnsPerHost if maxConnsPerHost < 0 { maxConnsPerHost = 240 } idleConnTimeout := time.Duration(cfg.Gateway.IdleConnTimeoutSeconds) * time.Second if idleConnTimeout <= 0 { idleConnTimeout = 300 * time.Second } responseHeaderTimeout := time.Duration(cfg.Gateway.ResponseHeaderTimeout) * time.Second if responseHeaderTimeout <= 0 { responseHeaderTimeout = 300 * time.Second } transport := &http.Transport{ MaxIdleConns: maxIdleConns, // 最大空闲连接总数 MaxIdleConnsPerHost: maxIdleConnsPerHost, // 每主机最大空闲连接 MaxConnsPerHost: maxConnsPerHost, // 每主机最大连接数(含活跃) IdleConnTimeout: idleConnTimeout, // 空闲连接超时 ResponseHeaderTimeout: responseHeaderTimeout, } if proxyURL != nil { transport.Proxy = http.ProxyURL(proxyURL) } return transport }