1#define _POSIX_C_SOURCE 200809L
16#include "../../net/http/async_internal.h"
26static unsigned int rid_seed = 0;
35static void generate_request_id(
char id[17]) {
36 static const char charset[] =
"abcdefghijklmnopqrstuvwxyz0123456789";
40 unsigned int seed = rid_seed++;
43 for (
int i = 0;
i < 16;
i++) {
126static void format_clf_time(
char *buf,
size_t len,
const struct timeval *
tv) {
143static void format_iso8601_time(
char *buf,
size_t len,
const struct timeval *
tv) {
148 snprintf(buf + pos, len - pos,
".%03ldZ",
tv->tv_usec / 1000);
205 const char *
rid = access_log_request_id(req, res);
213 access_log_status_bytes(res,
"-", status,
sizeof(status),
res_bytes,
sizeof(
res_bytes));
217 req->version ? req->version->data :
"HTTP/1.1", status,
res_bytes);
236 const char *
rid = access_log_request_id(req, res);
246 access_log_status_bytes(res,
"-", status,
sizeof(status),
res_bytes,
sizeof(
res_bytes));
248 access_log_printf(
"%s - %s [%s] \"%s %s %s\" %s %s \"%s\" \"%s\"\n",
ip_str,
rid ?
rid :
"-",
250 req->version ? req->version->data :
"HTTP/1.1", status,
res_bytes,
271 const char *
rid = access_log_request_id(req, res);
280 access_log_status_bytes(res,
"null", status,
sizeof(status),
res_bytes,
sizeof(
res_bytes));
283 "{\"time\":\"%s\",\"client\":\"%s\",\"rid\":\"%s\",\"method\":\"%s\",\"path\":\"%s\",\"protocol\":\"%s\",\"status\":%s,\"duration_ms\":%ld,\"req_bytes\":%zu,\"res_bytes\":%s}\n",
285 req->path->data, req->version ? req->version->data :
"HTTP/1.1", status,
msec,
req_bytes,
301 default:
return cwist_mw_access_log_common_handler;
339#include <ttak/limit/limit.h>
347#define MAX_IP_BUCKETS 1024
349static int ip_bucket_count = 0;
367 if (rpm <= 0) rpm = 60;
376 ip_bucket_t *found =
NULL;
377 for (
int i = 0;
i < ip_bucket_count;
i++) {
378 if (ip_buckets[
i].active &&
strcmp(ip_buckets[
i].ip, ip->
data) == 0) {
379 found = &ip_buckets[
i];
385 found = &ip_buckets[ip_bucket_count++];
386 strncpy(found->ip, ip->
data,
sizeof(found->ip) - 1);
387 found->ip[
sizeof(found->ip) - 1] =
'\0';
388 found->active =
true;
389 double rate = rpm / 60.0;
416#define CWIST_RATE_LIMIT_MAX_CFGS 8
423static int s_rate_cfg_count = 0;
426#define CWIST_RATE_LIMIT_DEFINE_WRAPPER(N) \
427 static void cwist_mw_rate_limit_wrap_##N(cwist_http_request *req, cwist_http_response *res, \
428 cwist_handler_func next) { \
429 cwist_mw_rate_limit_ip_handler(req, res, next, s_rate_cfgs[N].rpm); \
468 if (requests_per_minute <= 0) requests_per_minute = 60;
472 for (
int i = 0;
i < s_rate_cfg_count;
i++) {
473 if (s_rate_cfgs[
i].rpm == requests_per_minute) {
475 return s_rate_wrappers[
i];
481 fprintf(
stderr,
"[CWIST] cwist_mw_rate_limit_ip: maximum config slots exceeded\n");
482 return s_rate_wrappers[0];
485 int slot = s_rate_cfg_count++;
486 s_rate_cfgs[
slot].rpm = requests_per_minute;
489 return s_rate_wrappers[
slot];
508 "GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD");
510 "Content-Type, Authorization, X-Request-Id");
544typedef struct cwist_jwt_ctx {
547 struct cwist_jwt_ctx *outer;
550#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L)
552#elif defined(__GNUC__) || defined(__clang__)
599 if (next) next(req, res);
613#define CWIST_JWT_MAX_SECRETS 8
622#define CWIST_JWT_DEFINE_WRAPPER(N) \
623 static void cwist_mw_jwt_wrap_##N(cwist_http_request *req, cwist_http_response *res, \
624 cwist_handler_func next) { \
625 cwist_mw_jwt_auth_handler(req, res, next, s_jwt_cfgs[N].secret); \
665 fprintf(
stderr,
"[CWIST] cwist_mw_jwt_auth: maximum secret slots exceeded\n");
688 if (!req)
return NULL;
690 if (ctx->req == req)
return ctx->claims;
__attribute__((constructor))
Memory allocation wrappers backed by libttak.
void cwist_free(void *ptr)
Free memory obtained via cwist_alloc/cwist_strdup/etc.
void * cwist_alloc(size_t size)
Allocate zeroed memory tracked by libttak.
bool cwist_async_res_deferred(const cwist_http_response *res)
Convenience size-conversion and utility macros shared across CWIST.
#define CWIST_UNUSED(x)
Mark an intentionally unused variable to silence compiler warnings.
cwist_error_t cwist_sstring_assign(cwist_sstring *str, const char *data)
Assign a C string.
void cwist_sstring_destroy(cwist_sstring *str)
Destroy an sstring instance.
Simple JSON string builder utility.
char * cwist_http_header_get(cwist_http_header_node *head, const char *key)
Find a header value by key.
@ CWIST_HTTP_UNAUTHORIZED
cwist_error_t cwist_http_header_add(cwist_http_header_node **head, const char *key, const char *value)
Add a header to the list.
const char * cwist_http_method_to_string(cwist_http_method_t method)
Convert method enum to string.
void cwist_jwt_claims_destroy(cwist_jwt_claims *claims)
Free a claims object.
struct cwist_jwt_claims cwist_jwt_claims
Opaque JWT claims object returned after successful verification.
cwist_jwt_claims * cwist_jwt_verify(const char *token, const char *secret)
Verify a JWT token and return the decoded claims.
bool cwist_log_async_submit(int fd, const char *line, size_t len)
Queue one complete line for fd (STDOUT_FILENO or STDERR_FILENO).
Internal hook from the log call sites into the async log writer.
Production-grade metrics engine for CWIST.
cwist_metrics_registry_t * cwist_metrics_registry(void)
Obtain the global registry, creating it on first call.
@ CWIST_METRIC_REQUESTS_TOTAL
@ CWIST_METRIC_REQUEST_DURATION_NS
struct cwist_metrics_registry cwist_metrics_registry_t
Global metrics registry (singleton, lazy-initialised).
void cwist_metric_inc(cwist_metrics_registry_t *reg, cwist_metric_id_t id)
Increment a counter metric by 1.
void cwist_metric_add(cwist_metrics_registry_t *reg, cwist_metric_id_t id, uintmax_t delta)
Increment a counter metric by delta.
void cwist_mw_request_id_handler(cwist_http_request *req, cwist_http_response *res, cwist_handler_func next)
Attach an X-Request-Id header to both the request and the response.
struct cwist_jwt_ctx cwist_jwt_ctx_t
cwist_middleware_func cwist_mw_metrics(void)
Return the built-in metrics collection middleware.
#define CWIST_RATE_LIMIT_DEFINE_WRAPPER(N)
cwist_middleware_func cwist_mw_jwt_auth(const char *secret)
Register a JWT secret in a static slot and return the matching middleware wrapper.
cwist_middleware_func cwist_mw_request_id(const char *header_name)
Return the built-in request ID middleware.
const cwist_jwt_claims * cwist_mw_jwt_get_claims(const cwist_http_request *req)
Retrieve the claims the JWT middleware decoded for a request.
cwist_middleware_func cwist_mw_cors(void)
Return the built-in permissive CORS middleware.
void cwist_mw_cors_handler(cwist_http_request *req, cwist_http_response *res, cwist_handler_func next)
Inject permissive CORS headers and short-circuit preflight requests.
void cwist_mw_rate_limit_reset(void)
Reset all per-IP rate-limiter buckets, clearing tracked clients.
#define CWIST_RATE_LIMIT_MAX_CFGS
void cwist_mw_metrics_handler(cwist_http_request *req, cwist_http_response *res, cwist_handler_func next)
Increment request counter and observe duration for Prometheus metrics.
cwist_middleware_func cwist_mw_rate_limit_ip(int requests_per_minute)
Return the built-in per-IP rate-limiter middleware.
cwist_middleware_func cwist_mw_access_log(cwist_log_format_t format)
Return the built-in access-log middleware.
cwist_log_format_t
Access log middleware output formats.
HTTP Protocol Definitions and Helpers.
cwist_sstring * cwist_get_client_ip_from_fd(int fd)
Resolve the peer IP address for a connected client socket.
cwist_http_header_node * headers
cwist_http_method_t method
HTTP Response Object. Supports standard string body or Zero-Copy pointer body.
cwist_http_status_t status_code
cwist_http_header_node * headers
char * data
Access directly only when raw handling is necessary.
void(* cwist_handler_func)(cwist_http_request *req, cwist_http_response *res)
Function pointer type for HTTP route handlers.
void(* cwist_middleware_func)(cwist_http_request *req, cwist_http_response *res, cwist_handler_func next)
Middleware type that receives req/res pair and the next stage in the chain.
Built-in middleware factories.