Custom Middleware Development
Middleware in csilk is a function with signature void handler(csilk_ctx_t* c). Middleware can intercept requests before and after the actual business handler executes.
Middleware Pattern
flowchart TB
START["Request enters middleware"]
PRE["Pre-processing logic\n(e.g., validate token, check rate limit)"]
DEC{"Condition met?"}
NEXT["csilk_next(ctx)\n→ Delegate to next handler"]
POST["Post-processing logic\n(e.g., log latency, add headers)"]
ABORT_VALID["Fail gracefully\ncsilk_json_error()\ncsilk_abort()"]
ABORT_PANIC["Fail hard\ncsilk_panic(ctx)\n→ Caught by Recovery"]
DONE["Done"]
START --> PRE
PRE --> DEC
DEC -->|Yes| NEXT
DEC -->|Recoverable| ABORT_VALID
DEC -->|Unrecoverable| ABORT_PANIC
NEXT --> POST
POST --> DONE
ABORT_VALID --> DONE
ABORT_PANIC --> DONE
Basic Middleware Structure
void my_middleware(csilk_ctx_t* c) {
// === PRE-PROCESSING ===
// Access request data
const char* token = csilk_get_header(c, "Authorization");
const char* ip = csilk_get_client_ip(c);
// Validate / check conditions
if (!token) {
csilk_json_error(c, 401, "Missing token");
return; // Abort chain (don't call csilk_next)
}
// Store data for later middleware to use
csilk_set(c, "user_id", (void*)42);
// === DELEGATE ===
csilk_next(c);
// === POST-PROCESSING ===
// Only runs if csilk_next returns normally
// (i.e., no abort and no panic)
CSILK_LOG_I("Request completed");
}
Common Middleware Recipes
1. Authentication Middleware
void auth_middleware(csilk_ctx_t* c) {
const char* auth = csilk_get_header(c, "Authorization");
if (!auth || strncmp(auth, "Bearer ", 7) != 0) {
csilk_json_error(c, 401, "Unauthorized");
return;
}
const char* token = auth + 7;
if (!validate_token(token)) {
csilk_json_error(c, 403, "Invalid token");
return;
}
csilk_next(c);
// Post: audit logging
}
2. Timing/Logging Middleware
void timing_middleware(csilk_ctx_t* c) {
uint64_t start = uv_hrtime();
csilk_next(c);
uint64_t elapsed = (uv_hrtime() - start) / 1000000;
CSILK_LOG_I("%s %s → %d (%llums)",
csilk_get_method(c),
csilk_get_path(c),
csilk_get_status(c),
elapsed);
}
3. Response Header Injection
void security_headers_middleware(csilk_ctx_t* c) {
csilk_next(c);
// Add security headers after handler runs
csilk_set_header(c, "X-Content-Type-Options", "nosniff");
csilk_set_header(c, "X-Frame-Options", "DENY");
csilk_set_header(c, "X-XSS-Protection", "1; mode=block");
}
4. JWT Authorization Example
void protected_resource_handler(csilk_ctx_t* c) {
// Built-in JWT middleware already verified the token and stored payload
cJSON* payload = (cJSON*)csilk_get(c, "jwt_payload");
if (payload) {
const char* user = cJSON_GetObjectItem(payload, "sub")->valuestring;
CSILK_LOG_I("Access by user: %s", user);
}
csilk_string(c, 200, "Protected data");
}
// In main:
// csilk_group_use(api, (csilk_handler_t)csilk_jwt_middleware, "secret");
5. Request Tracing with Request ID
void trace_middleware(csilk_ctx_t* c) {
const char* req_id = csilk_get_request_id(c);
// Add request ID to all logs via context-aware logging (future feature)
// Or just manually:
CSILK_LOG_I("[%s] Started request", req_id);
csilk_next(c);
}
Registration Methods
flowchart TB
subgraph "Global Middleware"
G["csilk_server_use(server, handler)\n→ Applied to ALL routes\n→ Max 32 global middlewares"]
end
subgraph "Group Middleware"
GRP["csilk_group_use(group, handler)\n→ Applied to routes in group\n→ Inherits parent group middlewares"]
end
subgraph "Route Handlers"
R["csilk_router_add(r, method, path, [h1, h2, NULL], 2)\n→ Handlers specific to one route"]
end
subgraph "Execution Order"
G --> GRP
GRP --> R
Note["Combined chain:\nglobal[0] → global[1] → group[0] → route[0] → route[1]"]
end
Middleware Chain Assembly
sequenceDiagram
participant S as csilk_server_t
participant G as csilk_group_t
participant R as csilk_router_t
participant C as csilk_ctx_t
Note over S: csilk_server_use(server, recovery)
S->>S: middlewares[0] = recovery
S->>S: middleware_count = 1
Note over S: csilk_server_use(server, logger)
S->>S: middlewares[1] = logger
S->>S: middleware_count = 2
Note over G: csilk_group_use(api, auth)
G->>G: api->middlewares[0] = auth
G->>G: api->middleware_count = 1
Note over R: csilk_group_add_route(api, GET, "/ping", handler)
Note over C: At on_message_complete():
C->>R: csilk_router_match_ctx() → route_handlers = [auth, handler]
C->>S: Prepend global middlewares → [recovery, logger, auth, handler]
C->>C: csilk_next() starts at index 0
Best Practices
-
Always call
csilk_next(c)in middleware that wants to continue the chain. Skipping it terminates the chain and sends the response. -
Use
csilk_abort(c)to terminate the chain early without setting a response body (useful withcsilk_redirectwhich already set the response). -
Store data with
csilk_set(c, key, value)to pass data between middlewares and handlers. Data is stored on the context's linked list storage. -
Don't call
csilk_next()in post-processing logic -- only call it once in your middleware. The call stack handles the return path automatically. -
Use the Recovery middleware as the FIRST global middleware to ensure any
csilk_panic()calls in downstream handlers are caught. -
Use Arena-allocated memory for temporary data within handlers. Memory allocated on the Arena is automatically freed at the end of the request cycle.
Error Handling Flow
flowchart TB
REQ["Request"]
REC["Recovery Middleware\n(setjmp here)"]
AUTH["Auth Middleware"]
HAND["Business Handler"]
REQ --> REC
REC --> AUTH
AUTH -- "Valid" --> HAND
AUTH -- "No token" --> ERR1["csilk_json_error(c, 401)\nreturn (skip csilk_next)"]
AUTH -- "Invalid token" --> ERR2["csilk_panic(c, 'bad token')"]
HAND -- "Normal" --> OK["csilk_string(c, 200, 'OK')"]
HAND -- "Crash" --> ERR3["csilk_panic(c, 'db error')"]
ERR2 --> REC_L["longjmp → Recovery"]
ERR3 --> REC_L
REC_L --> RECOVER["csilk_string(c, 500)\ncsilk_abort(c)"]