Middleware API¶
Chain¶
Comptime middleware pipeline. Since Zig has no closures, the next callback is solved by generating the entire dispatch chain at comptime — each Chain type has its run method fully unrolled.
Types¶
NextFn = *const fn (std.Io, *Request, *Response) void— next callback typeMiddlewareFn = *const fn (std.Io, *Request, *Response, NextFn) void— middleware function signatureHandlerFn = *const fn (*Request, *Response) void— terminal handler function signature
Chain type¶
Chain(middlewares)— create a chain type; validates all items are functions at compile timeChain.run(io, req, res, handler)— execute the middleware chain, ending withhandler
Thread-local Terminal Handler¶
The terminal handler is passed through a threadlocal variable because Zig inner functions cannot capture outer parameters.
Logger¶
Request logging middleware using std.log.scoped(.http).
middleware(io, req, res, next)¶
Logs: HTTP method, path, response status code, and elapsed time in microseconds. Uses std.Io.Timestamp for timing.
CORS¶
Cross-Origin Resource Sharing middleware.
Config¶
allowed_origins: ?[]const []const u8 = null— allowed origins;nullallows all (reflects requestOrigin), empty slice blocks all, non-empty list restricts to listed originsallowed_methods: []const u8 = "GET, POST, PUT, DELETE, PATCH, OPTIONS"— methods for preflightallowed_headers: []const u8 = "Content-Type, Authorization, X-CSRF-Token"— headers for preflightmax_age: []const u8 = "86400"— preflight cache durationallow_credentials: bool = false— allow credentials (cookies, auth headers)
middleware(io, req, res, next)¶
Default CORS middleware — uses default config (block all origins by default; pass null origins to allow all).
middlewareWith(config)¶
Create a CORS middleware with custom Config. Returns a comptime-generated function pointer with signature (std.Io, *Request, *Response, *const fn (std.Io, *Request, *Response) void) void. Config:
- allowed_origins: ?[]const []const u8 = &.{} — null allows all (reflects request Origin), empty slice blocks all, non-empty list restricts to those origins
Behavior¶
- Requests without
Originheader pass through (not a CORS request) OPTIONSpreflight requests return 204 with CORS headers- Normal CORS requests get
Access-Control-Allow-Originheader and pass through - Blocked origins get 403
CSRF¶
Cross-Site Request Forgery protection middleware. Stores a 256-bit random token in the session.
Behavior¶
- Safe methods (GET, HEAD, OPTIONS): pass through, token set in
X-CSRF-Tokenresponse header - Unsafe methods (POST, PUT, DELETE, PATCH): validate
X-CSRF-Tokenheader or_csrfform value against session
Functions¶
setIo(io)— set thread-local io for use by handler-context helpersgetIo() std.Io— get the thread-local iomiddleware(io, req, res, next)— CSRF middleware functionensureToken(io, req) ![]const u8— return the CSRF token from the session, creating and storing a random one if absent; requiresreq.userto be a*SessionensureTokenForRequest(req) ![]const u8— same asensureTokenusing thread-local iovalidateTokenForRequest(req, token) bool— validate a token against the session token (constant-time comparison)formFieldForRequest(io, gpa, req) ![]u8— return an owned HTML hidden input<input type="hidden" name="_csrf" value="...">using the request session tokenformField(io, req) ![]u8— convenience wrapper forformFieldForRequest
Rate Limit¶
Fixed window rate limiter per client IP.
Config¶
max_requests: u32 = 100— max requests per windowwindow_seconds: u32 = 60— window duration
RateLimiter¶
RateLimiter.init(allocator, config) RateLimiter— create a new limiterlimiter.deinit()— free internal statelimiter.allow(key, io) !bool— check if a request from the given key is allowed
middleware(io, req, res, next)¶
Default rate limit middleware (100 req/min). Uses X-Forwarded-For header or "default" as the key.
middlewareWith(config)¶
Create a rate limit middleware type with custom configuration. Returns a comptime struct type with:
- handle(io, req, res, next) — the middleware function
- deinit() — free internal state
- middleware() — get the function pointer for use in Chain
Example:
const MyRL = zypher.middleware.rate_limit.middlewareWith(.{ .max_requests = 50, .window_seconds = 30 });
// Use: MyRL.handle as the middleware function
Static¶
Static file serving middleware.
Config¶
root_dir: []const u8 = "./public"— root directory to serve files fromprefix: []const u8 = "/"— URL prefix to stripserve_index: bool = true— whether to serve index.html for directory paths
middleware(io, req, res, next)¶
Default static middleware (root: ./public).
middlewareWith(config)¶
Create a static middleware with custom config. Returns a comptime-generated function pointer.
Features¶
- MIME type detection by extension (70+ types including web, images, fonts, video, audio, documents, archives, executables, scripts)
- Path traversal protection (rejects
..segments) - ETag/304 support via
If-None-Match(XxHash32 of content) Last-Modified/If-Modified-Sincesupport (usesstd.Io.File.stat)- Passes through to next handler if file not found
Compress¶
Response compression middleware (gzip).
middleware(io, req, res, next)¶
Compresses buffered response body (res.body) with gzip if client sends Accept-Encoding: gzip. Does not compress res.file_body (streaming file descriptor) or chunked streaming bodies. Skips encoding if:
- Client doesn't accept gzip
- Response already has a Content-Encoding header
- Response has no body or empty body
Sets Content-Encoding: gzip and Vary: Accept-Encoding headers.
Session¶
Session management middleware. Loads/saves session on every request.
Thread-local Configuration¶
setStore(store: *SessionStore)— set the session store for the current threadsetCookieConfig(config: CookieConfig)— set cookie attributes (HttpOnly, Secure, SameSite, Path, Max-Age)resetCookieConfig()— restore default secure cookie config
middleware(io, req, res, next)¶
- Reads
zypher_sessioncookie - Loads session from store via cookie hex ID
- Attaches session pointer to
req.user(cast to*anyopaque) - If no valid session, creates a new one, sets cookie, and attaches it
- Calls
next(handler may mutate session data through the pointer) - Post-handler: session is already saved via the pointer's in-place modifications
Security Headers¶
Security header injection middleware. Sets standard security-related HTTP headers on every response.
Options¶
x_content_type_options: bool = true—X-Content-Type-Options: nosniffx_frame_options: bool = true—X-Frame-Options: DENYreferrer_policy: bool = true—Referrer-Policy: strict-origin-when-cross-originx_xss_protection: bool = true—X-XSS-Protection: 0content_security_policy: bool = false—Content-Security-Policy: default-src 'self'strict_transport_security: bool = false—Strict-Transport-Security: max-age=31536000; includeSubDomainscustom_headers: []const []const u8 = &.{}— custom header name/value pairs (flat array)
Functions¶
configure(opts: Options) void— configure security headers (thread-local)middleware(io, req, res, next)— add configured security headers
Recovery¶
Recovery middleware placeholder. Zig panics are not recoverable through a middleware function signature; this middleware simply calls next(io, req, res). Use process supervision for panic isolation.
middleware(io, req, res, next)¶
Calls the next handler. Does not catch panics.