Files
cv-site/doc/_go-learning/diagrams/02-request-flow.md
T
juanatsap d95c62bad4 refactor: remove outdated server design documentation
Remove 557-line server-design.md from _go-learning/architecture - content is now covered in updated architecture documentation with real implementation examples and test coverage.
2025-12-02 20:25:05 +00:00

32 KiB

Request Flow Diagram

Complete HTTP Request Lifecycle

┌─────────────────────────────────────────────────────────────────┐
│                    Full Request Lifecycle                        │
└─────────────────────────────────────────────────────────────────┘

Client Browser
     │
     ├─→ User visits /?lang=es&cv-length=long
     │
     ▼
┌─────────────────────────────────────────────────────────────┐
│  HTTP Request                                                │
│  ┌───────────────────────────────────────────────────────┐  │
│  │  GET /?lang=es&cv-length=long HTTP/1.1               │  │
│  │  Host: localhost:8080                                 │  │
│  │  Cookie: cv-length=short; cv-icons=show              │  │
│  │  Accept: text/html                                    │  │
│  └───────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘
     │
     ▼
┌─────────────────────────────────────────────────────────────┐
│  Go HTTP Server (net/http)                                   │
│  ├─ Port :8080                                              │
│  ├─ ServeMux Router                                         │
│  └─ Match route pattern                                     │
└─────────────────────────────────────────────────────────────┘
     │
     ▼
┌─────────────────────────────────────────────────────────────┐
│  MIDDLEWARE CHAIN (4 layers)                                 │
│                                                              │
│  1. Recovery Middleware                                      │
│     └─→ Wraps entire request in defer/recover               │
│                                                              │
│  2. Logger Middleware                                        │
│     └─→ Logs: [GET] / 127.0.0.1                            │
│                                                              │
│  3. SecurityHeaders Middleware                               │
│     └─→ Sets: CSP, X-Frame-Options, etc.                   │
│                                                              │
│  4. PreferencesMiddleware                                    │
│     ├─→ Reads cookies                                       │
│     ├─→ Migrates old values                                 │
│     └─→ Stores in request context                          │
└─────────────────────────────────────────────────────────────┘
     │
     ▼
┌─────────────────────────────────────────────────────────────┐
│  ROUTER (ServeMux)                                           │
│  ├─ Pattern: /                                              │
│  ├─ Match: Home handler                                     │
│  └─ Call: handler.Home(w, r)                               │
└─────────────────────────────────────────────────────────────┘
     │
     ▼
┌─────────────────────────────────────────────────────────────┐
│  HANDLER: CVHandler.Home()                                   │
│  (internal/handlers/cv_pages.go)                            │
│                                                              │
│  Step 1: Get preferences from context                       │
│  ├─→ prefs := middleware.GetPreferences(r)                 │
│  └─→ Result: CVLength="long", CVLanguage="es"              │
│                                                              │
│  Step 2: Validate language from query params                │
│  ├─→ lang := r.URL.Query().Get("lang")                     │
│  ├─→ Fallback to: prefs.CVLanguage if empty               │
│  └─→ Validate: must be "en" or "es"                        │
│                                                              │
│  Step 3: Prepare template data                              │
│  ├─→ Call: h.prepareTemplateData(lang)                    │
│  └─→ Returns: map with CV, UI, preferences                 │
│                                                              │
│  Step 4: Render template                                     │
│  ├─→ Call: h.tmpl.Render(w, "index.html", data)           │
│  └─→ Returns: HTML response                                │
└─────────────────────────────────────────────────────────────┘
     │
     ▼
┌─────────────────────────────────────────────────────────────┐
│  TEMPLATE PREPARATION                                        │
│  (prepareTemplateData helper)                               │
│                                                              │
│  1. Load CV data                                             │
│  ├─→ cv, err := cvmodel.LoadCV(lang)                       │
│  └─→ Read: data/cv-es.json                                 │
│                                                              │
│  2. Load UI strings                                          │
│  ├─→ ui, err := uimodel.LoadUI(lang)                       │
│  └─→ Read: data/ui-es.json                                 │
│                                                              │
│  3. Calculate experience durations                           │
│  └─→ For each experience: years/months                      │
│                                                              │
│  4. Split skills into columns                                │
│  └─→ Distribute skills evenly across columns               │
│                                                              │
│  5. Build data map                                           │
│  └─→ Return: CV, UI, preferences, SEO metadata             │
└─────────────────────────────────────────────────────────────┘
     │
     ▼
┌─────────────────────────────────────────────────────────────┐
│  TEMPLATE RENDERING                                          │
│  (internal/templates/manager.go)                            │
│                                                              │
│  1. Get cached template                                      │
│  ├─→ tmpl := m.templates["index.html"]                     │
│  └─→ (or reload if hot reload enabled)                     │
│                                                              │
│  2. Execute template                                         │
│  ├─→ tmpl.Execute(w, data)                                 │
│  ├─→ Process: {{.CV.Name}}, {{range .CV.Experience}}      │
│  └─→ Include partials: header, footer, sections           │
│                                                              │
│  3. Generate HTML                                            │
│  └─→ Full HTML page with data injected                     │
└─────────────────────────────────────────────────────────────┘
     │
     ▼
┌─────────────────────────────────────────────────────────────┐
│  RESPONSE GENERATION                                         │
│                                                              │
│  Headers:                                                    │
│  ├─ Content-Type: text/html; charset=utf-8                 │
│  ├─ Content-Security-Policy: [CSP rules]                   │
│  ├─ X-Frame-Options: DENY                                  │
│  └─ Set-Cookie: cv-language=es; Path=/; Max-Age=...       │
│                                                              │
│  Body:                                                       │
│  └─ <!DOCTYPE html>                                         │
│     <html lang="es">                                        │
│       <head>...</head>                                      │
│       <body>                                                │
│         <!-- Full CV content -->                            │
│       </body>                                               │
│     </html>                                                 │
└─────────────────────────────────────────────────────────────┘
     │
     ▼
┌─────────────────────────────────────────────────────────────┐
│  LOGGER MIDDLEWARE (completion)                              │
│  └─→ Log: Completed in 45ms (status: 200)                  │
└─────────────────────────────────────────────────────────────┘
     │
     ▼
Client Browser receives HTML

HTMX Toggle Request Flow

┌─────────────────────────────────────────────────────────────┐
│              HTMX Toggle Request (Partial Update)            │
└─────────────────────────────────────────────────────────────┘

User clicks toggle button
     │
     ▼
┌─────────────────────────────────────────────────────────────┐
│  HTMX Request                                                │
│  ┌───────────────────────────────────────────────────────┐  │
│  │  GET /toggle/length?current=short HTTP/1.1           │  │
│  │  HX-Request: true                                     │  │
│  │  HX-Trigger: toggle-length-btn                       │  │
│  │  HX-Target: #main-content                            │  │
│  └───────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘
     │
     ▼
Middleware Chain (same as above)
     │
     ▼
┌─────────────────────────────────────────────────────────────┐
│  ROUTER                                                      │
│  ├─ Pattern: /toggle/length                                 │
│  └─ Handler: CVHandler.ToggleCVLength(w, r)                │
└─────────────────────────────────────────────────────────────┘
     │
     ▼
┌─────────────────────────────────────────────────────────────┐
│  HANDLER: CVHandler.ToggleCVLength()                         │
│  (internal/handlers/cv_htmx.go)                             │
│                                                              │
│  1. Get current preferences                                  │
│  └─→ prefs := middleware.GetPreferences(r)                 │
│                                                              │
│  2. Toggle state                                             │
│  ├─→ currentLength := prefs.CVLength                       │
│  └─→ newLength := "long" if current == "short"            │
│                                                              │
│  3. Save new preference                                      │
│  └─→ middleware.SetPreferenceCookie(w, "cv-length", newLength) │
│                                                              │
│  4. Get language and prepare data                            │
│  └─→ data := h.prepareTemplateData(lang)                  │
│                                                              │
│  5. Render partial template                                  │
│  └─→ h.tmpl.Render(w, "partials/cv_content.html", data)   │
└─────────────────────────────────────────────────────────────┘
     │
     ▼
┌─────────────────────────────────────────────────────────────┐
│  PARTIAL TEMPLATE RENDERING                                  │
│  └─ Only renders: partials/cv_content.html                 │
│     (Not full page, just the content section)               │
└─────────────────────────────────────────────────────────────┘
     │
     ▼
┌─────────────────────────────────────────────────────────────┐
│  HTMX Response                                               │
│  ┌───────────────────────────────────────────────────────┐  │
│  │  HTTP/1.1 200 OK                                      │  │
│  │  Content-Type: text/html                              │  │
│  │  Set-Cookie: cv-length=long; Path=/; Max-Age=...     │  │
│  │                                                        │  │
│  │  <div id="main-content">                              │  │
│  │    <!-- Updated CV content with long format -->       │  │
│  │  </div>                                               │  │
│  └───────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘
     │
     ▼
HTMX swaps content in #main-content
(No page reload, instant update)

PDF Export Request Flow

┌─────────────────────────────────────────────────────────────┐
│                 PDF Export Request Flow                      │
└─────────────────────────────────────────────────────────────┘

User clicks "Export PDF"
     │
     ▼
┌─────────────────────────────────────────────────────────────┐
│  HTTP POST Request                                           │
│  ┌───────────────────────────────────────────────────────┐  │
│  │  POST /export/pdf HTTP/1.1                            │  │
│  │  Content-Type: application/json                       │  │
│  │  Origin: http://localhost:8080                        │  │
│  │                                                        │  │
│  │  {                                                     │  │
│  │    "lang": "es",                                      │  │
│  │    "length": "long",                                  │  │
│  │    "icons": "show",                                   │  │
│  │    "version": "with_skills"                           │  │
│  │  }                                                     │  │
│  └───────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘
     │
     ▼
Global Middleware Chain
     │
     ▼
┌─────────────────────────────────────────────────────────────┐
│  ROUTE-SPECIFIC MIDDLEWARE                                   │
│                                                              │
│  1. OriginChecker                                            │
│  └─→ Verify same-origin request                            │
│                                                              │
│  2. RateLimiter                                              │
│  └─→ Check: 3 requests/min per IP                          │
└─────────────────────────────────────────────────────────────┘
     │
     ▼
┌─────────────────────────────────────────────────────────────┐
│  HANDLER: CVHandler.ExportPDF()                              │
│  (internal/handlers/cv_pdf.go)                              │
│                                                              │
│  1. Parse and validate request                               │
│  ├─→ var req PDFExportRequest                              │
│  ├─→ json.NewDecoder(r.Body).Decode(&req)                  │
│  └─→ Validate: lang, length, icons, version                │
│                                                              │
│  2. Render HTML for PDF                                      │
│  ├─→ Build data map with preferences                        │
│  ├─→ Render to buffer: index.html template                 │
│  └─→ Result: Full HTML page in memory                      │
│                                                              │
│  3. Generate PDF                                             │
│  ├─→ Call: pdf.GeneratePDF(htmlContent, pdfOptions)       │
│  └─→ Uses: chromedp to render HTML → PDF                  │
│                                                              │
│  4. Send PDF response                                        │
│  ├─→ Set headers: application/pdf                          │
│  ├─→ Set filename: CV-[Name]-[lang].pdf                    │
│  └─→ Write: PDF bytes to response                          │
└─────────────────────────────────────────────────────────────┘
     │
     ▼
┌─────────────────────────────────────────────────────────────┐
│  PDF GENERATION (chromedp)                                   │
│  (internal/pdf/generator.go)                                │
│                                                              │
│  1. Launch headless Chrome                                   │
│  └─→ chromedp.NewContext()                                 │
│                                                              │
│  2. Navigate to data URL                                     │
│  └─→ Load HTML content                                     │
│                                                              │
│  3. Wait for rendering                                       │
│  └─→ Ensure fonts, images loaded                           │
│                                                              │
│  4. Generate PDF                                             │
│  ├─→ chromedp.PrintToPDF() with options                    │
│  ├─→ A4 size, margins, print background                    │
│  └─→ Return: PDF bytes                                     │
└─────────────────────────────────────────────────────────────┘
     │
     ▼
┌─────────────────────────────────────────────────────────────┐
│  PDF Response                                                │
│  ┌───────────────────────────────────────────────────────┐  │
│  │  HTTP/1.1 200 OK                                      │  │
│  │  Content-Type: application/pdf                        │  │
│  │  Content-Disposition: attachment; filename="CV-..."   │  │
│  │  Content-Length: 245678                               │  │
│  │                                                        │  │
│  │  [PDF binary data]                                    │  │
│  └───────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘
     │
     ▼
Browser triggers download

Error Handling Flow

┌─────────────────────────────────────────────────────────────┐
│                    Error Handling Flow                       │
└─────────────────────────────────────────────────────────────┘

Request with invalid language
     │
     ▼
Handler validation detects error
     │
     ├─→ Create: InvalidLanguageError("xx")
     │
     ▼
┌─────────────────────────────────────────────────────────────┐
│  DomainError Created                                         │
│  ├─ Code: INVALID_LANGUAGE                                  │
│  ├─ Message: "Unsupported language: xx (use 'en' or 'es')" │
│  ├─ StatusCode: 400                                         │
│  └─ Field: "lang"                                           │
└─────────────────────────────────────────────────────────────┘
     │
     ▼
┌─────────────────────────────────────────────────────────────┐
│  Handler.HandleError(w, r, err)                              │
│  (internal/handlers/errors.go)                              │
│                                                              │
│  1. Check if DomainError                                     │
│  └─→ Extract: code, message, status, field                 │
│                                                              │
│  2. Log error                                                │
│  └─→ log.Printf("[ERROR] %s: %s", code, message)          │
│                                                              │
│  3. Build error response                                     │
│  ├─→ Create: ErrorInfo struct                              │
│  └─→ Create: APIResponse wrapper                           │
│                                                              │
│  4. Send error response                                      │
│  ├─→ Set status: 400 Bad Request                           │
│  ├─→ Set content-type: application/json                    │
│  └─→ Write: JSON error response                            │
└─────────────────────────────────────────────────────────────┘
     │
     ▼
┌─────────────────────────────────────────────────────────────┐
│  Error Response                                              │
│  {                                                           │
│    "success": false,                                        │
│    "error": {                                               │
│      "code": "INVALID_LANGUAGE",                           │
│      "message": "Unsupported language: xx",                │
│      "field": "lang"                                        │
│    }                                                         │
│  }                                                           │
└─────────────────────────────────────────────────────────────┘
     │
     ▼
Client receives error

Performance Metrics

Typical Request Timings:

┌─────────────────────────────────────────────────────┐
│  Component                      Time        %        │
├─────────────────────────────────────────────────────┤
│  Middleware overhead           ~350 μs     0.7%     │
│  ├─ Recovery                   ~10 ns               │
│  ├─ Logger                     ~100 μs              │
│  ├─ SecurityHeaders            ~50 ns               │
│  └─ Preferences                ~200 μs              │
│                                                      │
│  Handler processing            ~500 μs     1.0%     │
│  ├─ Get preferences            ~10 μs               │
│  ├─ Validate input             ~50 μs               │
│  └─ Prepare data               ~440 μs              │
│                                                      │
│  Data loading                  ~2 ms       4.0%     │
│  ├─ Load CV JSON               ~1 ms                │
│  └─ Load UI JSON               ~1 ms                │
│                                                      │
│  Template rendering            ~45 ms      90%      │
│  ├─ Template execution         ~40 ms               │
│  └─ HTML generation            ~5 ms                │
│                                                      │
│  Response transmission         ~2 ms       4.0%     │
├─────────────────────────────────────────────────────┤
│  TOTAL REQUEST TIME            ~50 ms      100%     │
└─────────────────────────────────────────────────────┘

PDF Export Timings:

┌─────────────────────────────────────────────────────┐
│  Component                      Time        %        │
├─────────────────────────────────────────────────────┤
│  Middleware + Handler          ~1 ms       0.1%     │
│  Template rendering            ~50 ms      5%       │
│  Chrome launch/navigation      ~200 ms     20%      │
│  PDF generation                ~700 ms     70%      │
│  Response transmission         ~50 ms      5%       │
├─────────────────────────────────────────────────────┤
│  TOTAL PDF EXPORT TIME         ~1 sec      100%     │
└─────────────────────────────────────────────────────┘