-
Notifications
You must be signed in to change notification settings - Fork 0
Middleware Configuration
The codechunking API server implements a comprehensive middleware system built on hexagonal architecture principles. The middleware provides cross-cutting concerns including logging, security, CORS, error handling, and request validation. All middleware is located in internal/adapter/inbound/api/.
- Middleware Architecture
- Available Middleware
- Advanced Middleware
- Middleware Chain Composition
- Configuration Reference
- Usage Examples
- Best Practices
The middleware layer sits at the adapter/inbound boundary, intercepting HTTP requests before they reach the application core. This design ensures:
- Separation of Concerns: Cross-cutting concerns are isolated from business logic
- Testability: Each middleware can be tested independently
- Composability: Middleware can be mixed and matched based on needs
- Observability: Centralized logging and monitoring across all requests
All middleware follows the standard Go HTTP middleware pattern:
type Middleware func(http.Handler) http.HandlerMiddleware is applied in reverse order for proper stacking. The middleware chain executes from outermost to innermost:
Request → Security Headers → Rate Limiting → Content Type → Security Validation → Handler
Response ← Security Headers ← Rate Limiting ← Content Type ← Security Validation ← Handler
Location: internal/adapter/inbound/api/middleware.go:176
Provides request/response logging with structured fields and request ID tracking.
-
Request ID Management: Generates or uses existing
X-Request-IDheaders - Correlation ID Propagation: Tracks requests across system boundaries
- Performance Metrics: Measures request duration
- Structured Fields: Logs method, path, status, duration, user agent, client IP
- Query Parameter Logging: Captures URL query parameters
- Field Chaining Optimization: Minimizes memory allocations
logger := NewDefaultLogger()
middleware := NewLoggingMiddleware(logger)
handler := middleware(yourHandler)The middleware injects request IDs into the request context:
// Extract request ID from context
requestID := GetRequestID(ctx)
// Set request ID in context
ctx = SetRequestID(ctx, "custom-request-id")Logging behavior can be customized through the DefaultLogger:
logger := NewDefaultLogger()
contextLogger := logger.
WithField("service", "api").
WithField("environment", "production")- Field Chain Pattern: Avoids premature map copying when creating logger contexts
- Deferred Materialization: Only materializes field maps when actually logging
- Response Writer Wrapping: Efficiently captures status codes without buffering
Location: internal/adapter/inbound/api/middleware.go:298-424
Adds comprehensive security headers to all HTTP responses. Uses the unified security headers implementation.
| Header | Default Value | Purpose |
|---|---|---|
X-Content-Type-Options |
nosniff |
Prevents MIME type sniffing |
X-Frame-Options |
DENY |
Prevents clickjacking attacks |
X-XSS-Protection |
1; mode=block |
Enables browser XSS protection |
Referrer-Policy |
strict-origin-when-cross-origin |
Controls referrer information |
Content-Security-Policy |
default-src 'self' |
Restricts resource loading |
Strict-Transport-Security |
max-age=31536000; includeSubDomains |
Enforces HTTPS (when using TLS) |
// Use default security headers
middleware := NewSecurityMiddleware()
handler := middleware(yourHandler)// Custom security configuration
config := &SecurityHeadersConfig{
ReferrerPolicy: "no-referrer",
CSPPolicy: "comprehensive", // or "basic" or custom string
HStsEnabled: boolPtr(true),
HStsMaxAge: 63072000, // 2 years
HStsSubdomains: boolPtr(true),
HStsPreload: boolPtr(true),
XFrameOptions: "SAMEORIGIN",
XContentType: "nosniff",
XXSSProtection: "1; mode=block",
}
middleware := NewUnifiedSecurityMiddleware(config)Three CSP policy levels are available:
-
Basic (default):
default-src 'self' -
Comprehensive:
default-src 'self'; script-src 'self'; object-src 'none' - Custom: Provide your own CSP string
HSTS (HTTP Strict Transport Security) is only applied when:
-
HStsEnabledis set totrue - The request is made over TLS (
r.TLS != nil)
// Disable HSTS
config := &SecurityHeadersConfig{
HStsEnabled: boolPtr(false),
}
// Enable with custom settings
config := &SecurityHeadersConfig{
HStsEnabled: boolPtr(true),
HStsMaxAge: 31536000, // 1 year
HStsSubdomains: boolPtr(true), // Include subdomains
HStsPreload: boolPtr(true), // Enable HSTS preload
}Location: internal/adapter/inbound/api/middleware.go:454-495
Handles Cross-Origin Resource Sharing (CORS) for browser-based API clients.
- Preflight Request Handling: Responds to OPTIONS requests
- Configurable Origins: Control which origins can access the API
- Method Whitelisting: Specify allowed HTTP methods
- Header Management: Configure allowed request headers
- Cache Control: Set preflight response cache duration
config := CORSConfig{
AllowedOrigins: []string{"*"},
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowedHeaders: []string{"Content-Type", "Authorization"},
MaxAge: 86400, // 24 hours
}Basic (allow all origins):
middleware := NewCORSMiddleware()Custom configuration:
config := CORSConfig{
AllowedOrigins: []string{"https://app.example.com", "https://admin.example.com"},
AllowedMethods: []string{"GET", "POST"},
AllowedHeaders: []string{"Content-Type", "Authorization", "X-API-Key"},
MaxAge: 3600, // 1 hour
}
middleware := NewCORSMiddlewareWithConfig(config)The middleware automatically appends any requested headers from Access-Control-Request-Headers to maintain backward compatibility with existing clients.
Location: internal/adapter/inbound/api/middleware.go:222-271
Provides centralized panic recovery and error response formatting.
- Panic Recovery: Catches and logs panics in HTTP handlers
- Context Cancellation Handling: Properly handles cancelled requests
- Structured Error Responses: Returns JSON error responses
- Request ID Propagation: Includes request IDs in error responses
- Comprehensive Logging: Logs panic details with request context
On Panic:
{
"error": "Internal Server Error",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}On Context Cancellation:
{
"error": "Request cancelled",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}When a panic occurs, the middleware logs:
{
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"method": "POST",
"path": "/repositories",
"panic": "runtime error: index out of range"
}middleware := NewErrorHandlingMiddleware()
handler := middleware(yourHandler)Location: internal/adapter/inbound/api/middleware/logging_middleware.go:278
A production-grade logging middleware with advanced features including async logging, object pooling, sampling, and resource monitoring.
config := LoggingConfig{
LogLevel: "INFO", // DEBUG, INFO, WARN, ERROR
EnableRequestBody: false, // Log request bodies
EnableResponseBody: false, // Log response bodies
MaxBodySize: 1024 * 64, // 64KB max body size
SensitiveHeaders: []string{"Authorization", "X-API-Key"},
SlowRequestThreshold: 500 * time.Millisecond,
EnablePerfMetrics: true,
EnablePanicLogs: true,
EnableSecurityLogging: true,
SuspiciousPatterns: []string{"bot", "scanner"},
ExcludePaths: []string{"/health", "/metrics"},
SampleRate: 1.0, // 1.0 = log everything, 0.5 = log 50%
// Performance optimizations
EnableObjectPooling: true,
EnableAsyncLogging: true,
AsyncBufferSize: 1000,
MaxMemoryMB: 512,
MaxRequestsPerSecond: 10000,
EnableResourceMetrics: true,
}
middleware := NewStructuredLoggingMiddleware(config)Process log writes in a background goroutine to minimize request latency:
config := LoggingConfig{
EnableAsyncLogging: true,
AsyncBufferSize: 1000, // Buffer up to 1000 log entries
}Reduce memory allocations by reusing log entry objects:
config := LoggingConfig{
EnableObjectPooling: true,
}Reduce logging volume in high-traffic environments:
config := LoggingConfig{
SampleRate: 0.1, // Log only 10% of requests
}Skip logging for health checks and metrics endpoints:
config := LoggingConfig{
ExcludePaths: []string{"/health", "/metrics", "/ready"},
}Track request performance and identify slow requests:
config := LoggingConfig{
EnablePerfMetrics: true,
SlowRequestThreshold: 500 * time.Millisecond,
}Detect and log suspicious request patterns:
config := LoggingConfig{
EnableSecurityLogging: true,
SuspiciousPatterns: []string{"bot", "scanner", "crawler"},
}Security checks include:
- Suspicious user agents
- Missing authorization on sensitive endpoints
- Potential SQL injection patterns
Monitor memory usage and request rates:
config := LoggingConfig{
MaxMemoryMB: 512,
MaxRequestsPerSecond: 10000,
EnableResourceMetrics: true,
}{
"timestamp": "2025-11-21T10:30:45Z",
"level": "INFO",
"message": "HTTP GET /repositories - 200",
"correlation_id": "550e8400-e29b-41d4-a716-446655440000",
"component": "http-middleware",
"operation": "http_request",
"duration": 123.45,
"request": {
"method": "GET",
"path": "/repositories",
"status": 200,
"duration": 123.45,
"request_size": 0,
"response_size": 1024,
"client_ip": "192.168.1.100",
"user_agent": "Mozilla/5.0...",
"content_type": "application/json"
}
}Extract correlation IDs from context:
import "codechunking/internal/adapter/inbound/api/middleware"
correlationID := middleware.GetCorrelationIDFromContext(ctx)Retrieve middleware performance metrics:
metrics := middleware.GetMiddlewarePerformanceMetrics()
// Returns map with:
// - total_requests
// - total_bytes
// - average_latency_ns
// - errors_count
// - dropped_requests
// - memory_usage_mb
// - last_memory_checkLocation: internal/adapter/inbound/api/security_middleware.go:38
Provides comprehensive input validation to protect against common web attacks.
- SQL Injection Detection: Scans query parameters and JSON fields
- XSS Prevention: Validates request data for malicious scripts
- Header Injection Prevention: Detects CRLF injection in headers
- Path Traversal Protection: Prevents directory traversal attacks
- Payload Size Limits: Enforces 64KB request body limit
- JSON Validation: Ensures well-formed JSON requests
middleware := NewSecurityValidationMiddleware()
handler := middleware(yourHandler)The middleware validates requests in the following order:
- Query Parameters → Checks for SQL injection patterns
- Request Headers → Detects CRLF injection
- Request Body (POST/PUT) → Validates JSON content and size
SQL Injection Detected:
{
"error": "malicious SQL detected",
"code": 400,
"message": "Security validation failed"
}Payload Too Large:
{
"error": "payload too large",
"code": 413,
"message": "Security validation failed"
}Multiple Violations:
{
"error": "multiple security violations",
"code": 400,
"message": "Security validation failed"
}The validation uses internal/application/common.ValidateQueryParameters() which checks for:
- SQL keywords:
SELECT,INSERT,UPDATE,DELETE,DROP,UNION - Comment sequences:
--,/*,*/ - String termination attempts:
'," - Boolean-based injection:
OR 1=1,AND 1=1
Recursively validates all JSON fields using internal/application/common.ValidateJSONField():
// Validates nested JSON structures
{
"user": {
"name": "John", // ✓ Valid
"query": "'; DROP TABLE" // ✗ SQL injection detected
}
}Location: internal/adapter/inbound/api/security_middleware.go:192
Implements per-client IP rate limiting to prevent abuse.
config := RateLimitConfig{
RequestsPerMinute: 60, // Max requests per minute
BurstSize: 10, // Burst allowance
WindowSize: "1m", // Time window
}
middleware := NewRateLimitingMiddleware(config)- Per-Client Tracking: Separate limits for each client IP
- Sliding Window: Uses 1-minute rolling window
- Thread-Safe: Safe for concurrent requests
- Automatic Cleanup: Removes expired request records
When rate limit is exceeded:
{
"error": "rate limit exceeded",
"code": 429,
"message": "Security validation failed"
}HTTP Status: 429 Too Many Requests
Uses internal/adapter/inbound/api/netutil.ClientIP() which checks:
-
X-Forwarded-Forheader (first IP) -
X-Real-IPheader - Remote address from connection
type ClientState struct {
requests []time.Time // Request timestamps
mutex sync.Mutex // Thread-safe access
}Location: internal/adapter/inbound/api/security_middleware.go:244
Ensures only supported content types are processed.
-
JSON-Only Enforcement: Accepts only
application/jsoncontent type - POST/PUT Validation: Only validates requests with bodies
-
Flexible Matching: Handles charset parameters (e.g.,
application/json; charset=utf-8)
middleware := NewContentTypeValidationMiddleware()
handler := middleware(yourHandler)When unsupported content type is detected:
{
"error": "unsupported content type",
"code": 415,
"message": "Security validation failed"
}HTTP Status: 415 Unsupported Media Type
application/jsonapplication/json; charset=utf-8- Any
application/jsonvariant with parameters
The default server uses this middleware stack (applied in reverse):
builder := NewServerBuilder(config).
WithHealthService(healthService).
WithRepositoryService(repoService).
WithErrorHandler(errorHandler).
WithDefaultMiddleware() // Applies standard chainDefault middleware order (outermost to innermost):
- Error Handling → Catches panics
- CORS → Handles cross-origin requests
- Logging → Tracks requests
- Security Headers → Adds security headers
For maximum security, use the comprehensive security stack:
securityStack := CreateSecurityMiddlewareStack()
handler := securityStack(yourHandler)Security stack order (outermost to innermost):
- Security Headers → Adds protection headers
- Rate Limiting → Prevents abuse (10 req/min)
- Content Type Validation → Ensures JSON only
- Security Validation → Validates input
Build custom chains using NewMiddlewareChain:
customChain := NewMiddlewareChain(
NewSecurityMiddleware(),
NewLoggingMiddleware(logger),
NewRateLimitingMiddleware(rateLimitConfig),
)
handler := customChain(yourHandler)Use the builder for fine-grained control:
server, err := NewServerBuilder(config).
WithHealthService(healthService).
WithRepositoryService(repoService).
WithErrorHandler(errorHandler).
WithMiddleware(NewSecurityMiddleware()).
WithMiddleware(NewLoggingMiddleware(logger)).
WithMiddleware(customMiddleware).
Build()Middleware behavior can be influenced by configuration:
| Variable | Default | Description |
|---|---|---|
CODECHUNK_API_HOST |
0.0.0.0 |
API server host |
CODECHUNK_API_PORT |
8080 |
API server port |
CODECHUNK_API_READ_TIMEOUT |
30s |
Request read timeout |
CODECHUNK_API_WRITE_TIMEOUT |
30s |
Response write timeout |
Configure middleware through the builder:
config := &config.Config{
API: config.APIConfig{
Host: "localhost",
Port: "8080",
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second,
},
}
server, _ := NewServerBuilder(config).
WithDefaultMiddleware().
Build()package main
import (
"codechunking/internal/adapter/inbound/api"
"codechunking/internal/config"
"context"
)
func main() {
cfg := config.Load()
server, err := api.NewServer(
cfg,
healthService,
repoService,
errorHandler,
)
if err != nil {
panic(err)
}
ctx := context.Background()
if err := server.Start(ctx); err != nil {
panic(err)
}
}// Custom logging middleware
func customLogger(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("Custom: %s %s", r.Method, r.URL.Path)
next.ServeHTTP(w, r)
})
}
server, err := api.NewServerBuilder(config).
WithHealthService(healthService).
WithRepositoryService(repoService).
WithErrorHandler(errorHandler).
WithMiddleware(api.NewSecurityMiddleware()).
WithMiddleware(customLogger).
WithMiddleware(api.NewCORSMiddleware()).
Build()// Rate limiting configuration
rateLimitConfig := api.RateLimitConfig{
RequestsPerMinute: 100,
BurstSize: 20,
WindowSize: "1m",
}
// Security headers configuration
securityConfig := &api.SecurityHeadersConfig{
ReferrerPolicy: "no-referrer",
CSPPolicy: "comprehensive",
HStsEnabled: boolPtr(true),
HStsMaxAge: 63072000, // 2 years
HStsSubdomains: boolPtr(true),
HStsPreload: boolPtr(true),
}
// Structured logging configuration
loggingConfig := api.LoggingConfig{
LogLevel: "INFO",
EnablePerfMetrics: true,
SlowRequestThreshold: 500 * time.Millisecond,
EnableSecurityLogging: true,
EnableAsyncLogging: true,
EnableObjectPooling: true,
SampleRate: 1.0,
ExcludePaths: []string{"/health"},
}
server, err := api.NewServerBuilder(config).
WithHealthService(healthService).
WithRepositoryService(repoService).
WithSearchService(searchService).
WithErrorHandler(errorHandler).
WithMiddleware(api.NewUnifiedSecurityMiddleware(securityConfig)).
WithMiddleware(api.NewRateLimitingMiddleware(rateLimitConfig)).
WithMiddleware(api.NewContentTypeValidationMiddleware()).
WithMiddleware(api.NewSecurityValidationMiddleware()).
WithMiddleware(api.NewStructuredLoggingMiddleware(loggingConfig)).
WithMiddleware(api.NewCORSMiddleware()).
WithMiddleware(api.NewErrorHandlingMiddleware()).
Build()func TestMyHandler(t *testing.T) {
var logOutput strings.Builder
logger := api.NewTestLogger(&logOutput)
middleware := api.NewLoggingMiddleware(logger)
handler := middleware(myHandler)
req := httptest.NewRequest("GET", "/test", nil)
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
assert.Contains(t, logOutput.String(), "GET /test")
}Always apply middleware in the correct order:
// ✓ Correct: Error handling catches panics from all middleware
WithMiddleware(NewErrorHandlingMiddleware()).
WithMiddleware(NewCORSMiddleware()).
WithMiddleware(NewLoggingMiddleware(logger)).
WithMiddleware(NewSecurityMiddleware())
// ✗ Incorrect: Panics in logging won't be caught
WithMiddleware(NewLoggingMiddleware(logger)).
WithMiddleware(NewErrorHandlingMiddleware())Always propagate request IDs to downstream services:
func myHandler(w http.ResponseWriter, r *http.Request) {
requestID := api.GetRequestID(r.Context())
// Pass to downstream service
downstreamReq, _ := http.NewRequest("POST", "http://service/api", body)
downstreamReq.Header.Set("X-Request-ID", requestID)
}Configure sensitive headers to avoid logging credentials:
config := LoggingConfig{
SensitiveHeaders: []string{
"Authorization",
"X-API-Key",
"Cookie",
"X-Auth-Token",
},
}Always use the full security stack in production:
// Production
handler := CreateSecurityMiddlewareStack()(yourHandler)
// Development (optional - can skip some validation)
handler := NewLoggingMiddleware(logger)(yourHandler)Adjust rate limits based on your traffic patterns:
// Public API - restrictive
RateLimitConfig{
RequestsPerMinute: 60,
BurstSize: 10,
}
// Internal API - permissive
RateLimitConfig{
RequestsPerMinute: 1000,
BurstSize: 100,
}Development:
config := CORSConfig{
AllowedOrigins: []string{"*"},
}Production:
config := CORSConfig{
AllowedOrigins: []string{
"https://app.example.com",
"https://admin.example.com",
},
AllowedMethods: []string{"GET", "POST"},
AllowedHeaders: []string{"Content-Type", "Authorization"},
}Let the error handling middleware manage panics:
// ✓ Correct: Let middleware handle panic
func handler(w http.ResponseWriter, r *http.Request) {
result := somethingThatMightPanic()
json.NewEncoder(w).Encode(result)
}
// ✗ Incorrect: Don't add redundant panic recovery
func handler(w http.ResponseWriter, r *http.Request) {
defer func() {
if r := recover(); r != nil {
// Middleware already handles this
}
}()
}Test middleware in isolation before integration:
func TestMyMiddleware(t *testing.T) {
nextCalled := false
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
nextCalled = true
w.WriteHeader(http.StatusOK)
})
middleware := MyMiddleware()
handler := middleware(next)
req := httptest.NewRequest("GET", "/test", nil)
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
assert.True(t, nextCalled)
assert.Equal(t, http.StatusOK, rec.Code)
}Use async logging and object pooling in high-traffic scenarios:
config := LoggingConfig{
EnableAsyncLogging: true,
EnableObjectPooling: true,
SampleRate: 0.1, // Log 10% in very high traffic
}Only enable HSTS when serving over HTTPS:
config := &SecurityHeadersConfig{
HStsEnabled: boolPtr(true), // Only works with TLS
HStsMaxAge: 31536000,
HStsSubdomains: boolPtr(true),
}The middleware system is designed for efficiency:
- Field Chaining: Logging middleware uses field chains to avoid premature map copying
- Object Pooling: Structured logging middleware can reuse log entry objects
- Response Buffering: Only buffers response body when needed
Typical middleware overhead per request:
| Middleware | Overhead |
|---|---|
| Logging | < 100μs |
| Security Headers | < 10μs |
| CORS | < 10μs |
| Error Handling | < 5μs |
| Security Validation | 100-500μs (depends on payload) |
| Rate Limiting | 10-50μs |
Total typical overhead: < 1ms per request
For systems handling > 10,000 req/s:
config := LoggingConfig{
EnableAsyncLogging: true, // Process logs in background
EnableObjectPooling: true, // Reuse log objects
SampleRate: 0.1, // Log only 10% of requests
ExcludePaths: []string{"/health", "/metrics"},
}Problem: Request IDs not appearing in logs
Solution: Ensure logging middleware is applied:
WithMiddleware(NewLoggingMiddleware(logger))Problem: Browser shows CORS errors
Solution: Verify CORS middleware is applied and configured:
config := CORSConfig{
AllowedOrigins: []string{"https://your-frontend.com"},
AllowedMethods: []string{"GET", "POST", "OPTIONS"},
}
WithMiddleware(NewCORSMiddlewareWithConfig(config))Problem: Legitimate clients being rate limited
Solution: Adjust rate limit configuration:
config := RateLimitConfig{
RequestsPerMinute: 120, // Increase limit
BurstSize: 30, // Increase burst
}Problem: Middleware consuming too much memory
Solution: Enable object pooling and sampling:
config := LoggingConfig{
EnableObjectPooling: true,
SampleRate: 0.5, // Log 50% of requests
}Problem: Security headers missing from responses
Solution: Ensure security middleware is applied:
WithMiddleware(NewSecurityMiddleware())If you're using the old middleware pattern:
Before:
mux := http.NewServeMux()
handler := loggingMiddleware(corsMiddleware(mux))After:
server, _ := api.NewServerBuilder(config).
WithHealthService(healthService).
WithRepositoryService(repoService).
WithErrorHandler(errorHandler).
WithDefaultMiddleware().
Build()Before:
handler := customMiddleware(defaultHandler)After:
server, _ := api.NewServerBuilder(config).
WithDefaultMiddleware().
WithMiddleware(customMiddleware).
Build()-
Source Code:
-
internal/adapter/inbound/api/middleware.go- Core middleware -
internal/adapter/inbound/api/security_middleware.go- Security middleware -
internal/adapter/inbound/api/middleware/logging_middleware.go- Advanced logging
-
-
Tests:
internal/adapter/inbound/api/middleware_test.gointernal/adapter/inbound/api/security_middleware_test.gointernal/adapter/inbound/api/security_integration_test.go
Configuration
- [📖 Configuration Reference](configuration reference) - Complete reference guide
- Configuration
- API Configuration
- Database Configuration
- Gemini Configuration
- Git Configuration
- Logging Configuration
- Middleware Configuration
- NATS Configuration
- Worker Configuration