Middleware Chain
Onion Model
HTTPC middleware follows an onion model -- requests go from outer to inner, responses from inner to outer:
Request -> Recovery -> Logging -> RequestID -> Handler
|
Response <- Recovery <- Logging <- RequestID <- Responsecfg := httpc.DefaultConfig()
cfg.Middleware.Middlewares = []httpc.MiddlewareFunc{
httpc.RecoveryMiddleware(), // Outermost: panic recovery
httpc.LoggingMiddleware(log.Printf), // Second layer: logging
httpc.RequestIDMiddleware("X-Request-ID", nil), // Innermost: request ID
}
client, _ := httpc.New(cfg)Built-in Middleware
RecoveryMiddleware
Panic recovery to prevent process crashes:
httpc.RecoveryMiddleware()LoggingMiddleware
Request/response logging with automatic URL masking:
httpc.LoggingMiddleware(func(format string, args ...any) {
log.Printf("[HTTP] "+format, args...)
})
// Output example: [HTTP] GET https://api.example.com/data -> 200 (150ms) (status code and duration are measured values, not fixed)RequestIDMiddleware
Adds a unique ID to each request, generated with crypto/rand:
httpc.RequestIDMiddleware("X-Request-ID", nil) // Default 32-char hex
// Custom generator
httpc.RequestIDMiddleware("X-Request-ID", func() string {
return uuid.New().String()
})TimeoutMiddleware
Middleware-level timeout enforced before the client timeout:
httpc.TimeoutMiddleware(30 * time.Second)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 the WithTimeout option instead.
HeaderMiddleware
Adds static headers to all requests:
httpc.HeaderMiddleware(map[string]string{
"X-App-Version": "1.0.0",
"X-Platform": "server",
})MetricsMiddleware
Collects request metrics:
httpc.MetricsMiddleware(func(method, url string, statusCode int, duration time.Duration, err error) {
metrics.IncrCounter("http.requests", 1)
metrics.RecordTimer("http.latency", duration)
if err != nil {
metrics.IncrCounter("http.errors", 1)
}
})AuditMiddleware
Security auditing for financial, medical, and other compliance scenarios:
httpc.AuditMiddleware(func(event httpc.AuditEvent) {
log.Printf("[AUDIT] %s %s -> %d (%v)",
event.Method, event.URL, event.StatusCode, event.Duration)
})AuditMiddlewareWithConfig
Configurable audit middleware:
auditCfg := &httpc.AuditMiddlewareConfig{
Format: "json",
IncludeHeaders: true,
MaskHeaders: []string{"Authorization", "Cookie"},
SanitizeError: true,
}
httpc.AuditMiddlewareWithConfig(func(event httpc.AuditEvent) {
data, _ := json.Marshal(event)
log.Println(string(data))
}, auditCfg)Audit events support extracting SourceIP and UserID from context:
ctx := context.WithValue(context.Background(), httpc.SourceIPKey, "192.168.1.1")
ctx = context.WithValue(ctx, httpc.UserIDKey, "user-123")Manual Chain Composition
Use the Chain function to compose middleware:
middleware := httpc.Chain(
httpc.RecoveryMiddleware(),
httpc.LoggingMiddleware(log.Printf),
httpc.RequestIDMiddleware("X-Request-ID", nil),
)
cfg := httpc.DefaultConfig()
cfg.Middleware.Middlewares = []httpc.MiddlewareFunc{middleware}Custom Middleware
func CORSMiddleware(origin string) httpc.MiddlewareFunc {
return func(next httpc.Handler) httpc.Handler {
return func(ctx context.Context, req httpc.RequestMutator) (httpc.ResponseMutator, error) {
// Request phase: modify request
req.SetHeader("Origin", origin)
// Call next handler
resp, err := next(ctx, req)
// Response phase: log or modify response
if resp != nil {
log.Printf("Response status: %d", resp.StatusCode())
}
return resp, err
}
}
}Short-Circuit Middleware
func CircuitBreakerMiddleware(threshold int) httpc.MiddlewareFunc {
var failures int
var mu sync.Mutex
return func(next httpc.Handler) httpc.Handler {
return func(ctx context.Context, req httpc.RequestMutator) (httpc.ResponseMutator, error) {
mu.Lock()
if failures >= threshold {
mu.Unlock()
return nil, fmt.Errorf("circuit breaker open")
}
mu.Unlock()
resp, err := next(ctx, req)
if err != nil {
mu.Lock()
failures++
mu.Unlock()
}
return resp, err
}
}
}Middleware Configuration
cfg := httpc.DefaultConfig()
cfg.Middleware = &httpc.MiddlewareConfig{
Middlewares: []httpc.MiddlewareFunc{
httpc.RecoveryMiddleware(),
httpc.LoggingMiddleware(log.Printf),
},
UserAgent: "my-app/1.0",
Headers: map[string]string{"X-App": "my-app"},
FollowRedirects: true,
MaxRedirects: 10,
}
client, _ := httpc.New(cfg)Next Steps
- Middleware API - Complete middleware reference
- Retry and Fault Tolerance - Retry strategy guide
- Security Overview - Audit middleware security practices