Files
cv-site/doc/_go-learning/diagrams/03-middleware-chain.md
T

316 lines
18 KiB
Markdown
Raw Normal View History

# Middleware Chain Diagram
## Middleware Execution Order
```
HTTP Request
┌────────────────────────────────────────────────────────────┐
│ MIDDLEWARE CHAIN │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 1. Recovery Middleware │ │
│ │ - Catches panics │ │
│ │ - Logs stack trace │ │
│ │ - Returns 500 error │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 2. Logger Middleware │ │
│ │ - Logs request method, path, IP │ │
│ │ - Measures request duration │ │
│ │ - Logs response status │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 3. SecurityHeaders Middleware │ │
│ │ - Sets CSP header │ │
│ │ - Sets X-Frame-Options │ │
│ │ - Sets X-Content-Type-Options │ │
│ │ - Sets Referrer-Policy │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 4. PreferencesMiddleware │ │
│ │ - Reads preference cookies │ │
│ │ - Migrates old values │ │
│ │ - Stores in request context │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
└───────────────────────────┼────────────────────────────────┘
┌───────────────┐
│ Router │
│ (ServeMux) │
└───────────────┘
┌───────────────┐
│ Handler │
└───────────────┘
```
## Detailed Flow
```
┌─────────────────────────────────────────────────────────────────┐
│ Request Processing Flow │
└─────────────────────────────────────────────────────────────────┘
Client Request: GET /?lang=es
╔═══════════════════════════════════════════════════════════╗
║ RECOVERY MIDDLEWARE ║
╠═══════════════════════════════════════════════════════════╣
║ defer func() { ║
║ if err := recover(); err != nil { ║
║ log error + stack trace ║
║ http.Error(w, "Internal Server Error", 500) ║
║ } ║
║ }() ║
║ ║
║ next.ServeHTTP(w, r) ────────────────┐ ║
╚════════════════════════════════════════│══════════════════╝
╔═══════════════════════════════════════════════════════════╗
║ LOGGER MIDDLEWARE ║
╠═══════════════════════════════════════════════════════════╣
║ start := time.Now() ║
║ log.Printf("[%s] %s %s", r.Method, r.URL.Path, r.RemoteAddr) ║
║ ║
║ wrapped := responseWriter wrapper ║
║ next.ServeHTTP(wrapped, r) ──────────┐ ║
║ │ ║
║ duration := time.Since(start) │ ║
║ log.Printf("Completed in %v (status: %d)", duration, status) ║
╚═════════════════════════════════════════│════════════════╝
╔═══════════════════════════════════════════════════════════╗
║ SECURITY HEADERS MIDDLEWARE ║
╠═══════════════════════════════════════════════════════════╣
║ w.Header().Set("Content-Security-Policy", CSP_POLICY) ║
║ w.Header().Set("X-Frame-Options", "DENY") ║
║ w.Header().Set("X-Content-Type-Options", "nosniff") ║
║ w.Header().Set("Referrer-Policy", "strict-origin") ║
║ ║
║ next.ServeHTTP(w, r) ────────────────┐ ║
╚════════════════════════════════════════│══════════════════╝
╔═══════════════════════════════════════════════════════════╗
║ PREFERENCES MIDDLEWARE ║
╠═══════════════════════════════════════════════════════════╣
║ // Read cookies ║
║ prefs := &Preferences{ ║
║ CVLength: getCookie(r, "cv-length", "short"), ║
║ CVIcons: getCookie(r, "cv-icons", "show"), ║
║ CVLanguage: getCookie(r, "cv-language", "en"), ║
║ CVTheme: getCookie(r, "cv-theme", "default"), ║
║ ColorTheme: getCookie(r, "color-theme", "light"), ║
║ } ║
║ ║
║ // Migrate old values ║
║ if prefs.CVLength == "extended" { ║
║ prefs.CVLength = "long" ║
║ } ║
║ ║
║ // Store in context ║
║ ctx := context.WithValue(r.Context(), PreferencesKey, prefs) ║
║ next.ServeHTTP(w, r.WithContext(ctx)) ───┐ ║
╚═════════════════════════════════════════════│════════════╝
┌──────────────────┐
│ ROUTER HANDLER │
│ │
│ Matches route │
│ Calls handler │
└──────────────────┘
┌──────────────────┐
│ HANDLER FUNC │
│ │
│ Processes req │
│ Returns resp │
└──────────────────┘
```
## Route-Specific Middleware
```
┌────────────────────────────────────────────────────────────────┐
│ Route-Specific Middleware Example │
│ (PDF Export Endpoint) │
└────────────────────────────────────────────────────────────────┘
Global Middleware Chain (all routes)
├─ Recovery
├─ Logger
├─ SecurityHeaders
└─ PreferencesMiddleware
┌─────────────────────────────────────────┐
│ Router (ServeMux) │
│ │
│ /export/pdf → pdfHandler (protected) │
│ │ │
│ ▼ │
│ ┌───────────────────────────┐ │
│ │ Route-Specific Chain │ │
│ │ │ │
│ │ 1. OriginChecker │ │
│ │ └─ Verify same origin│ │
│ │ │ │
│ │ 2. RateLimiter │ │
│ │ └─ 3 req/min per IP │ │
│ │ │ │
│ │ 3. ExportPDF Handler │ │
│ │ └─ Generate PDF │ │
│ └───────────────────────────┘ │
└─────────────────────────────────────────┘
```
## Middleware Wrapping Pattern
```go
// Middleware function signature
type Middleware func(http.Handler) http.Handler
// Wrapping example
handler := routes.Setup(cvHandler, healthHandler)
// Returns:
// Recovery(
// Logger(
// SecurityHeaders(
// PreferencesMiddleware(mux)
// )
// )
// )
// Execution flow (unwraps from outside to inside):
Request
enters Recovery
enters Logger
enters SecurityHeaders
enters PreferencesMiddleware
enters mux/handler
handler processes
exits PreferencesMiddleware
exits SecurityHeaders
exits Logger (logs duration)
exits Recovery
Response
```
## Context Flow
```
┌─────────────────────────────────────────────────────────────┐
│ Context Values Through Middleware │
└─────────────────────────────────────────────────────────────┘
Initial Request Context
├─ Empty context.Background()
PreferencesMiddleware
├─ Reads cookies
├─ Creates Preferences struct
└─ Adds to context
└─→ ctx = context.WithValue(r.Context(), PreferencesKey, prefs)
┌──────────────────────────────────────┐
│ Modified Request Context │
│ │
│ PreferencesKey → &Preferences{ │
│ CVLength: "long", │
│ CVIcons: "show", │
│ CVLanguage: "es", │
│ CVTheme: "default", │
│ ColorTheme: "light", │
│ } │
└──────────────────────────────────────┘
Handler receives request with enriched context
├─→ prefs := middleware.GetPreferences(r)
│ // Retrieves from context
└─→ lang := middleware.GetLanguage(r)
// Helper that calls GetPreferences
```
## Error Handling in Middleware
```
┌────────────────────────────────────────────────────────────┐
│ Error Handling Flow │
└────────────────────────────────────────────────────────────┘
Recovery Middleware
│ Normal Flow:
│ ┌─────────────────────────────────────┐
│ │ next.ServeHTTP(w, r) │
│ │ ↓ │
│ │ Handler processes successfully │
│ │ ↓ │
│ │ Returns response │
│ └─────────────────────────────────────┘
│ Panic Flow:
│ ┌─────────────────────────────────────┐
│ │ next.ServeHTTP(w, r) │
│ │ ↓ │
│ │ Handler panics! │
│ │ ↓ │
│ │ defer recover() catches it │
│ │ ↓ │
│ │ log.Printf("PANIC: %v\\n%s", │
│ │ err, debug.Stack()) │
│ │ ↓ │
│ │ http.Error(w, "Internal Error", 500)│
│ └─────────────────────────────────────┘
Response to client
```
## Performance Characteristics
```
Middleware Performance Impact (per request):
Recovery: ~10 ns (defer overhead)
Logger: ~100 μs (time measurements, string formatting)
SecurityHeaders: ~50 ns (header setting)
Preferences: ~200 μs (cookie parsing, context creation)
Total overhead: ~350 μs per request
Handler time: ~1-5 ms (template rendering)
Total request: ~1.5-5.5 ms
```
## Related Diagrams
- [System Architecture](./01-system-architecture.md) - Overall system design
- [Request Flow](./02-request-flow.md) - Complete HTTP request lifecycle
- [Error Handling](./06-error-handling-flow.md) - Error propagation