cwist
Modern C web framework with libttak runtime
Loading...
Searching...
No Matches
mux.c
Go to the documentation of this file.
1#define _POSIX_C_SOURCE 200809L
5#include <stdlib.h>
6#include <string.h>
7#include <stdio.h>
8#include <stdint.h>
9#include <math.h>
10
16#define CWIST_MUX_DEFAULT_BUCKETS 4099
17
18typedef struct {
19 uint64_t hi;
20 uint64_t lo;
21} cwist_mux_signature;
22
29static cwist_mux_signature cwist_mux_signature_from_path(cwist_http_method_t method,
30 const char *path) {
31 uint64_t hi = 14695981039346656037ULL;
32 uint64_t lo = 0xcbf29ce484222325ULL;
33
34 const uint64_t prime_hi = 1099511628211ULL; /* FNV Prime 64 */
35 const uint64_t prime_lo = 0x9e3779b185ebca87ULL; /* SplitMix Prime */
36
37 /* 1. Method hashing (single pass) */
38 const uint8_t *m_ptr = (const uint8_t *)&method;
39 for (size_t i = 0; i < sizeof(method); ++i) {
40 hi = (hi ^ m_ptr[i]) * prime_hi;
41 lo = (lo ^ m_ptr[i]) * prime_lo;
42 }
43
44 /* 2. Path normalization and single-pass hashing */
45 if (!path) {
46 path = "/";
47 }
48
49 size_t seg_count = 0;
50 const char *cursor = path;
51
52 while (*cursor) {
53 while (*cursor == '/') {
54 cursor++;
55 }
56 if (!*cursor) {
57 break;
58 }
59
60 const char *start = cursor;
61 while (*cursor && *cursor != '/') {
62 cursor++;
63 }
64
65 /* Inject normalized segment delimiter '/' */
66 hi = (hi ^ '/') * prime_hi;
67 lo = (lo ^ '/') * prime_lo;
68
69 /* Hash segment bytes */
70 for (const char *p = start; p < cursor; ++p) {
71 uint8_t b = (uint8_t)*p;
72 hi = (hi ^ b) * prime_hi;
73 lo = (lo ^ b) * prime_lo;
74 }
75 seg_count++;
76 }
77
78 /* Handle root path ('/') or empty path */
79 if (seg_count == 0) {
80 hi = (hi ^ '/') * prime_hi;
81 lo = (lo ^ '/') * prime_lo;
82 }
83
84 return (cwist_mux_signature){.hi = hi, .lo = lo};
85}
86
93static size_t cwist_mux_bucket_index(const cwist_mux_router *router,
94 const cwist_mux_signature *sig) {
95 uint64_t hash = sig->hi ^ (sig->lo + 0x9e3779b97f4a7c15ULL);
96
97 /* SplitMix64-style finalizer to maximize avalanche effect. */
98 hash ^= hash >> 30;
99 hash *= 0xbf58476d1ce4e5b9ULL;
100 hash ^= hash >> 27;
101 hash *= 0x94d049bb133111ebULL;
102 hash ^= hash >> 31;
103
104 return (size_t)(hash % router->bucket_count);
105}
106
107/* --- Mux Router Implementation --- */
108
115 if (!router) return NULL;
117 router->buckets =
119 if (!router->buckets) {
120 cwist_free(router);
121 return NULL;
122 }
123 memset(router->buckets, 0, router->bucket_count * sizeof(cwist_mux_route *));
124 router->routes = NULL;
125 router->param_routes = NULL;
126 router->wildcard_routes = NULL;
127 return router;
128}
129
135 if (!router) return;
136 cwist_mux_route *curr = router->routes;
137 while (curr) {
138 cwist_mux_route *next = curr->next;
140 cwist_mux_middleware_node *mw = curr->middleware;
141 while (mw) {
143 cwist_free(mw);
144 mw = mw_next;
145 }
147 curr = next;
148 }
149 cwist_free(router->buckets);
150 cwist_free(router);
151}
152
160void cwist_mux_handle(cwist_mux_router *router, cwist_http_method_t method, const char *path,
161 cwist_http_handler_func handler) {
162 if (!router || !path || !handler) return;
163
165 if (!route) return;
166
167 route->method = method;
168 route->path = cwist_sstring_create();
169 cwist_sstring_assign(route->path, (char *)path);
170 route->handler = handler;
171 route->bucket_next = NULL;
172 route->param_next = NULL;
173 route->middleware = NULL;
174 route->is_wildcard = false;
175 route->next = router->routes;
176 router->routes = route;
177
178 size_t path_len = strlen(path);
179 route->is_parametric = (strchr(path, ':') != NULL);
180 route->is_wildcard = (path_len > 0 && path[path_len - 1] == '*');
181
182 if (route->is_wildcard) {
183 route->param_next = router->wildcard_routes;
184 router->wildcard_routes = route;
185 } else if (route->is_parametric) {
186 route->param_next = router->param_routes;
187 router->param_routes = route;
188 } else {
189 cwist_mux_signature signature = cwist_mux_signature_from_path(method, path);
190 route->signature_hi = signature.hi;
191 route->signature_lo = signature.lo;
192
193 size_t idx = cwist_mux_bucket_index(router, &signature);
194 route->bucket_next = router->buckets[idx];
195 router->buckets[idx] = route;
196 }
197}
198
210static bool match_parametric_route(const char *route_tmpl, const char *req_path,
212 const char *t = route_tmpl;
213 const char *p = req_path;
215
216 while (*t && *p) {
217 if (*t == ':') {
218 t++;
219 const char *t_next = strchr(t, '/');
220 size_t t_len = t_next ? (size_t)(t_next - t) : strlen(t);
221 char param_name[256] = {0};
222 if (t_len < sizeof(param_name)) memcpy(param_name, t, t_len);
223
224 const char *p_next = strchr(p, '/');
225 size_t p_len = p_next ? (size_t)(p_next - p) : strlen(p);
226 char param_value[256] = {0};
227 if (p_len < sizeof(param_value)) memcpy(param_value, p, p_len);
228
231
232 t += t_len;
233 p += p_len;
234 } else if (*t == *p) {
235 t++;
236 p++;
237 } else {
239 return false;
240 }
241 }
242
243 // Ignore trailing slashes
244 if (*t == '/' && *(t + 1) == '\0') t++;
245 if (*p == '/' && *(p + 1) == '\0') p++;
246
247 if (*t == '\0' && *p == '\0') {
249 return true;
250 }
251
253 return false;
254}
255
263static bool match_wildcard_route(const char *route_tmpl, const char *req_path) {
264 size_t len = strlen(route_tmpl);
265 if (len > 0 && route_tmpl[len - 1] == '*') {
266 size_t prefix_len = len - 1;
267 return strncmp(req_path, route_tmpl, prefix_len) == 0;
268 }
269 return false;
270}
271
279static void mux_chain_next(cwist_http_request *req, cwist_http_response *res) {
280 typedef struct {
285 if (!state) return;
286 if (state->current) {
287 cwist_mux_middleware_node *mw = state->current;
288 state->current = mw->next;
289 mw->func(req, res, mux_chain_next);
290 } else {
291 state->handler(req, res);
292 }
293}
294
305static void run_middleware_chain(cwist_mux_middleware_node *mw, cwist_http_request *req,
307 typedef struct {
311 mux_chain_state state = {.current = mw, .handler = handler};
312 req->route_middleware_state = &state;
313 mux_chain_next(req, res);
315}
316
326 const char *path) {
327 if (!router || !path) return NULL;
328 cwist_mux_signature signature = cwist_mux_signature_from_path(method, path);
329 size_t idx = cwist_mux_bucket_index(router, &signature);
330 cwist_mux_route *curr = router->buckets[idx];
331 while (curr) {
332 if (curr->method == method && curr->signature_hi == signature.hi &&
333 curr->signature_lo == signature.lo && curr->path && curr->path->data &&
334 strcmp(curr->path->data, path) == 0) {
335 return curr;
336 }
338 }
339 curr = router->param_routes;
340 while (curr) {
341 if (curr->method == method && curr->path && curr->path->data) {
343 if (match_parametric_route(curr->path->data, path, &dummy)) {
345 return curr;
346 }
347 }
348 curr = curr->param_next;
349 }
350 return NULL;
351}
352
353/* --- Route Groups --- */
354
362 if (!router || !prefix) return NULL;
364 if (!group) return NULL;
365 group->router = router;
366 group->prefix = (char *)cwist_alloc(strlen(prefix) + 1);
367 if (!group->prefix) {
369 return NULL;
370 }
371 strcpy(group->prefix, prefix);
372 return group;
373}
374
382 if (!group) return;
383 cwist_free(group->prefix);
385}
386
397 cwist_http_handler_func handler) {
398 if (!group || !group->router || !path || !handler) return;
399 size_t prefix_len = strlen(group->prefix);
400 size_t path_len = strlen(path);
401 char *full_path CWIST_DEFER_FREE = (char *)cwist_alloc(prefix_len + path_len + 1);
402 if (!full_path) return;
403 memcpy(full_path, group->prefix, prefix_len);
404 memcpy(full_path + prefix_len, path, path_len);
405 full_path[prefix_len + path_len] = '\0';
406 cwist_mux_handle(group->router, method, full_path, handler);
407}
408
409/* --- Per-Route Middleware --- */
410
419 if (!route || !mw) return;
422 if (!node) return;
423 node->func = mw;
424 node->next = route->middleware;
425 route->middleware = node;
426}
427
436 if (!router || !req || !res) return false;
437
438 const char *path = (req->path && req->path->data) ? req->path->data : "/";
439 cwist_mux_signature signature = cwist_mux_signature_from_path(req->method, path);
440 size_t idx = cwist_mux_bucket_index(router, &signature);
441 cwist_mux_route *curr = router->buckets[idx];
442
443 // 1. Try exact match (O(1))
444 while (curr) {
445 if (curr->method == req->method && curr->signature_hi == signature.hi &&
446 curr->signature_lo == signature.lo && curr->path && curr->path->data &&
447 strcmp(curr->path->data, path) == 0) {
448 if (curr->middleware) {
449 run_middleware_chain(curr->middleware, req, res, curr->handler);
450 } else {
451 curr->handler(req, res);
452 }
453 return true;
454 }
455 curr = curr->bucket_next;
456 }
457
458 // 2. Try parametric matching (linear search)
459 curr = router->param_routes;
460 while (curr) {
461 if (curr->method == req->method && curr->path && curr->path->data) {
463 if (match_parametric_route(curr->path->data, path, &extracted_params)) {
464 if (extracted_params) {
465 if (req->path_params) {
467 }
469 }
470 if (curr->middleware) {
471 run_middleware_chain(curr->middleware, req, res, curr->handler);
472 } else {
473 curr->handler(req, res);
474 }
475 return true;
476 }
477 }
478 curr = curr->param_next;
479 }
480
481 // 3. Try wildcard matching (linear search)
482 curr = router->wildcard_routes;
483 while (curr) {
484 if (curr->method == req->method && curr->path && curr->path->data) {
485 if (match_wildcard_route(curr->path->data, path)) {
486 if (curr->middleware) {
487 run_middleware_chain(curr->middleware, req, res, curr->handler);
488 } else {
489 curr->handler(req, res);
490 }
491 return true;
492 }
493 }
494 curr = curr->param_next;
495 }
496
497 return false;
498}
Memory allocation wrappers backed by libttak.
void cwist_free(void *ptr)
Free memory obtained via cwist_alloc/cwist_strdup/etc.
Definition alloc.c:52
#define CWIST_DEFER_FREE
Attach to a pointer variable's declaration to cwist_free() it automatically when that variable's encl...
Definition alloc.h:101
void * cwist_alloc_array(size_t count, size_t elem_size)
Allocate zeroed memory for count elements.
Definition alloc.c:21
void * cwist_alloc(size_t size)
Allocate zeroed memory tracked by libttak.
Definition alloc.c:17
cwist_sstring * cwist_sstring_create(void)
Create a new sstring instance.
Definition sstring.c:802
cwist_error_t cwist_sstring_assign(cwist_sstring *str, const char *data)
Assign a C string.
Definition sstring.c:504
void cwist_sstring_destroy(cwist_sstring *str)
Destroy an sstring instance.
Definition sstring.c:823
cwist_http_method_t
Definition http.h:16
void cwist_mux_route_use(cwist_mux_route *route, cwist_middleware_func mw)
Attach a middleware callback to a route, applied only when that route is served.
Definition mux.c:418
void cwist_mux_group_destroy(cwist_mux_group *group)
Destroy a route group and its stored prefix string.
Definition mux.c:381
bool cwist_mux_serve(cwist_mux_router *router, cwist_http_request *req, cwist_http_response *res)
Attempt to dispatch an incoming request through the mux table.
Definition mux.c:435
void cwist_mux_handle(cwist_mux_router *router, cwist_http_method_t method, const char *path, cwist_http_handler_func handler)
Register a concrete method/path pair inside the mux buckets.
Definition mux.c:160
cwist_mux_route * cwist_mux_find_route(cwist_mux_router *router, cwist_http_method_t method, const char *path)
Look up a registered route for a method/path pair without dispatching to it.
Definition mux.c:325
cwist_mux_router * cwist_mux_router_create(void)
Create an empty mux router with the default bucket fan-out.
Definition mux.c:113
cwist_mux_group * cwist_mux_group_create(cwist_mux_router *router, const char *prefix)
Create a route group that registers all its handlers under a shared path prefix.
Definition mux.c:361
void cwist_mux_group_handle(cwist_mux_group *group, cwist_http_method_t method, const char *path, cwist_http_handler_func handler)
Register a method/path handler on the router, prefixed by the group's prefix.
Definition mux.c:396
void cwist_mux_router_destroy(cwist_mux_router *router)
Destroy a mux router and all registered route records.
Definition mux.c:134
#define CWIST_MUX_DEFAULT_BUCKETS
Definition mux.c:16
void(* cwist_http_handler_func)(cwist_http_request *req, cwist_http_response *res)
Definition mux.h:12
HTTP Protocol Definitions and Helpers.
HTTP request multiplexer (router).
void cwist_query_map_set(cwist_query_map *map, const char *key, const char *value)
Set a key-value pair.
Definition query.c:146
cwist_query_map * cwist_query_map_create(void)
Create a new query map.
Definition query.c:54
void cwist_query_map_destroy(cwist_query_map *map)
Destroy a query map.
Definition query.c:81
size_t cap
Definition otel.c:56
cwist_sstring * path
e.g., "/users/1"
Definition http.h:67
void * route_middleware_state
Router middleware chain state (internal).
Definition http.h:181
cwist_query_map * path_params
Parsed path parameters (e.g. :id)
Definition http.h:70
cwist_http_method_t method
Definition http.h:66
HTTP Response Object. Supports standard string body or Zero-Copy pointer body.
Definition http.h:82
cwist_mux_router * router
Definition mux.h:46
cwist_middleware_func func
Definition mux.h:19
struct cwist_mux_middleware_node * next
Definition mux.h:20
struct cwist_mux_route * next
Used for global cleanup.
Definition mux.h:18
struct cwist_mux_route * param_next
Used for parametric routes.
Definition mux.h:33
cwist_http_method_t method
Definition mux.h:15
struct cwist_mux_route * bucket_next
Used for exact matches.
Definition mux.h:32
size_t bucket_count
Definition mux.h:38
cwist_mux_route * param_routes
Head of the parametric routes list.
Definition mux.h:41
cwist_mux_route ** buckets
Definition mux.h:39
cwist_mux_route * wildcard_routes
Head of wildcard routes list.
Definition mux.h:42
cwist_mux_route * routes
Definition mux.h:22
char * data
Access directly only when raw handling is necessary.
Definition sstring.h:15
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.
Definition app.h:115