Middleware
Architecture Overview
This page is the built-in middleware reference. For the overall Handler pipeline architecture, the onion model, and writing custom middleware, see Handler Pipeline / Handler & Middleware Chain.
HTTPC uses an onion-model middleware architecture, wrapping request handling logic via MiddlewareFunc.
type MiddlewareFunc func(Handler) Handler
type Handler func(ctx context.Context, req RequestMutator) (ResponseMutator, error)Middleware is configured in MiddlewareConfig.Middlewares and executes in order:
client, _ := httpc.New(&httpc.Config{
Middleware: &httpc.MiddlewareConfig{
Middlewares: []httpc.MiddlewareFunc{
httpc.RecoveryMiddleware(),
httpc.LoggingMiddleware(log.Printf),
httpc.RequestIDMiddleware("X-Request-ID", nil),
},
},
})Chain
func Chain(middlewares ...MiddlewareFunc) MiddlewareFuncCombines multiple middleware into a single middleware. Executes in the order passed; the last middleware calls the final Handler when done.
combined := httpc.Chain(
httpc.RecoveryMiddleware(),
httpc.LoggingMiddleware(log.Printf),
)Built-in Middleware
RecoveryMiddleware
func RecoveryMiddleware() MiddlewareFuncPanic recovery middleware. Catches panics in the processing chain and converts them to errors with stack traces.
client, _ := httpc.New(&httpc.Config{
Middleware: &httpc.MiddlewareConfig{
Middlewares: []httpc.MiddlewareFunc{
httpc.RecoveryMiddleware(),
},
},
})LoggingMiddleware
func LoggingMiddleware(log func(format string, args ...any)) MiddlewareFuncRequest logging middleware. Logs method, URL, status code, and duration. URLs are automatically masked (credentials removed).
client, _ := httpc.New(&httpc.Config{
Middleware: &httpc.MiddlewareConfig{
Middlewares: []httpc.MiddlewareFunc{
httpc.LoggingMiddleware(log.Printf),
},
},
})
// Output example: GET https://api.example.com/data -> 200 (125ms)RequestIDMiddleware
func RequestIDMiddleware(headerName string, generator func() string) MiddlewareFuncAdds a unique ID to each request. Defaults to a 32-character hex ID generated with crypto/rand. If the request already has a header with the same name, the original value is preserved (not overwritten).
| Parameter | Description |
|---|---|
headerName | Header name, e.g. "X-Request-ID" |
generator | Custom ID generation function; pass nil for the default cryptographically secure generator |
// Use default generator
middleware := httpc.RequestIDMiddleware("X-Request-ID", nil)
// Use custom generator
middleware := httpc.RequestIDMiddleware("X-Request-ID", func() string {
return uuid.New().String()
})TIP
The default generator uses crypto/rand, producing unpredictable IDs suitable for security-sensitive scenarios.
TimeoutMiddleware
func TimeoutMiddleware(timeout time.Duration) MiddlewareFuncMiddleware-level timeout control. Takes effect before the client's built-in timeout; cancels context and returns an error on timeout.
Do not use for Download or streaming requests
TimeoutMiddleware's defer cancel() fires immediately after the handler returns (i.e., once the response headers are received), so for Download or WithStreamBody requests it cancels the context before the response body is read, producing a "context canceled" error. For streaming/download scenarios, use WithTimeout.
client, _ := httpc.New(&httpc.Config{
Middleware: &httpc.MiddlewareConfig{
Middlewares: []httpc.MiddlewareFunc{
httpc.TimeoutMiddleware(10 * time.Second),
},
},
})HeaderMiddleware
func HeaderMiddleware(headers map[string]string) MiddlewareFuncAdds static headers to every request. Header security is validated at creation time (CRLF injection protection). Conflicts with existing same-name headers will overwrite them.
client, _ := httpc.New(&httpc.Config{
Middleware: &httpc.MiddlewareConfig{
Middlewares: []httpc.MiddlewareFunc{
httpc.HeaderMiddleware(map[string]string{
"X-API-Version": "v2",
"X-Client": "myapp/1.0",
}),
},
},
})MetricsMiddleware
func MetricsMiddleware(onMetrics func(method, url string, statusCode int, duration time.Duration, err error)) MiddlewareFuncMetrics collection middleware. Invokes the callback after each request completes, passing method, URL, status code, duration, and error.
client, _ := httpc.New(&httpc.Config{
Middleware: &httpc.MiddlewareConfig{
Middlewares: []httpc.MiddlewareFunc{
httpc.MetricsMiddleware(func(method, url string, status int, d time.Duration, err error) {
metrics.Record(method, status, d, err)
}),
},
},
})AuditMiddleware
func AuditMiddleware(onAudit func(event AuditEvent)) MiddlewareFuncSecurity audit middleware, suitable for financial, medical, government, and other compliance scenarios. By default, records request/response metadata (method, URL, status code, duration, retries, etc.) with automatic URL masking; for full headers use AuditMiddlewareWithConfig with IncludeHeaders: true.
client, _ := httpc.New(&httpc.Config{
Middleware: &httpc.MiddlewareConfig{
Middlewares: []httpc.MiddlewareFunc{
httpc.AuditMiddleware(func(event httpc.AuditEvent) {
log.Printf("[AUDIT] %s %s -> %d (%v) user=%s ip=%s",
event.Method, event.URL, event.StatusCode,
event.Duration, event.UserID, event.SourceIP)
}),
},
},
})AuditMiddlewareWithConfig
func AuditMiddlewareWithConfig(onAudit func(event AuditEvent), config *AuditMiddlewareConfig) MiddlewareFuncConfigurable security audit middleware.
config := &httpc.AuditMiddlewareConfig{
Format: "json",
IncludeHeaders: true,
MaskHeaders: []string{"Authorization", "Cookie"},
SanitizeError: true,
}
client, _ := httpc.New(&httpc.Config{
Middleware: &httpc.MiddlewareConfig{
Middlewares: []httpc.MiddlewareFunc{
httpc.AuditMiddlewareWithConfig(func(event httpc.AuditEvent) {
data, _ := json.Marshal(event)
auditLog.Write(data)
}, config),
},
},
})Audit Types
AuditEvent
type AuditEvent struct {
Timestamp time.Time `json:"timestamp"`
Method string `json:"method"`
URL string `json:"url"` // Masked (credentials removed)
StatusCode int `json:"statusCode"`
Duration time.Duration `json:"duration"`
Attempts int `json:"attempts"`
Error error `json:"error,omitempty"`
SourceIP string `json:"sourceIP,omitempty"`
UserID string `json:"userID,omitempty"`
RedirectChain []string `json:"redirectChain,omitempty"`
ReqHeaders map[string][]string `json:"reqHeaders,omitempty"`
RespHeaders map[string][]string `json:"respHeaders,omitempty"`
}Security audit event.
MarshalJSON
func (e AuditEvent) MarshalJSON() ([]byte, error)Custom JSON serialization, handling two special fields:
| Field | Conversion Rule |
|---|---|
Duration | Adds durationMs (integer milliseconds), preserves original duration field (nanoseconds) |
Error | Converts to error (error message string), omitted when nil |
event := httpc.AuditEvent{
Method: "GET",
URL: "https://api.example.com/data",
Duration: 150 * time.Millisecond,
StatusCode: 200,
}
data, _ := json.Marshal(event)
// {"timestamp":"...","method":"GET","url":"...","statusCode":200,"duration":150000000,"attempts":0,"durationMs":150}AuditMiddlewareConfig
type AuditMiddlewareConfig struct {
Format string // "text" (default) or "json"
IncludeHeaders bool // Whether to include request/response headers
MaskHeaders []string // Header names to mask
SanitizeError bool // Whether to mask error messages
}| Field | Default | Description |
|---|---|---|
| Format | "text" | Output format |
| IncludeHeaders | false | Whether to record headers |
| MaskHeaders | ["Authorization", "Cookie", ...] | Standard sensitive header list |
| SanitizeError | true | Error messages replaced with [sanitized] |
DefaultAuditMiddlewareConfig
func DefaultAuditMiddlewareConfig() *AuditMiddlewareConfigReturns default audit configuration.
Audit Context Keys
Pass audit information via request context:
// Set source IP
ctx = context.WithValue(ctx, httpc.SourceIPKey, "192.168.1.1")
// Set user ID
ctx = context.WithValue(ctx, httpc.UserIDKey, "user-123")
result, err := client.Request(ctx, "GET", url)| Constant | Type | Description |
|---|---|---|
SourceIPKey | auditContextKey | Source IP context key |
UserIDKey | auditContextKey | User ID context key |
See Also
- Interface Definitions - MiddlewareFunc, Handler type definitions
- Middleware Chain - Middleware usage guide
- Constants and Types - AuditEvent, AuditMiddlewareConfig types