mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-18 04:10:51 +08:00
feat(runtime): introduce newProxyAwareHTTPClient for enhanced proxy handling
- Added `newProxyAwareHTTPClient` to centralize proxy configuration with priority on `auth.ProxyURL` and `cfg.ProxyURL`. - Integrated enhanced proxy support across executors for HTTP, HTTPS, and SOCKS5 protocols. - Refactored redundant HTTP client initialization to use `newProxyAwareHTTPClient` for consistent behavior.
This commit is contained in:
@@ -61,10 +61,7 @@ func (e *ClaudeExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, r
|
|||||||
}
|
}
|
||||||
applyClaudeHeaders(httpReq, apiKey, false)
|
applyClaudeHeaders(httpReq, apiKey, false)
|
||||||
|
|
||||||
httpClient := &http.Client{}
|
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
|
||||||
if rt, ok := ctx.Value("cliproxy.roundtripper").(http.RoundTripper); ok && rt != nil {
|
|
||||||
httpClient.Transport = rt
|
|
||||||
}
|
|
||||||
resp, err := httpClient.Do(httpReq)
|
resp, err := httpClient.Do(httpReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cliproxyexecutor.Response{}, err
|
return cliproxyexecutor.Response{}, err
|
||||||
@@ -130,10 +127,7 @@ func (e *ClaudeExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.A
|
|||||||
}
|
}
|
||||||
applyClaudeHeaders(httpReq, apiKey, true)
|
applyClaudeHeaders(httpReq, apiKey, true)
|
||||||
|
|
||||||
httpClient := &http.Client{Timeout: 0}
|
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
|
||||||
if rt, ok := ctx.Value("cliproxy.roundtripper").(http.RoundTripper); ok && rt != nil {
|
|
||||||
httpClient.Transport = rt
|
|
||||||
}
|
|
||||||
resp, err := httpClient.Do(httpReq)
|
resp, err := httpClient.Do(httpReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -196,10 +190,7 @@ func (e *ClaudeExecutor) CountTokens(ctx context.Context, auth *cliproxyauth.Aut
|
|||||||
}
|
}
|
||||||
applyClaudeHeaders(httpReq, apiKey, false)
|
applyClaudeHeaders(httpReq, apiKey, false)
|
||||||
|
|
||||||
httpClient := &http.Client{}
|
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
|
||||||
if rt, ok := ctx.Value("cliproxy.roundtripper").(http.RoundTripper); ok && rt != nil {
|
|
||||||
httpClient.Transport = rt
|
|
||||||
}
|
|
||||||
resp, err := httpClient.Do(httpReq)
|
resp, err := httpClient.Do(httpReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cliproxyexecutor.Response{}, err
|
return cliproxyexecutor.Response{}, err
|
||||||
|
|||||||
@@ -85,10 +85,7 @@ func (e *CodexExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, re
|
|||||||
}
|
}
|
||||||
applyCodexHeaders(httpReq, auth, apiKey)
|
applyCodexHeaders(httpReq, auth, apiKey)
|
||||||
|
|
||||||
httpClient := &http.Client{}
|
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
|
||||||
if rt, ok := ctx.Value("cliproxy.roundtripper").(http.RoundTripper); ok && rt != nil {
|
|
||||||
httpClient.Transport = rt
|
|
||||||
}
|
|
||||||
resp, err := httpClient.Do(httpReq)
|
resp, err := httpClient.Do(httpReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cliproxyexecutor.Response{}, err
|
return cliproxyexecutor.Response{}, err
|
||||||
@@ -172,10 +169,7 @@ func (e *CodexExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Au
|
|||||||
}
|
}
|
||||||
applyCodexHeaders(httpReq, auth, apiKey)
|
applyCodexHeaders(httpReq, auth, apiKey)
|
||||||
|
|
||||||
httpClient := &http.Client{Timeout: 0}
|
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
|
||||||
if rt, ok := ctx.Value("cliproxy.roundtripper").(http.RoundTripper); ok && rt != nil {
|
|
||||||
httpClient.Transport = rt
|
|
||||||
}
|
|
||||||
resp, err := httpClient.Do(httpReq)
|
resp, err := httpClient.Do(httpReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ func (e *GeminiCLIExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth
|
|||||||
models = append([]string{req.Model}, models...)
|
models = append([]string{req.Model}, models...)
|
||||||
}
|
}
|
||||||
|
|
||||||
httpClient := newHTTPClient(ctx, 0)
|
httpClient := newHTTPClient(ctx, e.cfg, auth, 0)
|
||||||
respCtx := context.WithValue(ctx, "alt", opts.Alt)
|
respCtx := context.WithValue(ctx, "alt", opts.Alt)
|
||||||
|
|
||||||
var lastStatus int
|
var lastStatus int
|
||||||
@@ -155,7 +155,7 @@ func (e *GeminiCLIExecutor) ExecuteStream(ctx context.Context, auth *cliproxyaut
|
|||||||
models = append([]string{req.Model}, models...)
|
models = append([]string{req.Model}, models...)
|
||||||
}
|
}
|
||||||
|
|
||||||
httpClient := newHTTPClient(ctx, 0)
|
httpClient := newHTTPClient(ctx, e.cfg, auth, 0)
|
||||||
respCtx := context.WithValue(ctx, "alt", opts.Alt)
|
respCtx := context.WithValue(ctx, "alt", opts.Alt)
|
||||||
|
|
||||||
var lastStatus int
|
var lastStatus int
|
||||||
@@ -281,7 +281,7 @@ func (e *GeminiCLIExecutor) CountTokens(ctx context.Context, auth *cliproxyauth.
|
|||||||
models = append([]string{req.Model}, models...)
|
models = append([]string{req.Model}, models...)
|
||||||
}
|
}
|
||||||
|
|
||||||
httpClient := newHTTPClient(ctx, 0)
|
httpClient := newHTTPClient(ctx, e.cfg, auth, 0)
|
||||||
respCtx := context.WithValue(ctx, "alt", opts.Alt)
|
respCtx := context.WithValue(ctx, "alt", opts.Alt)
|
||||||
|
|
||||||
var lastStatus int
|
var lastStatus int
|
||||||
@@ -438,15 +438,8 @@ func updateGeminiCLITokenMetadata(auth *cliproxyauth.Auth, base map[string]any,
|
|||||||
auth.Metadata["token"] = merged
|
auth.Metadata["token"] = merged
|
||||||
}
|
}
|
||||||
|
|
||||||
func newHTTPClient(ctx context.Context, timeout time.Duration) *http.Client {
|
func newHTTPClient(ctx context.Context, cfg *config.Config, auth *cliproxyauth.Auth, timeout time.Duration) *http.Client {
|
||||||
client := &http.Client{}
|
return newProxyAwareHTTPClient(ctx, cfg, auth, timeout)
|
||||||
if timeout > 0 {
|
|
||||||
client.Timeout = timeout
|
|
||||||
}
|
|
||||||
if rt, ok := ctx.Value("cliproxy.roundtripper").(http.RoundTripper); ok && rt != nil {
|
|
||||||
client.Transport = rt
|
|
||||||
}
|
|
||||||
return client
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func cloneMap(in map[string]any) map[string]any {
|
func cloneMap(in map[string]any) map[string]any {
|
||||||
|
|||||||
@@ -103,10 +103,7 @@ func (e *GeminiExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, r
|
|||||||
httpReq.Header.Set("Authorization", "Bearer "+bearer)
|
httpReq.Header.Set("Authorization", "Bearer "+bearer)
|
||||||
}
|
}
|
||||||
|
|
||||||
httpClient := &http.Client{}
|
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
|
||||||
if rt, ok := ctx.Value("cliproxy.roundtripper").(http.RoundTripper); ok && rt != nil {
|
|
||||||
httpClient.Transport = rt
|
|
||||||
}
|
|
||||||
resp, err := httpClient.Do(httpReq)
|
resp, err := httpClient.Do(httpReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cliproxyexecutor.Response{}, err
|
return cliproxyexecutor.Response{}, err
|
||||||
@@ -159,10 +156,7 @@ func (e *GeminiExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.A
|
|||||||
httpReq.Header.Set("Authorization", "Bearer "+bearer)
|
httpReq.Header.Set("Authorization", "Bearer "+bearer)
|
||||||
}
|
}
|
||||||
|
|
||||||
httpClient := &http.Client{Timeout: 0}
|
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
|
||||||
if rt, ok := ctx.Value("cliproxy.roundtripper").(http.RoundTripper); ok && rt != nil {
|
|
||||||
httpClient.Transport = rt
|
|
||||||
}
|
|
||||||
resp, err := httpClient.Do(httpReq)
|
resp, err := httpClient.Do(httpReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -230,10 +224,7 @@ func (e *GeminiExecutor) CountTokens(ctx context.Context, auth *cliproxyauth.Aut
|
|||||||
httpReq.Header.Set("Authorization", "Bearer "+bearer)
|
httpReq.Header.Set("Authorization", "Bearer "+bearer)
|
||||||
}
|
}
|
||||||
|
|
||||||
httpClient := &http.Client{}
|
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
|
||||||
if rt, ok := ctx.Value("cliproxy.roundtripper").(http.RoundTripper); ok && rt != nil {
|
|
||||||
httpClient.Transport = rt
|
|
||||||
}
|
|
||||||
resp, err := httpClient.Do(httpReq)
|
resp, err := httpClient.Do(httpReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cliproxyexecutor.Response{}, err
|
return cliproxyexecutor.Response{}, err
|
||||||
|
|||||||
@@ -63,10 +63,7 @@ func (e *OpenAICompatExecutor) Execute(ctx context.Context, auth *cliproxyauth.A
|
|||||||
httpReq.Header.Set("Authorization", "Bearer "+apiKey)
|
httpReq.Header.Set("Authorization", "Bearer "+apiKey)
|
||||||
httpReq.Header.Set("User-Agent", "cli-proxy-openai-compat")
|
httpReq.Header.Set("User-Agent", "cli-proxy-openai-compat")
|
||||||
|
|
||||||
httpClient := &http.Client{}
|
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
|
||||||
if rt, ok := ctx.Value("cliproxy.roundtripper").(http.RoundTripper); ok && rt != nil {
|
|
||||||
httpClient.Transport = rt
|
|
||||||
}
|
|
||||||
resp, err := httpClient.Do(httpReq)
|
resp, err := httpClient.Do(httpReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cliproxyexecutor.Response{}, err
|
return cliproxyexecutor.Response{}, err
|
||||||
@@ -115,10 +112,7 @@ func (e *OpenAICompatExecutor) ExecuteStream(ctx context.Context, auth *cliproxy
|
|||||||
httpReq.Header.Set("Accept", "text/event-stream")
|
httpReq.Header.Set("Accept", "text/event-stream")
|
||||||
httpReq.Header.Set("Cache-Control", "no-cache")
|
httpReq.Header.Set("Cache-Control", "no-cache")
|
||||||
|
|
||||||
httpClient := &http.Client{Timeout: 0}
|
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
|
||||||
if rt, ok := ctx.Value("cliproxy.roundtripper").(http.RoundTripper); ok && rt != nil {
|
|
||||||
httpClient.Transport = rt
|
|
||||||
}
|
|
||||||
resp, err := httpClient.Do(httpReq)
|
resp, err := httpClient.Do(httpReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
113
internal/runtime/executor/proxy_helpers.go
Normal file
113
internal/runtime/executor/proxy_helpers.go
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
package executor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// 3. Use RoundTripper from context if neither are configured
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// - ctx: The context containing optional RoundTripper
|
||||||
|
// - cfg: The application configuration
|
||||||
|
// - auth: The authentication information
|
||||||
|
// - timeout: The client timeout (0 means no timeout)
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
// - *http.Client: An HTTP client with configured proxy or 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Priority 1: Use auth.ProxyURL if configured
|
||||||
|
var proxyURL string
|
||||||
|
if auth != nil {
|
||||||
|
proxyURL = strings.TrimSpace(auth.ProxyURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Priority 2: Use cfg.ProxyURL if auth proxy is not configured
|
||||||
|
if proxyURL == "" && cfg != nil {
|
||||||
|
proxyURL = strings.TrimSpace(cfg.ProxyURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have a proxy URL configured, set up the transport
|
||||||
|
if proxyURL != "" {
|
||||||
|
transport := buildProxyTransport(proxyURL)
|
||||||
|
if transport != nil {
|
||||||
|
httpClient.Transport = transport
|
||||||
|
return httpClient
|
||||||
|
}
|
||||||
|
// If proxy setup failed, log and fall through to context RoundTripper
|
||||||
|
log.Debugf("failed to setup proxy from URL: %s, falling back to context transport", proxyURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Priority 3: Use RoundTripper from context (typically from RoundTripperFor)
|
||||||
|
if rt, ok := ctx.Value("cliproxy.roundtripper").(http.RoundTripper); ok && rt != nil {
|
||||||
|
httpClient.Transport = rt
|
||||||
|
}
|
||||||
|
|
||||||
|
return httpClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildProxyTransport creates an HTTP transport configured for the given proxy URL.
|
||||||
|
// It supports SOCKS5, HTTP, and HTTPS proxy protocols.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// - proxyURL: The proxy URL string (e.g., "socks5://user:pass@host:port", "http://host:port")
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
// - *http.Transport: A configured transport, or nil if the proxy URL is invalid
|
||||||
|
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
|
||||||
|
|
||||||
|
// Handle different proxy schemes
|
||||||
|
if parsedURL.Scheme == "socks5" {
|
||||||
|
// Configure SOCKS5 proxy with optional authentication
|
||||||
|
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
|
||||||
|
}
|
||||||
|
// Set up a custom transport using the SOCKS5 dialer
|
||||||
|
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" {
|
||||||
|
// Configure HTTP or HTTPS proxy
|
||||||
|
transport = &http.Transport{Proxy: http.ProxyURL(parsedURL)}
|
||||||
|
} else {
|
||||||
|
log.Errorf("unsupported proxy scheme: %s", parsedURL.Scheme)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return transport
|
||||||
|
}
|
||||||
@@ -58,10 +58,7 @@ func (e *QwenExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, req
|
|||||||
}
|
}
|
||||||
applyQwenHeaders(httpReq, token, false)
|
applyQwenHeaders(httpReq, token, false)
|
||||||
|
|
||||||
httpClient := &http.Client{}
|
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
|
||||||
if rt, ok := ctx.Value("cliproxy.roundtripper").(http.RoundTripper); ok && rt != nil {
|
|
||||||
httpClient.Transport = rt
|
|
||||||
}
|
|
||||||
resp, err := httpClient.Do(httpReq)
|
resp, err := httpClient.Do(httpReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cliproxyexecutor.Response{}, err
|
return cliproxyexecutor.Response{}, err
|
||||||
@@ -112,10 +109,7 @@ func (e *QwenExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Aut
|
|||||||
}
|
}
|
||||||
applyQwenHeaders(httpReq, token, true)
|
applyQwenHeaders(httpReq, token, true)
|
||||||
|
|
||||||
httpClient := &http.Client{Timeout: 0}
|
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
|
||||||
if rt, ok := ctx.Value("cliproxy.roundtripper").(http.RoundTripper); ok && rt != nil {
|
|
||||||
httpClient.Transport = rt
|
|
||||||
}
|
|
||||||
resp, err := httpClient.Do(httpReq)
|
resp, err := httpClient.Do(httpReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
Reference in New Issue
Block a user