Documentation
¶
Overview ¶
Package middleware provides HTTP middleware implementations for common cross-cutting concerns.
Index ¶
- Variables
- func BasicAuth(username, password string) decorator.Decorator
- func ContextTokenAuth() decorator.Decorator
- func DebugFinal(config DebugConfig) decorator.Decorator
- func DebugInit(logger *zap.Logger, config DebugConfig) decorator.Decorator
- func DumpRequest(outDir ...string) decorator.Decorator
- func DumpResponse(outDir ...string) decorator.Decorator
- func GetLogger(ctx context.Context) *zap.Logger
- func GetRequestID(ctx context.Context) string
- func GetToken(ctx context.Context) (string, bool)
- func RateLimiter(manager *RateLimiterManager) decorator.Decorator
- func RetryOnBadProxy(maxTimes uint) decorator.Decorator
- func RetryOnTimeout(maxTimes uint, timeout time.Duration) decorator.Decorator
- func StaticTokenAuth(token string) decorator.Decorator
- func UserAgent() decorator.Decorator
- func WithLogger(ctx context.Context, logger *zap.Logger) context.Context
- func WithRequestID(ctx context.Context, id string) context.Context
- func WithStats(ctx context.Context, stats *RequestStats) context.Context
- func WithToken(ctx context.Context, token string) context.Context
- func XdailiProxyAuth(order, secret string) decorator.Decorator
- type DebugConfig
- type Phase
- type RateLimiterManager
- func (m *RateLimiterManager) Clear()
- func (m *RateLimiterManager) Close()
- func (m *RateLimiterManager) GetLimiter(key string) *rate.Limiter
- func (m *RateLimiterManager) ID() string
- func (m *RateLimiterManager) SetID(id string)
- func (m *RateLimiterManager) SetMaxSize(maxSize int)
- func (m *RateLimiterManager) Size() int
- type RequestStats
- func (s *RequestStats) AddPhase(name string, startTime time.Time, duration time.Duration, ...)
- func (s *RequestStats) Finalize(err error)
- func (s *RequestStats) GetError() error
- func (s *RequestStats) GetPhases() []Phase
- func (s *RequestStats) GetRateLimitKey() string
- func (s *RequestStats) GetRateLimitWait() time.Duration
- func (s *RequestStats) GetRequestID() string
- func (s *RequestStats) GetRetryCount() int
- func (s *RequestStats) GetTotalDuration() time.Duration
- func (s *RequestStats) RecordRateLimitWait(duration time.Duration, key string)
- func (s *RequestStats) RecordRetry()
- func (s *RequestStats) SetRequestID(id string)
- func (s *RequestStats) StartPhase(name string, metadata map[string]interface{}) func(error)
- type RetryConfig
Constants ¶
This section is empty.
Variables ¶
var LoggerKey = loggerKeyType{}
LoggerKey is the context key for attaching a logger to a request.
Purpose: This key enables automatic logger propagation through the middleware chain. When a logger is attached, middleware can automatically log their operations without requiring explicit logger parameters.
Behavior:
- If set: Middleware uses this logger for debug/info/warn logging
- If not set: Middleware operates silently (no logging overhead)
Value Type: *zap.Logger
Note: The DebugInit middleware automatically attaches the logger to the context. For most use cases, use preset.WithDebugSimple() or preset.WithDebug() instead of manually setting the logger.
Example - Automatic logger (via DebugInit):
logger, _ := zap.NewDevelopment()
client := preset.NewHTTPClient(
preset.WithDebugSimple(logger), // Automatically propagates logger
preset.WithRateLimit(10, 20), // Will use the logger automatically
)
Example - Manual logger attachment:
logger, _ := zap.NewDevelopment() ctx := middleware.WithLogger(context.Background(), logger) req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
var ( // RateLimiterKey is the context key for rate limiter identification. // // Purpose: // This key enables per-entity rate limiting. Each unique key value gets its own // rate limiter bucket, allowing different limits for different users, API keys, // or any other identifier. // // Behavior: // - If set: The middleware retrieves or creates a rate limiter for this key // - If not set: Requests pass through without any rate limiting // // Value Type: string // // Example - Per-user rate limiting: // // // Create rate limiter manager (10 requests/second, burst of 20) // manager := middleware.NewRateLimiterManager(rate.Limit(10), 20) // // // Build client with rate limiting middleware // client := preset.NewHTTPClient(preset.WithRateLimit(10, 20)) // // // Set rate limit key per user // ctx := context.WithValue(context.Background(), middleware.RateLimiterKey, "user-123") // req, _ := http.NewRequestWithContext(ctx, "GET", "https://api.example.com/data", nil) // resp, err := client.Do(req) // // Example - Per-API-key rate limiting: // // apiKey := getAPIKeyFromRequest(r) // ctx := context.WithValue(r.Context(), middleware.RateLimiterKey, apiKey) // req = req.WithContext(ctx) RateLimiterKey = rateLimiterKeyType{} )
var RequestIDKey = requestIDKeyType{}
RequestIDKey is the context key for the unique request identifier.
Purpose: This key stores a globally unique identifier for the request, which is included in all log entries to enable tracing of a single request across multiple middleware components and potentially across services.
Value Type: string
var StatsKey = statsContextKeyType{}
StatsKey is the context key for attaching RequestStats to a request.
Purpose: This key enables request statistics collection. When RequestStats is attached to the context, middleware will automatically record timing information, retry counts, rate limit waits, and phase details.
Behavior:
- If set: Middleware records statistics to the attached RequestStats
- If not set: No statistics are collected (zero overhead)
Value Type: *RequestStats
Note: The DebugInit middleware automatically attaches RequestStats to the context if not already present. For manual control, use WithStats() and GetStats().
Example - Automatic stats (via DebugInit):
logger, _ := zap.NewDevelopment()
client := preset.NewHTTPClient(
preset.WithDebugSimple(logger), // Automatically attaches RequestStats
)
resp, _ := client.Do(req)
// Stats are collected and logged automatically
Example - Manual stats collection:
stats := middleware.NewRequestStats()
ctx := middleware.WithStats(context.Background(), stats)
req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
resp, _ := client.Do(req)
// After request completes, inspect the stats
fmt.Printf("Total duration: %v\n", stats.GetTotalDuration())
fmt.Printf("Retries: %d\n", stats.GetRetryCount())
fmt.Printf("Rate limit wait: %v\n", stats.GetRateLimitWait())
var ( // TokenContextKey is the context key for attaching per-request tokens. // // Purpose: // This key enables dynamic token authentication where the token is decided // at request time rather than at client construction. // // Behavior: // - If set: ContextTokenAuth reads the token and sets the Authorization header // - If not set: ContextTokenAuth is a no-op (useful when combined with StaticTokenAuth) // // Value Type: string // // Example - Per-request token: // // ctx := middleware.WithToken(context.Background(), "rotating-token") // req, _ := http.NewRequestWithContext(ctx, "GET", url, nil) // resp, _ := client.Do(req) TokenContextKey = tokenContextKeyType{} )
var ( // UserAgentRandomSeed is the context key for deterministic user agent generation. // // Purpose: // This key allows you to generate consistent User-Agent strings across multiple // requests. This is useful for maintaining session consistency or when you need // repeatable behavior for the same user or entity. // // Behavior: // - If set: The middleware generates a User-Agent deterministically based on the seed value // - If not set: A weighted random User-Agent is generated for each request // // Value Type: string // // Example - Consistent UA per user session: // // // All requests for the same user get the same User-Agent // sessionID := "user-session-abc123" // ctx := context.WithValue(context.Background(), middleware.UserAgentRandomSeed, sessionID) // req, _ := http.NewRequestWithContext(ctx, "GET", "https://api.example.com", nil) // // Example - Random UA (default behavior): // // // Without setting the seed, each request gets a random User-Agent // req, _ := http.NewRequest("GET", "https://api.example.com", nil) // // User-Agent will be randomly selected // // Example - Using preset package: // // client := preset.NewHTTPClient(preset.WithUserAgent()) // ctx := context.WithValue(ctx, middleware.UserAgentRandomSeed, "my-seed") // req, _ := http.NewRequestWithContext(ctx, "GET", url, nil) // resp, _ := client.Do(req) UserAgentRandomSeed = userAgentSeedKeyType{} )
Functions ¶
func BasicAuth ¶
BasicAuth is a middleware decorator for basic access authentication with username and password.
The middleware automatically uses a logger from the context if one was set by Debug or ZapVerboseLogger middleware. This enables automatic logging of authentication events without explicit logger configuration.
Example:
// Basic usage without logging
client := &http.Client{
Transport: decorator.Decorate(
http.DefaultTransport,
middleware.BasicAuth("user", "pass"),
),
}
// With automatic logging (via DebugInit middleware)
logger, _ := zap.NewDevelopment()
client := &http.Client{
Transport: decorator.Decorate(
http.DefaultTransport,
middleware.DebugInit(logger, middleware.DefaultDebugConfig()), // Sets logger in context
middleware.BasicAuth("user", "pass"), // Uses logger from context
),
}
func ContextTokenAuth ¶ added in v0.1.1
ContextTokenAuth is a middleware decorator that sets the Authorization header using a token stored in the request context via WithToken.
Intended use cases:
- Rotating or short-lived tokens fetched per request
- Multi-tenant clients where each request carries its own credentials
- Overriding a StaticTokenAuth when both are present (context wins)
Behavior:
- If TokenContextKey exists in the context, the Authorization header is set to "Bearer <value>"
- If the key is missing, the middleware does nothing (allowing a static token to remain)
Logging:
- Uses logger from context when available (set by Debug/ZapVerboseLogger)
- Logs token length only; never logs the token value
func DebugFinal ¶ added in v0.1.1
func DebugFinal(config DebugConfig) decorator.Decorator
DebugFinal returns a middleware decorator that logs the final request state before sending. It should be placed at the TAIL of the middleware chain (innermost decorator, closest to transport).
DebugFinal performs the following:
- Logs the final request details including all headers set by other middleware
- Shows the complete picture of what is about to be sent to the server
This is useful for seeing the complete state of the request just before sending, after all middleware have had a chance to modify headers, add authentication, etc.
For complete debug logging, use DebugInit at the head and DebugFinal at the tail:
transport := decorator.Decorate(
http.DefaultTransport,
middleware.DebugInit(logger, config), // HEAD: initialize + log start
middleware.RateLimiter(manager),
middleware.StaticTokenAuth("token"), // Adds Authorization header
middleware.UserAgent(), // Adds User-Agent header
middleware.DebugFinal(config), // TAIL: log final request before sending
)
DebugFinal retrieves the logger from context (set by DebugInit). If no logger is found in context, it silently skips logging.
Example output:
[DEBUG] ⚡ Final request ready {"method": "GET", "url": "http://example.com"}
[DEBUG] » Final headers {"headers": {"Authorization": "token xxx", "User-Agent": "Mozilla/5.0 ..."}}
func DebugInit ¶ added in v0.1.1
func DebugInit(logger *zap.Logger, config DebugConfig) decorator.Decorator
DebugInit returns a middleware decorator that initializes debug context and logs request start. It should be placed at the HEAD of the middleware chain (outermost decorator).
DebugInit performs the following:
- Generates a unique Request ID and attaches it to the context
- Attaches RequestStats to the context if not already present
- Attaches Logger to the context for downstream middleware (annotated with Request ID)
- Logs the initial request information (method, URL)
- Logs initial request headers if LogHeaders is enabled
- After request completes, logs rate limiter info, retries, phases, and final status
For complete debug logging (including final request state with all headers), use DebugInit at the head and DebugFinal at the tail:
transport := decorator.Decorate(
http.DefaultTransport,
middleware.DebugInit(logger, config), // HEAD: initialize + log start
middleware.RateLimiter(manager),
middleware.RetryOnTimeout(5, 60*time.Second),
middleware.DebugFinal(config), // TAIL: log final request before sending
)
Logger Propagation: The logger is automatically propagated through the context. Downstream middleware can retrieve it using GetLogger(req.Context()) and use it for logging without requiring explicit logger parameters. The propagated logger includes the "request_id" field.
Concurrency Safety: This middleware is safe for concurrent use. Each request gets its own annotated logger instance stored in the context, preventing request_id field accumulation across concurrent requests. The provided logger parameter is read-only and never modified.
Example output:
[DEBUG] → Request starting {"request_id": "202512081652_abc8jikx", "method": "GET", "url": "http://example.com"}
[DEBUG] ← Request completed {"request_id": "202512081652_abc8jikx", "duration": "1.234s", "retries": 2, "status": 200}
func DumpRequest ¶
DumpRequest creates a middleware that saves HTTP requests to files for debugging.
func DumpResponse ¶
DumpResponse creates a middleware that saves HTTP responses to files for debugging.
func GetLogger ¶ added in v0.1.1
GetLogger retrieves the logger from the context.
Purpose: This function allows custom middleware to use the logger that was set up by DebugInit. This enables consistent logging throughout the middleware chain without passing logger references explicitly.
Parameters:
- ctx: The context to retrieve the logger from
Returns:
- *zap.Logger if a logger is attached, nil otherwise
Example - In custom middleware:
func myMiddleware(next http.RoundTripper) http.RoundTripper {
return decorator.TransportFunc(func(req *http.Request) (*http.Response, error) {
if logger := middleware.GetLogger(req.Context()); logger != nil {
logger.Debug("My middleware processing request",
zap.String("url", req.URL.String()),
)
}
return next.RoundTrip(req)
})
}
Example - Conditional logging:
logger := middleware.GetLogger(req.Context())
if logger != nil {
logger.Info("Request completed", zap.Int("status", resp.StatusCode))
}
func GetRequestID ¶ added in v0.1.1
GetRequestID retrieves the request ID from the context.
func GetToken ¶ added in v0.1.1
GetToken retrieves a token from the context. Returns the token and a boolean indicating whether the key was present.
func RateLimiter ¶ added in v0.1.0
func RateLimiter(manager *RateLimiterManager) decorator.Decorator
RateLimiter is a middleware decorator that applies rate limiting based on context keys.
How It Works:
- The middleware checks for middleware.RateLimiterKey in the request context
- If a key is found, it retrieves (or creates) a rate limiter for that specific key
- The request blocks until a token is available from the rate limiter
- If no key is set, the request passes through without any rate limiting
Key Features:
- Per-key rate limiting: Different keys get independent rate limit buckets
- Automatic logger detection: Uses logger from context if set by DebugInit
- Statistics integration: Records wait times to RequestStats if available
- Context cancellation: Respects context deadlines and cancellation
Parameters:
- manager: A RateLimiterManager created via NewRateLimiterManager
Example - Basic setup:
// Create a manager: 10 requests/second with burst of 20
manager := middleware.NewRateLimiterManager(rate.Limit(10), 20)
// Apply to transport
transport := decorator.Decorate(
http.DefaultTransport,
middleware.RateLimiter(manager),
)
client := &http.Client{Transport: transport}
Example - Setting the rate limit key:
// Each unique key gets its own rate limiter ctx := context.WithValue(context.Background(), middleware.RateLimiterKey, "user-123") req, _ := http.NewRequestWithContext(ctx, "GET", url, nil) resp, err := client.Do(req) // Rate limited per "user-123"
Example - With debug logging:
logger, _ := zap.NewDevelopment()
transport := decorator.Decorate(
http.DefaultTransport,
middleware.DebugInit(logger, middleware.DefaultDebugConfig()), // Provides logger
middleware.RateLimiter(manager), // Automatically logs wait times
middleware.DebugFinal(middleware.DefaultDebugConfig()),
)
Example - Using preset package (recommended):
client := preset.NewHTTPClient(
preset.WithRateLimit(10, 20), // 10 req/s, burst 20
preset.WithDebugSimple(logger),
)
func RetryOnBadProxy ¶
RetryOnBadProxy creates a middleware that retries requests on gateway/proxy errors.
This middleware retries requests when proxy or gateway errors are encountered (502, 504). It properly handles request bodies and closes response bodies to avoid connection leaks.
The middleware automatically uses a logger from the request context if one was set by DebugInit middleware earlier in the middleware chain.
Parameters:
- maxTimes: maximum number of attempts (including the initial request)
Example:
client := &http.Client{
Transport: decorator.Decorate(
http.DefaultTransport,
middleware.DebugInit(logger, config), // Sets logger in context
middleware.RetryOnBadProxy(6), // Uses logger from context
),
}
func RetryOnTimeout ¶
RetryOnTimeout creates a middleware that retries requests on timeout.
This middleware creates a new context with the specified timeout for EACH retry attempt. The timeout is per-attempt, not cumulative.
The middleware automatically uses a logger from the request context if one was set by DebugInit middleware earlier in the middleware chain.
Parameters:
- maxTimes: maximum number of attempts (including the initial request)
- timeout: timeout duration for EACH attempt
Example:
client := &http.Client{
Transport: decorator.Decorate(
http.DefaultTransport,
middleware.DebugInit(logger, config), // Sets logger in context
middleware.RetryOnTimeout(3, 10*time.Second), // Uses logger from context
),
}
func StaticTokenAuth ¶ added in v0.1.1
StaticTokenAuth is a middleware decorator for bearer token authentication with a fixed token.
The middleware automatically uses a logger from the context if one was set by Debug or ZapVerboseLogger middleware. This enables automatic logging of authentication events without explicit logger configuration.
Note: For security reasons, the token value is never logged, only its presence.
Example:
// Basic usage without logging
client := &http.Client{
Transport: decorator.Decorate(
http.DefaultTransport,
middleware.StaticTokenAuth("your-api-token"),
),
}
// With automatic logging (via DebugInit middleware)
logger, _ := zap.NewDevelopment()
client := &http.Client{
Transport: decorator.Decorate(
http.DefaultTransport,
middleware.DebugInit(logger, middleware.DefaultDebugConfig()), // Sets logger in context
middleware.StaticTokenAuth("your-api-token"), // Uses logger from context
),
}
func UserAgent ¶
UserAgent is a middleware decorator that adds a User-Agent header to requests.
How It Works:
- The middleware checks for middleware.UserAgentRandomSeed in the request context
- If a seed is found, it generates a deterministic User-Agent based on that seed
- If no seed is set, it generates a weighted random User-Agent
Key Features:
- Weighted random selection: Common browsers are more likely to be selected
- Deterministic mode: Same seed always produces the same User-Agent
- Session consistency: Use the same seed for all requests in a session
Example - Random User-Agent (default):
// Each request gets a random User-Agent
client := preset.NewHTTPClient(preset.WithUserAgent())
req, _ := http.NewRequest("GET", "https://api.example.com", nil)
resp, _ := client.Do(req)
Example - Consistent User-Agent per session:
client := preset.NewHTTPClient(preset.WithUserAgent()) // All requests with the same seed get the same User-Agent sessionID := getUserSessionID() ctx := context.WithValue(context.Background(), middleware.UserAgentRandomSeed, sessionID) req, _ := http.NewRequestWithContext(ctx, "GET", "https://api.example.com", nil) resp, _ := client.Do(req)
Example - Direct decorator usage:
transport := decorator.Decorate(
http.DefaultTransport,
middleware.UserAgent(),
)
client := &http.Client{Transport: transport}
func WithLogger ¶ added in v0.1.1
WithLogger attaches a logger to the context.
Purpose: This function enables logger propagation for the request. Once attached, downstream middleware (RateLimiter, RetryOnTimeout, etc.) will automatically use this logger for their debug output without requiring explicit configuration.
Parameters:
- ctx: The parent context
- logger: A zap.Logger instance
Returns:
- A new context with the logger attached
Note: In most cases, you don't need to call this directly. The DebugInit middleware (used via preset.WithDebug or preset.WithDebugSimple) automatically attaches the logger to the context.
Example - Manual logger attachment:
logger, _ := zap.NewDevelopment() ctx := middleware.WithLogger(context.Background(), logger) req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
Example - Combined with other context values:
ctx := context.Background() ctx = middleware.WithLogger(ctx, logger) ctx = context.WithValue(ctx, middleware.RateLimiterKey, "user-123") req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
Example - Recommended approach (using preset):
// The logger is automatically attached to context client := preset.NewHTTPClient(preset.WithDebugSimple(logger))
func WithRequestID ¶ added in v0.1.1
WithRequestID attaches a request ID to the context.
func WithStats ¶ added in v0.1.0
func WithStats(ctx context.Context, stats *RequestStats) context.Context
WithStats attaches a RequestStats instance to the given context.
Purpose: This function enables statistics collection for a request. Once attached, middleware throughout the chain will record their timing and behavior data to this stats instance.
Parameters:
- ctx: The parent context
- stats: A RequestStats instance (created via NewRequestStats)
Returns:
- A new context with the stats attached
Example - Basic usage:
stats := middleware.NewRequestStats()
ctx := middleware.WithStats(context.Background(), stats)
req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
resp, _ := client.Do(req)
// Inspect collected statistics
fmt.Printf("Request took %v\n", stats.GetTotalDuration())
Example - Combining with other context values:
ctx := context.Background() ctx = middleware.WithStats(ctx, middleware.NewRequestStats()) ctx = context.WithValue(ctx, middleware.RateLimiterKey, "user-123") req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
func WithToken ¶ added in v0.1.1
WithToken attaches a token to the given context for per-request authentication.
Example:
ctx := middleware.WithToken(context.Background(), "token-from-vault") req, _ := http.NewRequestWithContext(ctx, "GET", url, nil) resp, _ := client.Do(req)
func XdailiProxyAuth ¶
XdailiProxyAuth is a middleware decorator for authentication of xdaili.
Types ¶
type DebugConfig ¶ added in v0.1.0
type DebugConfig struct {
// LogTimings enables logging of phase timings and total duration
LogTimings bool
// LogRetries enables logging of retry attempts
LogRetries bool
// LogRateLimit enables logging of rate limiter waits
LogRateLimit bool
// LogPhases enables detailed logging of each middleware phase
LogPhases bool
// LogHeaders enables logging of request/response headers (may contain sensitive data)
LogHeaders bool
// ChainNames holds the names of middlewares in the decorator chain.
// When set, the chain structure is logged once on first request.
// This is typically populated by preset.NewHTTPClient automatically.
ChainNames []string
}
DebugConfig configures what information the Debug middleware should log.
func DefaultDebugConfig ¶ added in v0.1.0
func DefaultDebugConfig() DebugConfig
DefaultDebugConfig returns a debug configuration with all options enabled.
type Phase ¶ added in v0.1.0
type Phase struct {
// Name is the name of this phase (e.g., "rate_limiter", "retry", "proxy")
Name string
// StartTime is when this phase started
StartTime time.Time
// Duration is how long this phase took
Duration time.Duration
// Metadata contains phase-specific information (e.g., retry attempt number)
Metadata map[string]interface{}
// Error is any error that occurred in this phase
Error error
}
Phase represents a single phase in the request processing pipeline.
type RateLimiterManager ¶ added in v0.1.0
type RateLimiterManager struct {
// contains filtered or unexported fields
}
RateLimiterManager manages multiple rate limiters keyed by string identifiers. It creates rate limiters on-demand and stores them for reuse. To prevent unbounded memory growth, it enforces a maximum number of limiters and uses LRU eviction when the limit is reached.
Uses sync.Map for better concurrent performance and to avoid race conditions in the double-check locking pattern.
func NewRateLimiterManager ¶ added in v0.1.0
func NewRateLimiterManager(limit rate.Limit, burst int) *RateLimiterManager
NewRateLimiterManager creates a new rate limiter manager with the specified rate limit and burst size.
The manager limits the number of cached limiters to prevent memory leaks. When the limit is reached, the least recently used limiter is evicted.
A random 8-character ID is automatically generated for the manager to aid in debugging and logging. You can override this ID by calling SetID() on the returned manager.
Parameters:
- limit: the rate limit in requests per second (e.g., rate.Limit(10) for 10 requests/second)
- burst: the maximum burst size (number of tokens that can be consumed at once). If burst is less than 1, it will be set to 1 to ensure requests can proceed.
Example:
manager := NewRateLimiterManager(rate.Limit(10), 20) // 10 req/s, burst of 20
// manager.ID() will return something like "a3b7k9m2"
manager.SetID("my-custom-id") // Optional: override the auto-generated ID
func (*RateLimiterManager) Clear ¶ added in v0.1.1
func (m *RateLimiterManager) Clear()
Clear removes all cached limiters from the manager. This is useful for cleanup during application shutdown or testing. After calling Clear(), new requests will create fresh limiters.
func (*RateLimiterManager) Close ¶ added in v0.1.1
func (m *RateLimiterManager) Close()
Close releases all resources held by the manager. This is an alias for Clear() and satisfies common cleanup patterns. The manager can still be used after Close() is called.
func (*RateLimiterManager) GetLimiter ¶ added in v0.1.0
func (m *RateLimiterManager) GetLimiter(key string) *rate.Limiter
GetLimiter returns the rate limiter for the given key. If the limiter doesn't exist, it creates a new one with the manager's configured rate and burst. Updates the last access time for LRU eviction.
func (*RateLimiterManager) ID ¶ added in v0.1.1
func (m *RateLimiterManager) ID() string
ID returns the identifier of this rate limiter manager. Useful for debugging and logging to distinguish between multiple managers.
func (*RateLimiterManager) SetID ¶ added in v0.1.1
func (m *RateLimiterManager) SetID(id string)
SetID sets the identifier for this rate limiter manager. This should be called before the manager is used for clearer configuration.
func (*RateLimiterManager) SetMaxSize ¶ added in v0.1.0
func (m *RateLimiterManager) SetMaxSize(maxSize int)
SetMaxSize sets the maximum number of limiters to cache. When this limit is reached, the least recently used limiter is evicted. This should be called before the manager is used.
func (*RateLimiterManager) Size ¶ added in v0.1.0
func (m *RateLimiterManager) Size() int
Size returns the current number of cached limiters.
type RequestStats ¶ added in v0.1.0
type RequestStats struct {
// StartTime is when the request entered the middleware chain
StartTime time.Time
// EndTime is when the request completed (successfully or with error)
EndTime time.Time
// RateLimitWait is the total time spent waiting for rate limiter tokens
RateLimitWait time.Duration
// RateLimitKey is the key used for rate limiting (empty if no rate limit applied)
RateLimitKey string
// RequestID is the unique identifier for the request
RequestID string
// RetryCount is the number of retry attempts made (0 means no retries)
RetryCount int
// Phases records detailed timing for each middleware phase
Phases []Phase
// TotalDuration is the total time from start to end
TotalDuration time.Duration
// Error is the final error if the request failed
Error error
// contains filtered or unexported fields
}
RequestStats collects detailed statistics about a single HTTP request's journey through the middleware chain. This includes timing information, retry counts, rate limiting delays, and phase-by-phase execution details.
RequestStats is thread-safe and can be safely accessed from multiple goroutines.
func GetStats ¶ added in v0.1.0
func GetStats(ctx context.Context) *RequestStats
GetStats retrieves the RequestStats from the context.
Purpose: This function allows any code with access to the request context to retrieve collected statistics. Useful for custom middleware, logging, or post-request analysis.
Parameters:
- ctx: The context to retrieve stats from
Returns:
- *RequestStats if stats are attached, nil otherwise
Example - In custom middleware:
func myMiddleware(next http.RoundTripper) http.RoundTripper {
return decorator.TransportFunc(func(req *http.Request) (*http.Response, error) {
stats := middleware.GetStats(req.Context())
start := time.Now()
resp, err := next.RoundTrip(req)
if stats != nil {
stats.AddPhase("my_middleware", start, time.Since(start), nil, err)
}
return resp, err
})
}
Example - Post-request analysis:
stats := middleware.NewRequestStats()
ctx := middleware.WithStats(context.Background(), stats)
req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
resp, _ := client.Do(req)
// Check if rate limiting caused delays
if stats.GetRateLimitWait() > time.Second {
log.Printf("Warning: Rate limit wait was %v", stats.GetRateLimitWait())
}
func NewRequestStats ¶ added in v0.1.0
func NewRequestStats() *RequestStats
NewRequestStats creates a new RequestStats instance with StartTime set to now.
func (*RequestStats) AddPhase ¶ added in v0.1.0
func (s *RequestStats) AddPhase(name string, startTime time.Time, duration time.Duration, metadata map[string]interface{}, err error)
AddPhase records a completed phase in the request pipeline.
func (*RequestStats) Finalize ¶ added in v0.1.0
func (s *RequestStats) Finalize(err error)
Finalize marks the request as complete and calculates total duration. This should be called when the request finishes (successfully or with error).
func (*RequestStats) GetError ¶ added in v0.1.0
func (s *RequestStats) GetError() error
GetError returns the final error (if any). Thread-safe.
func (*RequestStats) GetPhases ¶ added in v0.1.0
func (s *RequestStats) GetPhases() []Phase
GetPhases returns a deep copy of all recorded phases. Thread-safe.
func (*RequestStats) GetRateLimitKey ¶ added in v0.1.0
func (s *RequestStats) GetRateLimitKey() string
GetRateLimitKey returns the rate limiter key used for this request. Thread-safe.
func (*RequestStats) GetRateLimitWait ¶ added in v0.1.0
func (s *RequestStats) GetRateLimitWait() time.Duration
GetRateLimitWait returns the total time spent waiting for rate limiter tokens. Thread-safe.
func (*RequestStats) GetRequestID ¶ added in v0.1.1
func (s *RequestStats) GetRequestID() string
GetRequestID returns the unique request identifier. Thread-safe.
func (*RequestStats) GetRetryCount ¶ added in v0.1.0
func (s *RequestStats) GetRetryCount() int
GetRetryCount returns the number of retry attempts made. Thread-safe.
func (*RequestStats) GetTotalDuration ¶ added in v0.1.0
func (s *RequestStats) GetTotalDuration() time.Duration
GetTotalDuration returns the total request duration. Thread-safe.
func (*RequestStats) RecordRateLimitWait ¶ added in v0.1.0
func (s *RequestStats) RecordRateLimitWait(duration time.Duration, key string)
RecordRateLimitWait records time spent waiting for rate limiter tokens. This is additive - multiple calls will sum the durations.
func (*RequestStats) RecordRetry ¶ added in v0.1.0
func (s *RequestStats) RecordRetry()
RecordRetry increments the retry counter.
func (*RequestStats) SetRequestID ¶ added in v0.1.1
func (s *RequestStats) SetRequestID(id string)
SetRequestID sets the unique request identifier.
func (*RequestStats) StartPhase ¶ added in v0.1.0
func (s *RequestStats) StartPhase(name string, metadata map[string]interface{}) func(error)
StartPhase returns a function that should be called when the phase completes. This is a convenience method for recording phase timing.
Example:
defer stats.StartPhase("my_phase", map[string]interface{}{"key": "value"})()
type RetryConfig ¶ added in v0.1.0
type RetryConfig struct {
// MaxAttempts is the maximum number of attempts (including the initial request)
MaxAttempts uint
// Delay is the initial delay between retries
Delay time.Duration
// MaxDelay is the maximum delay between retries
MaxDelay time.Duration
// MaxJitter is the maximum jitter to add to delays
MaxJitter time.Duration
// RetryIf is the function to determine if a request should be retried
RetryIf func(error) bool
// PreRequest is called before each request attempt
PreRequest func(req *http.Request, attemptNum int) (*http.Request, error)
// PostResponse is called after each response (before retry decision)
PostResponse func(resp *http.Response, err error) (*http.Response, error)
// LogTag is the tag for debug logging
LogTag string
}
RetryConfig defines configuration for retry middleware