| package executor |
|
|
| import ( |
| "context" |
| "net" |
| "net/http" |
| "net/url" |
| "strings" |
| "sync" |
| "time" |
|
|
| "github.com/router-for-me/CLIProxyAPI/v6/internal/config" |
| cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" |
| log "github.com/sirupsen/logrus" |
| "golang.org/x/net/proxy" |
| ) |
|
|
| |
| var transportPool sync.Map |
|
|
| func getPooledTransport(proxyURL string) *http.Transport { |
| if v, ok := transportPool.Load(proxyURL); ok { |
| return v.(*http.Transport) |
| } |
|
|
| var t *http.Transport |
| if proxyURL == "" { |
| t = &http.Transport{} |
| } else { |
| t = buildProxyTransport(proxyURL) |
| if t == nil { |
| return nil |
| } |
| } |
| applyTransportDefaults(t, nil) |
|
|
| actual, _ := transportPool.LoadOrStore(proxyURL, t) |
| return actual.(*http.Transport) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| func newProxyAwareHTTPClient(ctx context.Context, cfg *config.Config, auth *cliproxyauth.Auth, timeout time.Duration) *http.Client { |
| httpClient := &http.Client{} |
| if timeout > 0 { |
| httpClient.Timeout = timeout |
| } |
|
|
| |
| var proxyURL string |
| if auth != nil { |
| proxyURL = strings.TrimSpace(auth.ProxyURL) |
| } |
|
|
| |
| if proxyURL == "" && cfg != nil { |
| proxyURL = strings.TrimSpace(cfg.ProxyURL) |
| } |
|
|
| |
| if proxyURL != "" { |
| transport := getPooledTransport(proxyURL) |
| if transport != nil { |
| applyTransportDefaults(transport, cfg) |
| httpClient.Transport = transport |
| return httpClient |
| } |
| log.Debugf("failed to setup proxy from URL: %s, falling back to context transport", proxyURL) |
| } |
|
|
| |
| if rt, ok := ctx.Value("cliproxy.roundtripper").(http.RoundTripper); ok && rt != nil { |
| if tr, ok := rt.(*http.Transport); ok { |
| applyTransportDefaults(tr, cfg) |
| } |
| httpClient.Transport = rt |
| } else { |
| |
| transport := getPooledTransport("") |
| applyTransportDefaults(transport, cfg) |
| httpClient.Transport = transport |
| } |
|
|
| return httpClient |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| func applyTransportDefaults(t *http.Transport, cfg *config.Config) { |
| if t == nil { |
| return |
| } |
| maxIdleConns := 100 |
| maxIdleConnsPerHost := 10 |
| maxConnsPerHost := 0 |
| idleConnTimeout := 90 * time.Second |
| if cfg != nil { |
| if cfg.HTTPTransport.MaxIdleConns > 0 { |
| maxIdleConns = cfg.HTTPTransport.MaxIdleConns |
| } |
| if cfg.HTTPTransport.MaxIdleConnsPerHost > 0 { |
| maxIdleConnsPerHost = cfg.HTTPTransport.MaxIdleConnsPerHost |
| } |
| if cfg.HTTPTransport.MaxConnsPerHost >= 0 { |
| maxConnsPerHost = cfg.HTTPTransport.MaxConnsPerHost |
| } |
| if cfg.HTTPTransport.IdleConnTimeoutSeconds > 0 { |
| idleConnTimeout = time.Duration(cfg.HTTPTransport.IdleConnTimeoutSeconds) * time.Second |
| } |
| } |
| t.MaxIdleConns = maxIdleConns |
| t.MaxIdleConnsPerHost = maxIdleConnsPerHost |
| t.MaxConnsPerHost = maxConnsPerHost |
| t.IdleConnTimeout = idleConnTimeout |
| if t.ForceAttemptHTTP2 == false { |
| t.ForceAttemptHTTP2 = true |
| } |
| } |
|
|
| func buildProxyTransport(proxyURL string) *http.Transport { |
| if proxyURL == "" { |
| return nil |
| } |
|
|
| parsedURL, errParse := url.Parse(proxyURL) |
| if errParse != nil { |
| log.Errorf("parse proxy URL failed: %v", errParse) |
| return nil |
| } |
|
|
| var transport *http.Transport |
|
|
| |
| if parsedURL.Scheme == "socks5" { |
| |
| var proxyAuth *proxy.Auth |
| if parsedURL.User != nil { |
| username := parsedURL.User.Username() |
| password, _ := parsedURL.User.Password() |
| proxyAuth = &proxy.Auth{User: username, Password: password} |
| } |
| dialer, errSOCKS5 := proxy.SOCKS5("tcp", parsedURL.Host, proxyAuth, proxy.Direct) |
| if errSOCKS5 != nil { |
| log.Errorf("create SOCKS5 dialer failed: %v", errSOCKS5) |
| return nil |
| } |
| |
| transport = &http.Transport{ |
| DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { |
| return dialer.Dial(network, addr) |
| }, |
| } |
| } else if parsedURL.Scheme == "http" || parsedURL.Scheme == "https" { |
| |
| transport = &http.Transport{Proxy: http.ProxyURL(parsedURL)} |
| } else { |
| log.Errorf("unsupported proxy scheme: %s", parsedURL.Scheme) |
| return nil |
| } |
|
|
| return transport |
| } |
|
|