req_client_pool.go 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. package repository
  2. import (
  3. "fmt"
  4. "net/http"
  5. "strings"
  6. "sync"
  7. "time"
  8. "github.com/Wei-Shaw/sub2api/internal/pkg/proxyurl"
  9. "github.com/Wei-Shaw/sub2api/internal/pkg/servertiming"
  10. "github.com/imroc/req/v3"
  11. )
  12. // reqClientOptions 定义 req 客户端的构建参数
  13. type reqClientOptions struct {
  14. ProxyURL string // 代理 URL(支持 http/https/socks5)
  15. Timeout time.Duration // 请求超时时间
  16. Impersonate bool // 是否模拟 Chrome 浏览器指纹
  17. ForceHTTP2 bool // 是否强制使用 HTTP/2
  18. }
  19. // sharedReqClients 存储按配置参数缓存的 req 客户端实例
  20. //
  21. // 性能优化说明:
  22. // 原实现在每次 OAuth 刷新时都创建新的 req.Client:
  23. // 1. claude_oauth_service.go: 每次刷新创建新客户端
  24. // 2. openai_oauth_service.go: 每次刷新创建新客户端
  25. // 3. gemini_oauth_client.go: 每次刷新创建新客户端
  26. //
  27. // 新实现使用 sync.Map 缓存客户端:
  28. // 1. 相同配置(代理+超时+模拟设置)复用同一客户端
  29. // 2. 复用底层连接池,减少 TLS 握手开销
  30. // 3. LoadOrStore 保证并发安全,避免重复创建
  31. var sharedReqClients sync.Map
  32. // getSharedReqClient 获取共享的 req 客户端实例
  33. // 性能优化:相同配置复用同一客户端,避免重复创建
  34. func getSharedReqClient(opts reqClientOptions) (*req.Client, error) {
  35. key := buildReqClientKey(opts)
  36. if cached, ok := sharedReqClients.Load(key); ok {
  37. if c, ok := cached.(*req.Client); ok {
  38. return c, nil
  39. }
  40. }
  41. client := req.C().SetTimeout(opts.Timeout)
  42. if opts.ForceHTTP2 {
  43. client = client.EnableForceHTTP2()
  44. }
  45. if opts.Impersonate {
  46. client = client.ImpersonateChrome()
  47. }
  48. trimmed, _, err := proxyurl.Parse(opts.ProxyURL)
  49. if err != nil {
  50. return nil, err
  51. }
  52. if trimmed != "" {
  53. client.SetProxyURL(trimmed)
  54. }
  55. client = instrumentReqClient(client)
  56. actual, _ := sharedReqClients.LoadOrStore(key, client)
  57. if c, ok := actual.(*req.Client); ok {
  58. return c, nil
  59. }
  60. return client, nil
  61. }
  62. func instrumentReqClient(client *req.Client) *req.Client {
  63. if client == nil {
  64. return nil
  65. }
  66. client.GetTransport().WrapRoundTripFunc(func(rt http.RoundTripper) req.HttpRoundTripFunc {
  67. timed := servertiming.WrapRoundTripper(rt)
  68. return timed.RoundTrip
  69. })
  70. return client
  71. }
  72. func buildReqClientKey(opts reqClientOptions) string {
  73. return fmt.Sprintf("%s|%s|%t|%t",
  74. strings.TrimSpace(opts.ProxyURL),
  75. opts.Timeout.String(),
  76. opts.Impersonate,
  77. opts.ForceHTTP2,
  78. )
  79. }
  80. // CreatePrivacyReqClient creates an HTTP client for OpenAI privacy settings API
  81. // This is exported for use by OpenAIPrivacyService
  82. // Uses Chrome TLS fingerprint impersonation to bypass Cloudflare checks
  83. func CreatePrivacyReqClient(proxyURL string) (*req.Client, error) {
  84. return getSharedReqClient(reqClientOptions{
  85. ProxyURL: proxyURL,
  86. Timeout: 30 * time.Second,
  87. Impersonate: true, // Enable Chrome TLS fingerprint impersonation
  88. })
  89. }