middleware

package
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 12, 2025 License: MIT Imports: 23 Imported by: 0

Documentation

Overview

Package middleware provides HTTP middleware implementations for common cross-cutting concerns.

Index

Constants

This section is empty.

Variables

View Source
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)
View Source
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{}
)
View Source
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

View Source
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())
View Source
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{}
)
View Source
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

func BasicAuth(username, password string) decorator.Decorator

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

func ContextTokenAuth() decorator.Decorator

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

func DumpRequest(outDir ...string) decorator.Decorator

DumpRequest creates a middleware that saves HTTP requests to files for debugging.

func DumpResponse

func DumpResponse(outDir ...string) decorator.Decorator

DumpResponse creates a middleware that saves HTTP responses to files for debugging.

func GetLogger added in v0.1.1

func GetLogger(ctx context.Context) *zap.Logger

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

func GetRequestID(ctx context.Context) string

GetRequestID retrieves the request ID from the context.

func GetToken added in v0.1.1

func GetToken(ctx context.Context) (string, bool)

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

func RetryOnBadProxy(maxTimes uint) decorator.Decorator

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

func RetryOnTimeout(maxTimes uint, timeout time.Duration) decorator.Decorator

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

func StaticTokenAuth(token string) decorator.Decorator

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

func UserAgent() decorator.Decorator

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

func WithLogger(ctx context.Context, logger *zap.Logger) context.Context

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

func WithRequestID(ctx context.Context, id string) context.Context

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

func WithToken(ctx context.Context, token string) context.Context

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

func XdailiProxyAuth(order, secret string) decorator.Decorator

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

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL