Repository navigation
Middleware
Headers: <cwist/app.h> (chain API), <cwist/sys/app/middleware.h>
(built-ins), <cwist/sys/app/compress.h>, <cwist/sys/app/csrf.h>,
<cwist/sys/app/waf.h>, <cwist/net/http/session.h>
A middleware wraps the rest of the chain: it can act before and after the
handler, change the request or response, or stop the chain by returning
without calling next.
typedef void (*cwist_middleware_func)(cwist_http_request *req, cwist_http_response *res,
cwist_handler_func next);
void cwist_app_use(cwist_app *app, cwist_middleware_func mw);static void timing(cwist_http_request *req, cwist_http_response *res, cwist_handler_func next) {
struct timespec a, b;
clock_gettime(CLOCK_MONOTONIC, &a);
next(req, res); /* rest of the chain + handler */
clock_gettime(CLOCK_MONOTONIC, &b);
char buf[32];
snprintf(buf, sizeof buf, "%ld", (b.tv_nsec - a.tv_nsec) / 1000 + (b.tv_sec - a.tv_sec) * 1000000);
cwist_http_header_add(&res->headers, "X-Elapsed-Us", buf);
}
cwist_app_use(app, timing);- Middleware runs in registration order: the first registered is the outermost.
- The chain runs for requests that match a route, a content-hashed asset or a static file.
-
It does not run on a route miss. A request that matches nothing goes
straight to the 404 error handler, so access logs do not record 404s and
response headers added by middleware are absent from 404 responses. This
includes CORS preflight: an
OPTIONSrequest for a path that only has aGETroute is a route miss and gets404; register anOPTIONSroute for paths that need preflight. - Handlers and middleware run concurrently on several threads; protect shared state.
typedef void (*cwist_middleware_func_ex)(cwist_http_request *req, cwist_http_response *res,
cwist_handler_func next, void *user_ctx);
typedef void (*cwist_middleware_ctx_destroy_func)(void *user_ctx);
cwist_error_t cwist_app_use_ex(cwist_app *app, cwist_middleware_func mw,
cwist_middleware_func_ex mw_ex, void *user_ctx,
cwist_middleware_ctx_destroy_func destroy);Pass exactly one of mw and mw_ex. user_ctx is passed to mw_ex on every
call; destroy runs once when the app is destroyed or, if registration fails,
before the call returns. Returns INT16 0 on success and -1 when app is
NULL, not exactly one function is set, or allocation fails. Multiport
sub-apps share the context without owning it. Language bindings use this to
register closures.
| Factory | Behavior |
|---|---|
cwist_mw_request_id(const char *header_name) |
Reuses the request's X-Request-Id or generates one, and sets it on the request and the response. The header_name argument is currently ignored; the header is always X-Request-Id. |
cwist_mw_access_log(cwist_log_format_t format) |
Writes one line per request to stdout after the handler: CWIST_LOG_COMMON, CWIST_LOG_COMBINED (adds Referer and User-Agent) or CWIST_LOG_JSON. For a deferred response the status and size are logged as - (null in JSON). |
cwist_mw_rate_limit_ip(int requests_per_minute) |
Per-client-IP token bucket; over the limit it answers 429 with Retry-After and stops the chain. See notes below. |
cwist_mw_cors(void) |
Adds Access-Control-Allow-Origin: * to every response. For OPTIONS it adds Access-Control-Allow-Methods, Access-Control-Allow-Headers: Content-Type, Authorization, X-Request-Id and Access-Control-Max-Age: 86400, and answers 204 without calling the handler (subject to the route-miss rule above). |
cwist_mw_metrics(void) |
Counts requests and total request duration into the metrics registry. It does not add a route; use cwist_app_enable_metrics() for GET /metrics. |
cwist_mw_jwt_auth(const char *secret) |
Requires Authorization: Bearer <token> (scheme matched case-insensitively) signed with HS256 by secret; otherwise answers 401 with a JSON error body. Claims are available to later stages through cwist_mw_jwt_get_claims(req). |
cwist_mw_compress(size_t min_body_size) |
Compresses bodies of at least min_body_size bytes with a registered backend chosen from Accept-Encoding. See Compression. |
cwist_mw_csrf(cwist_app *app) |
Double-submit-cookie CSRF protection. See CSRF and WAF. |
cwist_mw_waf_lite(void) |
Rejects requests matching the built-in attack signature set. See CSRF and WAF. |
cwist_mw_session(cwist_app *app) |
Loads the signed session cookie before the handler and commits it after. See Sessions and Flash. |
#include <cwist/sys/app/middleware.h>
cwist_app_use(app, cwist_mw_request_id(NULL));
cwist_app_use(app, cwist_mw_access_log(CWIST_LOG_JSON));
cwist_app_use(app, cwist_mw_rate_limit_ip(600));
cwist_app_use(app, cwist_mw_cors());
cwist_app_use(app, cwist_mw_metrics());cwist_app_use(app, cwist_mw_jwt_auth("change-me")); /* secret must outlive the app */
static void me(cwist_http_request *req, cwist_http_response *res) {
const cwist_jwt_claims *claims = cwist_mw_jwt_get_claims(req);
const char *sub = claims ? cwist_jwt_claims_get(claims, "sub") : NULL;
cwist_sstring_assign(res->body, sub ? sub : "anonymous");
}cwist_mw_jwt_get_claims() is valid only inside the chain behind the JWT
middleware, on the thread running it; the claims are freed when the chain
returns to the middleware. Copy anything you need later. One process supports
eight distinct JWT secrets; cwist_mw_jwt_auth() returns NULL past that
(and for a NULL secret). Registering the same secret again reuses its slot.
- The per-IP table holds 1024 clients per process. When it is full, new clients are allowed through (fail-open) so spoofed addresses cannot lock everyone out.
- The table is shared by every rate-limit middleware in the process, and a client's bucket is created with the rate of the first limiter that sees that client.
- A process supports eight distinct
requests_per_minutevalues. Past that,cwist_mw_rate_limit_ip()prints a warning to stderr and returns the limiter registered first, which uses that first rate. -
cwist_mw_rate_limit_reset()clears the table (for tests).
A handler may defer its response with cwist_async_defer()
(Async Handlers). Middleware still unwinds normally after the
handler returns, but the response no longer belongs to the chain: the access
log records it with unknown status and size, and cwist_mw_compress() leaves
it uncompressed.
App routes share one global chain. The standalone router offers per-route
middleware through cwist_mux_route_use(); see Routing.
CWIST wiki, written against the dev branch of c4punks/CWIST. Pages marked "Source:" are generated from files under docs/; fix those in the repository. Questions: Discord.
Getting started
- Installation
- Quick Start
- Linking
- Server Modes
- Configuration and Environment
- Project CLI
- Tutorial / Korean
- Examples and Tutorials
Guides
Core
- API Reference
- Application
- Routing
- Middleware
- Requests and Responses
- Async Handlers
- Streaming Responses
- Error Handling
- Graceful Shutdown
- Multiport
Protocols
- HTTPS and TLS
- HTTP/2
- HTTP/3 and QUIC
- WebTransport
- WebSocket
- Server-Sent Events
- gRPC Server
- gRPC Client
- Protobuf and Codegen
- GraphQL
- WebRTC DataChannels
- HTTP Clients
Web features
- Static Files and Assets
- Big Dumb Reply Cache
- Compression
- Cookies
- Sessions and Flash
- Query Maps
- Multipart Uploads
- HTML Components
- Templates
- JSON
- Validation
- OpenAPI
Security
Data
Runtime
- Memory Management
- Full GC
- Async GC Ownership
- SString
- Reactor and I/O
- Metrics and Health
- Logging
- Testing
Platforms
Performance notes
- Benchmark Methodology
- C1M File Limits
- Reactor Fairness
- Cooperative Queuing
- Classic Pool Starvation
- Reactor Wakeup
- wrk Dual Histogram
- FIXED Endpoint Cache
- ADR-0001
- Durable Queue Gate
- Mux References
Project