diff --git a/docs/configuration.md b/docs/configuration.md index 261862f..1c61bf2 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -58,7 +58,17 @@ if err != nil { ## Customizing the HTTP Client -You can provide your own HTTP client configuration using the `HttpTransportCustomizer` callback. This is useful if you need to tweak connection settings like timeouts, idle connection settings, or TLS configurations: +There are two ways to customize the HTTP client used by the UniFi client: +1. Using the `HttpTransportCustomizer`. +2. Using the `HttpRoundTripperProvider`. + +Those methods are mutually exclusive, and only one can be used at a time. If both are provided, the `HttpRoundTripperProvider` takes precedence, +unless it returns `nil`, in which case the `HttpTransportCustomizer` is used if defined (or default transport is used). + +### Using `HttpTransportCustomizer` + +You can provide your own HTTP client transport configuration using the `HttpTransportCustomizer` callback. This is useful if you need to tweak connection settings like timeouts, idle connection settings, +or TLS configurations: ```go c, err := unifi.NewClient(&unifi.ClientConfig{ @@ -76,6 +86,21 @@ if err != nil { } ``` +### Using `HttpRoundTripperProvider` + +You can provide your own HTTP client configuration using the `HttpRoundTripperProvider` callback. This is useful if you need to create a custom round tripper, when `http.Transport` is not enough: + +```go +c, err := unifi.NewClient(&unifi.ClientConfig{ + BaseURL: "https://unifi.localdomain", + APIKey: "your-api-key", + HttpRoundTripperProvider: func() http.RoundTripper { + // Create a custom HTTP Round Tripper instance + return &http.Transport{}, nil + }, +}) +``` + ## Using Interceptors Interceptors let you hook into the request and response flow. They can be used for logging, metrics, or modifying requests/responses. diff --git a/unifi/client.go b/unifi/client.go index 45b94e3..a8218ce 100644 --- a/unifi/client.go +++ b/unifi/client.go @@ -53,25 +53,27 @@ Fields: Timeout: The maximum duration to wait for responses; default is no timeout. VerifySSL: When false, disables SSL certificate verification. Interceptors: A slice of ClientInterceptor implementations that can modify requests and responses. - HttpTransportCustomizer:An optional function to customize the HTTP transport (e.g., for custom TLS settings). + HttpTransportCustomizer: An optional function to customize the HTTP transport (e.g., for custom TLS settings). + HttpRoundTripperProvider: A function that returns a http.RoundTripper for customizing the HTTP client. If both HttpTransportCustomizer and HttpRoundTripperProvider are provided, HttpRoundTripperProvider takes precedence. UserAgent: The User-Agent header string for outgoing HTTP requests. ErrorHandler: A custom handler for processing HTTP response errors. UseLocking: If true, enables internal locking for concurrent request processing. ValidationMode:The mode for validating request bodies. Can be "soft", "hard", or "disable". */ type ClientConfig struct { - URL string `validate:"required,http_url"` - APIKey string `validate:"required_without_all=User Password"` - User string `validate:"excluded_with=APIKey,required_with=Password"` - Password string `validate:"excluded_with=APIKey,required_with=User"` - Timeout time.Duration // How long to wait for replies, default: forever. - VerifySSL bool - Interceptors []ClientInterceptor - HttpTransportCustomizer HttpTransportCustomizer - UserAgent string - ErrorHandler ResponseErrorHandler - UseLocking bool - ValidationMode validationMode `validate:"omitempty,oneof=soft hard disable"` + URL string `validate:"required,http_url"` + APIKey string `validate:"required_without_all=User Password"` + User string `validate:"excluded_with=APIKey,required_with=Password"` + Password string `validate:"excluded_with=APIKey,required_with=User"` + Timeout time.Duration // How long to wait for replies, default: forever. + VerifySSL bool + Interceptors []ClientInterceptor + HttpTransportCustomizer HttpTransportCustomizer + HttpRoundTripperProvider func() http.RoundTripper + UserAgent string + ErrorHandler ResponseErrorHandler + UseLocking bool + ValidationMode validationMode `validate:"omitempty,oneof=soft hard disable"` } // Credentials abstracts authentication credentials. @@ -152,20 +154,27 @@ func parseBaseURL(base string) (*url.URL, error) { } func newClientFromConfig(config *ClientConfig, v *validator) (*client, error) { + var rt http.RoundTripper var err error config.URL = strings.TrimRight(config.URL, "/") - transport := &http.Transport{ - Proxy: http.ProxyFromEnvironment, - TLSClientConfig: &tls.Config{InsecureSkipVerify: !config.VerifySSL}, + if config.HttpRoundTripperProvider != nil { + rt = config.HttpRoundTripperProvider() } - if config.HttpTransportCustomizer != nil { - if transport, err = config.HttpTransportCustomizer(transport); err != nil { - return nil, fmt.Errorf("failed customizing HTTP transport: %w", err) + if rt == nil { + transport := &http.Transport{ + Proxy: http.ProxyFromEnvironment, + TLSClientConfig: &tls.Config{InsecureSkipVerify: !config.VerifySSL}, } + if config.HttpTransportCustomizer != nil { + if transport, err = config.HttpTransportCustomizer(transport); err != nil { + return nil, fmt.Errorf("failed customizing HTTP transport: %w", err) + } + } + rt = transport } httpClient := &http.Client{ Timeout: config.Timeout, - Transport: transport, + Transport: rt, } if config.APIKey == "" { jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})