|
|
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 ( |
|
|
httpClientCache = make(map[string]*http.Client) |
|
|
httpClientCacheMutex sync.RWMutex |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func newProxyAwareHTTPClient(ctx context.Context, cfg *config.Config, auth *cliproxyauth.Auth, timeout time.Duration) *http.Client { |
|
|
|
|
|
var proxyURL string |
|
|
if auth != nil { |
|
|
proxyURL = strings.TrimSpace(auth.ProxyURL) |
|
|
} |
|
|
|
|
|
|
|
|
if proxyURL == "" && cfg != nil { |
|
|
proxyURL = strings.TrimSpace(cfg.ProxyURL) |
|
|
} |
|
|
|
|
|
|
|
|
cacheKey := proxyURL |
|
|
|
|
|
|
|
|
httpClientCacheMutex.RLock() |
|
|
if cachedClient, ok := httpClientCache[cacheKey]; ok { |
|
|
httpClientCacheMutex.RUnlock() |
|
|
|
|
|
if timeout > 0 { |
|
|
return &http.Client{ |
|
|
Transport: cachedClient.Transport, |
|
|
Timeout: timeout, |
|
|
} |
|
|
} |
|
|
return cachedClient |
|
|
} |
|
|
httpClientCacheMutex.RUnlock() |
|
|
|
|
|
|
|
|
httpClient := &http.Client{} |
|
|
if timeout > 0 { |
|
|
httpClient.Timeout = timeout |
|
|
} |
|
|
|
|
|
|
|
|
if proxyURL != "" { |
|
|
transport := buildProxyTransport(proxyURL) |
|
|
if transport != nil { |
|
|
httpClient.Transport = transport |
|
|
|
|
|
httpClientCacheMutex.Lock() |
|
|
httpClientCache[cacheKey] = httpClient |
|
|
httpClientCacheMutex.Unlock() |
|
|
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 { |
|
|
httpClient.Transport = rt |
|
|
} |
|
|
|
|
|
|
|
|
if proxyURL == "" { |
|
|
httpClientCacheMutex.Lock() |
|
|
httpClientCache[cacheKey] = httpClient |
|
|
httpClientCacheMutex.Unlock() |
|
|
} |
|
|
|
|
|
return httpClient |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
} |
|
|
|