zoobzio December 15, 2025 Edit this page

Architecture

Understanding rocco's internal architecture helps you make better design decisions and debug issues effectively.

Request Flow

When a request arrives, it flows through several stages:

Request
   │
   ▼
┌──────────────────────────────────────────────────────────┐
│                  stdlib ServeMux                          │
│  ┌─────────────┐                                         │
│  │   Route     │  Match path and method to handler       │
│  │   Matching  │                                         │
│  └──────┬──────┘                                         │
└─────────│────────────────────────────────────────────────┘
          │
          ▼
┌──────────────────────────────────────────────────────────┐
│                 Middleware Chain                          │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐      │
│  │   Engine    │→ │   Handler   │→ │    Auth     │      │
│  │ Middleware  │  │ Middleware  │  │ Middleware  │      │
│  └─────────────┘  └─────────────┘  └─────────────┘      │
└─────────────────────────┬────────────────────────────────┘
                          │
                          ▼
┌──────────────────────────────────────────────────────────┐
│                  Handler Adapter                          │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐      │
│  │  Extract    │→ │   Parse     │→ │  Validate   │      │
│  │  Params     │  │   Body      │  │   Input     │      │
│  └─────────────┘  └─────────────┘  └─────────────┘      │
│                          │                               │
│                          ▼                               │
│                 ┌─────────────┐                          │
│                 │   Execute   │  Call handler function   │
│                 │   Handler   │                          │
│                 └──────┬──────┘                          │
│                        │                                 │
│            ┌───────────┴───────────┐                    │
│            ▼                       ▼                     │
│  ┌─────────────────┐    ┌─────────────────┐            │
│  │ Success Path    │    │  Error Path     │            │
│  │ Marshal output  │    │ Map to response │            │
│  │ Write response  │    │ Write error     │            │
│  └─────────────────┘    └─────────────────┘            │
└──────────────────────────────────────────────────────────┘
                          │
                          ▼
                      Response

Component Overview

Stdlib Router

Rocco is built on Go 1.22+ stdlib http.ServeMux with native path parameter support. The router handles:

  • URL routing with path parameters (/users/{id})
  • HTTP method routing (GET /users/{id})
  • Context propagation

Access the ServeMux directly for advanced use cases:

engine.Router().HandleFunc("GET /custom", customHandler)

Engine

The Engine orchestrates the server lifecycle:

type Engine struct {
    config           *EngineConfig
    server           *http.Server
    mux              *http.ServeMux
    globalMiddleware []func(http.Handler) http.Handler
    handlers         []Endpoint
    extractIdentity  func(context.Context, *http.Request) (Identity, error)
    spec             *EngineSpec
}

Key responsibilities:

  • Configure HTTP server timeouts
  • Manage global middleware
  • Register handlers with the router
  • Build authentication/authorization middleware
  • Generate OpenAPI specifications
  • Handle graceful shutdown

Handler Adapter

The handler adapter bridges typed handlers to stdlib's http.Handler interface:

func (e *Engine) adaptHandler(handler Endpoint) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        // Emit request received event
        // Call handler.Process()
        // Emit completion/failure event
    }
}

Handler Processing

The Handler.Process() method executes the request lifecycle:

  1. Extract Parameters - Read path/query params from request
  2. Read Body - Read request body with size limits
  3. Parse JSON - Unmarshal body into typed struct
  4. Validate Input - Run struct validation
  5. Execute Handler - Call user function
  6. Handle Errors - Map errors to responses
  7. Validate Output - Optional output validation
  8. Write Response - Marshal and write JSON

Middleware Execution

Middleware executes in a specific order:

Request  →  Engine MW 1  →  Engine MW 2  →  Handler MW  →  Auth MW  →  Handler
Response ←  Engine MW 1  ←  Engine MW 2  ←  Handler MW  ←  Auth MW  ←  Handler

Engine Middleware

Applied to all requests:

engine.WithMiddleware(middleware.Logger)
engine.WithMiddleware(middleware.Recoverer)

Handler Middleware

Applied to specific handlers:

handler.WithMiddleware(rateLimiter)

Auth Middleware

Automatically added when handler requires authentication:

handler.WithAuthentication()  // Adds auth middleware
handler.WithScopes("read")    // Adds auth + authz middleware
handler.WithRoles("admin")    // Adds auth + authz middleware

Error Handling Flow

Handler returns error
         │
         ▼
    ┌────────────┐
    │ Is rocco   │──No──→ Log error, return 500
    │  Error?    │
    └─────┬──────┘
          │Yes
          ▼
    ┌────────────┐
    │ Is error   │──No──→ Log warning, return 500
    │ declared?  │        (undeclared sentinel)
    └─────┬──────┘
          │Yes
          ▼
    Return structured
    error response

Undeclared sentinel errors indicate a programming error - the handler returned an error it didn't declare. This is logged as a warning and returns 500 to avoid leaking internal details.

Event Emission

Rocco emits events throughout the request lifecycle via capitan:

StageEvents
Server lifecycleEngineCreated, EngineStarting, EngineShutdownStarted, EngineShutdownComplete
Handler registrationHandlerRegistered
Request startRequestReceived
Handler executionHandlerExecuting, HandlerSuccess, HandlerError, HandlerSentinelError
Request endRequestCompleted, RequestFailed
ErrorsRequestParamsInvalid, RequestBodyParseError, RequestValidationInputFailed

OpenAPI Generation

OpenAPI specs are generated from handler metadata:

Handler Registration
        │
        ▼
┌───────────────────────────────────────┐
│         Metadata Collection           │
│  - Input/Output type metadata         │
│  - Path/Query parameters              │
│  - Error definitions                  │
│  - Tags, summary, description         │
└───────────────────┬───────────────────┘
                    │
                    ▼
┌───────────────────────────────────────┐
│          Schema Generation            │
│  - Struct → JSON Schema               │
│  - Validation tags → constraints      │
│  - Error details → schemas            │
└───────────────────┬───────────────────┘
                    │
                    ▼
┌───────────────────────────────────────┐
│           Spec Assembly               │
│  - Paths from handlers                │
│  - Components from types              │
│  - Info from EngineSpec               │
└───────────────────────────────────────┘

Type metadata is extracted using sentinel, which scans struct types at handler creation time.

Threading Model

Handler Registration: All handlers must be registered before calling Start(). Registration is not thread-safe.

Request Handling: Each request is handled in its own goroutine. Handlers may run concurrently.

Shutdown: Graceful shutdown waits for active requests to complete within the context deadline.

Memory and Performance

  • Handler creation: Type metadata is scanned once at handler creation
  • OpenAPI spec: Generated once on first request, cached thereafter
  • Body parsing: Uses io.ReadAll with configurable size limits
  • Validation: Validator instance created per handler (thread-safe)

Next Steps

See Also