From d29245666e31fc1c59d0625222cdcb7b25d1f0de Mon Sep 17 00:00:00 2001 From: Luis Pater Date: Thu, 3 Jul 2025 16:50:20 +0800 Subject: [PATCH] Add SOCKS5 and HTTP/HTTPS proxy support - Updated `GetAuthenticatedClient` to handle proxy configuration via `proxy-url`. - Extended `Config` to include `proxy-url` property. - Adjusted error handling and removed unused JSON error response logic for API handlers. - Updated documentation and configuration examples to reflect new proxy settings. --- README.md | 13 ++++++------ cmd/server/main.go | 4 ++-- config.yaml | 1 + internal/api/handlers.go | 30 +++++++++++++++----------- internal/auth/auth.go | 44 ++++++++++++++++++++++++++++++++++++--- internal/client/client.go | 4 +++- internal/config/config.go | 9 ++++---- 7 files changed, 77 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 2953c05d..018d64bb 100644 --- a/README.md +++ b/README.md @@ -146,12 +146,13 @@ The server uses a YAML configuration file (`config.yaml`) located in the project ### Configuration Options -| Parameter | Type | Default | Description | -|-----------|------|--------------------|-------------| -| `port` | integer | 8317 | The port number on which the server will listen | -| `auth_dir` | string | "~/.cli-proxy-api" | Directory where authentication tokens are stored. Supports using `~` for home directory | -| `debug` | boolean | false | Enable debug mode for verbose logging | -| `api_keys` | string[] | [] | List of API keys that can be used to authenticate requests | +| Parameter | Type | Default | Description | +|-------------|----------|--------------------|----------------------------------------------------------------------------------------------| +| `port` | integer | 8317 | The port number on which the server will listen | +| `auth_dir` | string | "~/.cli-proxy-api" | Directory where authentication tokens are stored. Supports using `~` for home directory | +| `proxy-url` | string | "" | Proxy url, support socks5/http/https protocol, example: socks5://user:pass@192.168.1.1:1080/ | +| `debug` | boolean | false | Enable debug mode for verbose logging | +| `api_keys` | string[] | [] | List of API keys that can be used to authenticate requests | ### Example Configuration File diff --git a/cmd/server/main.go b/cmd/server/main.go index 28ee0228..204ea2e1 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -104,7 +104,7 @@ func main() { clientCtx := context.Background() log.Info("Initializing authentication...") - httpClient, errGetClient := auth.GetAuthenticatedClient(clientCtx, &ts, cfg.AuthDir) + httpClient, errGetClient := auth.GetAuthenticatedClient(clientCtx, &ts, cfg) if errGetClient != nil { log.Fatalf("failed to get authenticated client: %v", errGetClient) return @@ -165,7 +165,7 @@ func main() { clientCtx := context.Background() log.Info("Initializing authentication...") - httpClient, errGetClient := auth.GetAuthenticatedClient(clientCtx, &ts, cfg.AuthDir) + httpClient, errGetClient := auth.GetAuthenticatedClient(clientCtx, &ts, cfg) if errGetClient != nil { log.Fatalf("failed to get authenticated client: %v", errGetClient) return errGetClient diff --git a/config.yaml b/config.yaml index 59dee5e8..ccc53dce 100644 --- a/config.yaml +++ b/config.yaml @@ -1,6 +1,7 @@ port: 8317 auth_dir: "~/.cli-proxy-api" debug: false +proxy-url: "" api_keys: - "12345" - "23456" \ No newline at end of file diff --git a/internal/api/handlers.go b/internal/api/handlers.go index 4d165606..8c58c9ee 100644 --- a/internal/api/handlers.go +++ b/internal/api/handlers.go @@ -429,12 +429,15 @@ func (h *APIHandlers) handleNonStreamingResponse(c *gin.Context, rawJson []byte) } case err, okError := <-errChan: if okError { - c.JSON(http.StatusInternalServerError, ErrorResponse{ - Error: ErrorDetail{ - Message: err.Error(), - Type: "server_error", - }, - }) + c.Status(http.StatusInternalServerError) + _, _ = fmt.Fprint(c.Writer, err.Error()) + flusher.Flush() + // c.JSON(http.StatusInternalServerError, ErrorResponse{ + // Error: ErrorDetail{ + // Message: err.Error(), + // Type: "server_error", + // }, + // }) cliCancel() return } @@ -523,12 +526,15 @@ func (h *APIHandlers) handleStreamingResponse(c *gin.Context, rawJson []byte) { } case err, okError := <-errChan: if okError { - c.JSON(http.StatusInternalServerError, ErrorResponse{ - Error: ErrorDetail{ - Message: err.Error(), - Type: "server_error", - }, - }) + c.Status(http.StatusInternalServerError) + _, _ = fmt.Fprint(c.Writer, err.Error()) + flusher.Flush() + // c.JSON(http.StatusInternalServerError, ErrorResponse{ + // Error: ErrorDetail{ + // Message: err.Error(), + // Type: "server_error", + // }, + // }) cliCancel() return } diff --git a/internal/auth/auth.go b/internal/auth/auth.go index a5ddb224..a0d146ae 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -5,10 +5,14 @@ import ( "encoding/json" "errors" "fmt" + "github.com/luispater/CLIProxyAPI/internal/config" log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" + "golang.org/x/net/proxy" "io" + "net" "net/http" + "net/url" "os" "path/filepath" "time" @@ -39,7 +43,42 @@ type TokenStorage struct { // GetAuthenticatedClient configures and returns an HTTP client with OAuth2 tokens. // It handles the entire flow: loading, refreshing, and fetching new tokens. -func GetAuthenticatedClient(ctx context.Context, ts *TokenStorage, authDir string) (*http.Client, error) { +func GetAuthenticatedClient(ctx context.Context, ts *TokenStorage, cfg *config.Config) (*http.Client, error) { + proxyURL, err := url.Parse(cfg.ProxyUrl) + if err == nil { + if proxyURL.Scheme == "socks5" { + username := proxyURL.User.Username() + password, _ := proxyURL.User.Password() + auth := &proxy.Auth{ + User: username, + Password: password, + } + dialer, errSOCKS5 := proxy.SOCKS5("tcp", proxyURL.Host, auth, proxy.Direct) + if errSOCKS5 != nil { + log.Fatalf("create SOCKS5 dialer failed: %v", errSOCKS5) + } + + transport := &http.Transport{ + DialContext: func(ctx context.Context, network, addr string) (c net.Conn, err error) { + return dialer.Dial(network, addr) + }, + } + proxyClient := &http.Client{ + Transport: transport, + } + + ctx = context.WithValue(ctx, oauth2.HTTPClient, proxyClient) + } else if proxyURL.Scheme == "http" || proxyURL.Scheme == "https" { + transport := &http.Transport{ + Proxy: http.ProxyURL(proxyURL), + } + proxyClient := &http.Client{ + Transport: transport, + } + ctx = context.WithValue(ctx, oauth2.HTTPClient, proxyClient) + } + } + conf := &oauth2.Config{ ClientID: oauthClientID, ClientSecret: oauthClientSecret, @@ -49,7 +88,6 @@ func GetAuthenticatedClient(ctx context.Context, ts *TokenStorage, authDir strin } var token *oauth2.Token - var err error if ts.Token == nil { log.Info("Could not load token from file, starting OAuth flow.") @@ -57,7 +95,7 @@ func GetAuthenticatedClient(ctx context.Context, ts *TokenStorage, authDir strin if err != nil { return nil, fmt.Errorf("failed to get token from web: %w", err) } - ts, err = saveTokenToFile(ctx, conf, token, ts.ProjectID, authDir) + ts, err = saveTokenToFile(ctx, conf, token, ts.ProjectID, cfg.AuthDir) if err != nil { // Log the error but proceed, as we have a valid token for the session. log.Errorf("Warning: failed to save token to file: %v", err) diff --git a/internal/client/client.go b/internal/client/client.go index 059e1703..20a96e7c 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -284,7 +284,9 @@ func (c *Client) StreamAPIRequest(ctx context.Context, endpoint string, body int _ = resp.Body.Close() }() bodyBytes, _ := io.ReadAll(resp.Body) - return nil, fmt.Errorf("api streaming request failed with status %d: %s", resp.StatusCode, string(bodyBytes)) + + return nil, fmt.Errorf(string(bodyBytes)) + // return nil, fmt.Errorf("api streaming request failed with status %d: %s", resp.StatusCode, string(bodyBytes)) } return resp.Body, nil diff --git a/internal/config/config.go b/internal/config/config.go index 874d1259..01bc8bf0 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -8,10 +8,11 @@ import ( // Config represents the application's configuration type Config struct { - Port int `yaml:"port"` - AuthDir string `yaml:"auth_dir"` - Debug bool `yaml:"debug"` - ApiKeys []string `yaml:"api_keys"` + Port int `yaml:"port"` + AuthDir string `yaml:"auth_dir"` + Debug bool `yaml:"debug"` + ProxyUrl string `yaml:"proxy-url"` + ApiKeys []string `yaml:"api_keys"` } // / LoadConfig loads the configuration from the specified file