A collection of HTTP client round trippers that can be chained together to enhance HTTP clients with reliability, observability, and traffic control features.
Additionally, the package includes a factory function to create HTTP clients with pre-configured round trippers based on YAML/JSON configuration files.
Automatically handles Bearer token authentication with token refresh capabilities.
Features:
- Automatic token injection
- Token refresh on authentication failures
- Cache invalidation with minimum intervals
- Request body rewinding for retries after token refresh
Usage:
// Create auth provider
tokenProvider := httpclient.AuthProviderFunc(func(ctx context.Context, scope ...string) (string, error) {
// Your token acquisition logic here
return "your-access-token", nil
})
// Basic usage
transport := httpclient.NewAuthBearerRoundTripper(http.DefaultTransport, tokenProvider)
// With custom options
transport := httpclient.NewAuthBearerRoundTripperWithOpts(
http.DefaultTransport,
tokenProvider,
httpclient.AuthBearerRoundTripperOpts{
TokenScope: []string{"my-service:read", "my-service:write"},
MinInvalidationInterval: 15 * time.Minute,
ShouldRefreshTokenAndRetry: func(ctx context.Context, resp *http.Response) bool {
// Custom condition to trigger token refresh and retry
},
},
)
client := &http.Client{Transport: transport}Configuration Options:
TokenScope: OAuth scopes for token requestsMinInvalidationInterval: Minimum time between auth token provider cache invalidationsShouldRefreshTokenAndRetry: Custom condition for token refresh and retryLoggerProvider: Context-specific logger function
Provides comprehensive HTTP request and response logging.
Features:
- Request/response logging
- Slow request detection
- Configurable logging modes (all/failed)
- Client type identification
Usage:
// Basic logging
transport := httpclient.NewLoggingRoundTripper(http.DefaultTransport)
// With custom options
transport := httpclient.NewLoggingRoundTripperWithOpts(
http.DefaultTransport,
httpclient.LoggingRoundTripperOpts{
ClientType: "my-api-client",
Mode: httpclient.LoggingModeAll,
SlowRequestThreshold: 2 * time.Second,
LoggerProvider: func(ctx context.Context) log.FieldLogger {
// Extract logger from your context
},
},
)
client := &http.Client{Transport: transport}Logging Modes:
LoggingModeAll: Log all requests and responses (default)LoggingModeFailed: Log only failed or slow requests
Configuration Options:
ClientType: Type identifier for the client (used in logs and metrics)Mode: Logging mode - all requests or only failed/slow requestsSlowRequestThreshold: Duration threshold for identifying slow requestsLoggerProvider: Context-specific logger function
Collects Prometheus metrics for HTTP client requests.
Features:
- Request duration histograms
- Request classification
- Client type labeling
- Customizable metrics collection
Usage:
// Create metrics collector
collector := httpclient.NewPrometheusMetricsCollector("myapp")
collector.MustRegister()
// Create transport
transport := httpclient.NewMetricsRoundTripperWithOpts(
http.DefaultTransport,
collector,
httpclient.MetricsRoundTripperOpts{
ClientType: "api-client",
ClassifyRequest: func(r *http.Request, clientType string) string {
// Custom request classification logic
},
},
)
client := &http.Client{Transport: transport}Metrics Collected:
http_client_request_duration_seconds: Histogram of request durations (suitable for Prometheus alerts on response times or codes)
Labels:
client_type: Type of client making the requestremote_address: Target server addresssummary: Request classification summarystatus: HTTP response status coderequest_type: Type of request (from context)
Configuration Options:
ClientType: Type identifier for the client (included in metrics labels)ClassifyRequest: Function to classify requests for metrics summary
Manages User-Agent headers in outgoing requests.
Features:
- Multiple update strategies
- Conditional User-Agent setting
- Header composition
Usage:
// Basic usage - set User-Agent if empty
transport := httpclient.NewUserAgentRoundTripper(http.DefaultTransport, "myapp/1.0")
// With custom strategy
transport := httpclient.NewUserAgentRoundTripperWithOpts(
http.DefaultTransport,
"myapp/1.0",
httpclient.UserAgentRoundTripperOpts{
UpdateStrategy: httpclient.UserAgentUpdateStrategyAppend,
},
)
client := &http.Client{Transport: transport}Configuration Options:
UpdateStrategy: Strategy for updating the User-Agent header
Update Strategies:
UserAgentUpdateStrategySetIfEmpty: Set only if User-Agent is empty (default)UserAgentUpdateStrategyAppend: Append to existing User-AgentUserAgentUpdateStrategyPrepend: Prepend to existing User-Agent
Controls outbound request rate to prevent overwhelming target services.
Features:
- Token bucket rate limiting
- Adaptive rate limiting based on response headers
- Configurable burst capacity
- Wait timeout control
Usage:
// Basic rate limiting - 10 requests per second
transport, err := httpclient.NewRateLimitingRoundTripper(http.DefaultTransport, 10)
// With custom options
transport, err := httpclient.NewRateLimitingRoundTripperWithOpts(
http.DefaultTransport, 10,
httpclient.RateLimitingRoundTripperOpts{
Burst: 20,
WaitTimeout: 5 * time.Second,
Adaptation: httpclient.RateLimitingRoundTripperAdaptation{
ResponseHeaderName: "X-RateLimit-Remaining",
SlackPercent: 10,
},
},
)
client := &http.Client{Transport: transport}Configuration Options:
Burst: Allow temporary request bursts (default: 1)WaitTimeout: Maximum wait time for rate limiting (default: 15s)Adaptation: Adaptive rate limiting based on response headers
Automatically adds X-Request-ID headers to outgoing requests.
Features:
- Automatic request ID generation
- Context-aware request ID extraction
- Request correlation support
Usage:
// Basic usage
transport := httpclient.NewRequestIDRoundTripper(http.DefaultTransport)
// With custom provider
transport := httpclient.NewRequestIDRoundTripperWithOpts(
http.DefaultTransport,
httpclient.RequestIDRoundTripperOpts{
RequestIDProvider: func(ctx context.Context) string {
// Extract request ID from your context
},
},
)
client := &http.Client{Transport: transport}Configuration Options:
RequestIDProvider: Function to extract request ID from context
Provides automatic retry functionality for failed HTTP requests with configurable backoff policies.
Features:
- Configurable retry attempts (default: 10)
- Multiple backoff policies (exponential, constant)
- Retry-After header support
- Idempotent request detection
- Body rewinding for safe retries
Usage:
// Basic usage with default settings
transport, err := httpclient.NewRetryableRoundTripper(http.DefaultTransport)
// With custom options
transport, err := httpclient.NewRetryableRoundTripperWithOpts(
http.DefaultTransport,
httpclient.RetryableRoundTripperOpts{
MaxRetryAttempts: 5,
CheckRetryFunc: func(ctx context.Context, resp *http.Response, err error, attempts int) (bool, error) {
// Custom retry logic, e.g., retry on 5xx status codes or network errors for idempotent methods, etc.
},
BackoffPolicy: retry.PolicyFunc(func() backoff.BackOff {
// Custom backoff policy
}),
},
)
client := &http.Client{Transport: transport}Configuration:
MaxRetryAttempts: Maximum number of retry attempts (default: 10)CheckRetryFunc: Custom retry condition functionIgnoreRetryAfter: Ignore Retry-After response headersBackoffPolicy: Backoff strategy for retry delays
The package provides factory functions to create HTTP clients with pre-configured round trippers. Configuration can be loaded from YAML, JSON, environment variables, or defined programmatically.
Configuration can be loaded from:
- YAML/JSON data using unmarshalling
- YAML/JSON files and/or environment variables using
config.Loader - Direct struct initialization
config.yaml:
httpclient:
timeout: 30s
retries:
enabled: true
maxAttempts: 5
policy: exponential
exponentialBackoff:
initialInterval: 1s
multiplier: 2.0
rateLimits:
enabled: true
limit: 100
burst: 20
waitTimeout: 5s
log:
enabled: true
mode: all
slowRequestThreshold: 2s
metrics:
enabled: trueGo code:
import (
"os"
"gopkg.in/yaml.v3"
"github.com/acronis/go-appkit/httpclient"
)
func createClientFromYAML() (*http.Client, error) {
data, err := os.ReadFile("config.yaml")
if err != nil {
return nil, err
}
var config struct {
HTTPClient httpclient.Config `yaml:"httpclient"`
}
if err := yaml.Unmarshal(data, &config); err != nil {
return nil, err
}
// Create auth provider
authProvider := httpclient.AuthProviderFunc(func(ctx context.Context, scope ...string) (string, error) {
// Your token acquisition logic here
return "your-access-token", nil
})
// Create metrics collector
metricsCollector := httpclient.NewPrometheusMetricsCollector("my_service")
metricsCollector.MustRegister()
// Create client with loaded configuration
return httpclient.NewWithOpts(&config.HTTPClient, httpclient.Opts{
UserAgent: "my-service/1.0.0",
ClientType: "external-api",
AuthProvider: authProvider,
MetricsCollector: metricsCollector,
LoggerProvider: func(ctx context.Context) log.FieldLogger {
return middleware.GetLoggerFromContext(ctx)
},
RequestIDProvider: func(ctx context.Context) string {
return middleware.GetRequestIDFromContext(ctx)
},
})
}