diff --git a/internal/runtime/executor/helps/proxy_helpers.go b/internal/runtime/executor/helps/proxy_helpers.go index 022bc65c17..8a3f7ab19b 100644 --- a/internal/runtime/executor/helps/proxy_helpers.go +++ b/internal/runtime/executor/helps/proxy_helpers.go @@ -12,6 +12,22 @@ import ( log "github.com/sirupsen/logrus" ) +func init() { + if transport, ok := http.DefaultTransport.(*http.Transport); ok && transport != nil { + applyTransportTuning(transport) + } +} + +func applyTransportTuning(t *http.Transport) { + if t == nil { + return + } + t.MaxIdleConns = 200 + t.MaxIdleConnsPerHost = 20 + t.IdleConnTimeout = 90 * time.Second + t.ForceAttemptHTTP2 = true +} + // NewProxyAwareHTTPClient creates an HTTP client with proper proxy configuration priority: // 1. Use auth.ProxyURL if configured (highest priority) // 2. Use cfg.ProxyURL if auth proxy is not configured @@ -46,6 +62,7 @@ func NewProxyAwareHTTPClient(ctx context.Context, cfg *config.Config, auth *clip if proxyURL != "" { transport := buildProxyTransport(proxyURL) if transport != nil { + applyTransportTuning(transport) httpClient.Transport = transport return httpClient } diff --git a/internal/runtime/executor/helps/proxy_helpers_test.go b/internal/runtime/executor/helps/proxy_helpers_test.go index 3311716765..6d29d249a4 100644 --- a/internal/runtime/executor/helps/proxy_helpers_test.go +++ b/internal/runtime/executor/helps/proxy_helpers_test.go @@ -4,6 +4,7 @@ import ( "context" "net/http" "testing" + "time" "github.com/router-for-me/CLIProxyAPI/v6/internal/config" cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" @@ -28,3 +29,58 @@ func TestNewProxyAwareHTTPClientDirectBypassesGlobalProxy(t *testing.T) { t.Fatal("expected direct transport to disable proxy function") } } + +func assertTransportTuned(t *testing.T, transport *http.Transport, label string) { + t.Helper() + if transport.MaxIdleConns != 200 { + t.Errorf("%s: MaxIdleConns = %d, want 200", label, transport.MaxIdleConns) + } + if transport.MaxIdleConnsPerHost != 20 { + t.Errorf("%s: MaxIdleConnsPerHost = %d, want 20", label, transport.MaxIdleConnsPerHost) + } + if transport.IdleConnTimeout != 90*time.Second { + t.Errorf("%s: IdleConnTimeout = %v, want 90s", label, transport.IdleConnTimeout) + } + if !transport.ForceAttemptHTTP2 { + t.Errorf("%s: ForceAttemptHTTP2 = false, want true", label) + } +} + +func TestNewProxyAwareHTTPClient_DefaultTransportTuned(t *testing.T) { + t.Parallel() + + transport, ok := http.DefaultTransport.(*http.Transport) + if !ok { + t.Skip("http.DefaultTransport is not *http.Transport") + } + + assertTransportTuned(t, transport, "global tuned transport") + + client := NewProxyAwareHTTPClient(context.Background(), nil, nil, 0) + if client.Transport != nil { + t.Error("expected NewProxyAwareHTTPClient to return nil transport when no proxy is configured to use http.DefaultTransport") + } +} + +func TestNewProxyAwareHTTPClient_ContextRoundTripperReused(t *testing.T) { + t.Parallel() + + original := &http.Transport{ + MaxIdleConns: 1, + MaxIdleConnsPerHost: 1, + IdleConnTimeout: time.Second, + ForceAttemptHTTP2: false, + } + + ctx := context.WithValue(context.Background(), "cliproxy.roundtripper", original) + client := NewProxyAwareHTTPClient(ctx, nil, nil, 0) + + if client.Transport != original { + t.Error("expected original context RoundTripper to be reused without cloning") + } + + // Verify the original transport was NOT mutated by NewProxyAwareHTTPClient + if original.MaxIdleConns != 1 || original.MaxIdleConnsPerHost != 1 { + t.Error("original transport was mutated, but it should be preserved as-is when provided via context") + } +}