From 115bf74456ea358eb77cb91b5148351b5d950a39 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Sun, 25 Feb 2024 22:59:58 +0100 Subject: [PATCH] feat(routing): multi-segment wildcard matches --- example/routing.c | 24 +++++++++++ src/_include/lnm/http/loop_internal.h | 25 +++++------ src/http/lnm_http_router.c | 60 +++++++++++++++++++++++++-- 3 files changed, 94 insertions(+), 15 deletions(-) diff --git a/example/routing.c b/example/routing.c index 19d64e1..e9ac1b1 100644 --- a/example/routing.c +++ b/example/routing.c @@ -23,6 +23,25 @@ lnm_http_step_err print_step(lnm_http_conn *conn) { return lnm_http_step_err_done; } +lnm_http_step_err print_step2(lnm_http_conn *conn) { + lnm_http_loop_ctx *ctx = conn->ctx; + + const char *key; + size_t key_len = lnm_http_req_route_segment(&key, &ctx->req, "key") ; + lnm_linfo("main", "yuhh key: %.*s", key_len, key); + return lnm_http_step_err_done; +} + +lnm_http_step_err print_step3(lnm_http_conn *conn) { + lnm_http_loop_ctx *ctx = conn->ctx; + + const char *key; + size_t key_len = lnm_http_req_route_segment(&key, &ctx->req, "cool") ; + lnm_linfo("main", "cool: %.*s", key_len, key); + return lnm_http_step_err_done; +} + + int main() { lnm_http_loop *hl; @@ -34,8 +53,13 @@ int main() { lnm_http_router_init(&router); lnm_http_route *route; + lnm_http_router_add(&route, router, lnm_http_method_get, "/emma"); lnm_http_router_add(&route, router, lnm_http_method_get, "/:key"); lnm_http_route_step_append(route, print_step, false); + lnm_http_router_add(&route, router, lnm_http_method_get, "/:key/two"); + lnm_http_route_step_append(route, print_step2, false); + lnm_http_router_add(&route, router, lnm_http_method_get, "/*cool"); + lnm_http_route_step_append(route, print_step3, false); lnm_http_loop_router_set(hl, router); diff --git a/src/_include/lnm/http/loop_internal.h b/src/_include/lnm/http/loop_internal.h index 2a8c0ec..d5cd510 100644 --- a/src/_include/lnm/http/loop_internal.h +++ b/src/_include/lnm/http/loop_internal.h @@ -5,19 +5,25 @@ #include "lnm/http/loop.h" -struct lnm_http_router { - struct lnm_http_router *exact_children[128]; - struct lnm_http_router *single_segment_child; - lnm_http_route *routes[lnm_http_method_total]; - bool represents_route; -}; - typedef struct lnm_http_route_segment_trie { struct lnm_http_route_segment_trie *children[128]; size_t index; bool represents_segment; } lnm_http_route_segment_trie; +struct lnm_http_route { + lnm_http_route_segment_trie *key_segments; + lnm_http_step *step; +}; + +struct lnm_http_router { + struct lnm_http_router *exact_children[128]; + struct lnm_http_router *single_segment_child; + lnm_http_route *multi_segment_routes[lnm_http_method_total]; + lnm_http_route *routes[lnm_http_method_total]; + bool represents_route; +}; + lnm_err lnm_http_route_segment_trie_init(lnm_http_route_segment_trie **out); void lnm_http_route_segment_trie_free(lnm_http_route_segment_trie *trie); @@ -39,11 +45,6 @@ typedef struct lnm_http_step { */ lnm_err lnm_http_step_init(lnm_http_step **out); -struct lnm_http_route { - lnm_http_route_segment_trie *key_segments; - lnm_http_step *step; -}; - lnm_err lnm_http_route_init(lnm_http_route **out); void lnm_http_route_free(lnm_http_route *route); diff --git a/src/http/lnm_http_router.c b/src/http/lnm_http_router.c index e735e02..ce15d9a 100644 --- a/src/http/lnm_http_router.c +++ b/src/http/lnm_http_router.c @@ -93,9 +93,42 @@ lnm_err lnm_http_router_add(lnm_http_route **out, lnm_http_router *http_router, http_router = http_router->single_segment_child; path = new_path; } break; - case '*': - // TODO multi-segment wildcard - break; + case '*': { + const char *next_slash_ptr = strchr(path + 1, '/'); + + // Star match should be at end of route + if (next_slash_ptr != NULL) { + res = lnm_err_invalid_route; + + goto end; + } + + const char *end = strchr(path + 1, '\0'); + size_t key_len = end - (path + 1); + + if (key_len == 0) { + res = lnm_err_invalid_route; + + goto end; + } + + res = lnm_http_route_key_segment_insert(route, path + 1, key_len, + key_segments_count); + + if (res != lnm_err_ok) { + goto end; + } + + key_segments_count++; + + if (http_router->multi_segment_routes[method] != NULL) { + res = lnm_err_overlapping_route; + } + + http_router->multi_segment_routes[method] = route; + goto end; + + } break; default: if (http_router->exact_children[c] == NULL) { res = lnm_http_router_init(&http_router->exact_children[c]); @@ -107,6 +140,7 @@ lnm_err lnm_http_router_add(lnm_http_route **out, lnm_http_router *http_router, http_router = http_router->exact_children[c]; path++; + break; } } @@ -195,6 +229,26 @@ static lnm_http_route_err __lnm_http_router_route(lnm_http_route_match *out, } } + const lnm_http_route *multi_segment_route = + router->multi_segment_routes[method]; + + if (multi_segment_route != NULL) { + const char *end_ptr = strchr(path + path_index, '\0'); + size_t segment_len = end_ptr - (path + path_index); + + // TODO do 405's make sense for star matches? + + if (segment_len > 0) { + if (out != NULL) { + out->route = multi_segment_route; + out->key_segments[matched_key_segments].start = path_index; + out->key_segments[matched_key_segments].len = segment_len; + } + + return lnm_http_route_err_match; + } + } + return res; }