From 2ce49a3347aba6ba77c00b81e3f74ceb8ecfe33f Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Sat, 2 Mar 2024 11:11:54 +0100 Subject: [PATCH 1/2] feat(routing): implement router merging --- include/lnm/http/route.h | 20 ++++-- src/http/lnm_http_router.c | 143 +++++++++++++++++++++++++++++++++++++ test/routing.c | 49 +++++++++++++ 3 files changed, 208 insertions(+), 4 deletions(-) diff --git a/include/lnm/http/route.h b/include/lnm/http/route.h index f121b96..2d87a1b 100644 --- a/include/lnm/http/route.h +++ b/include/lnm/http/route.h @@ -49,11 +49,23 @@ lnm_err lnm_http_router_add(lnm_http_route **out, lnm_http_router *http_router, lnm_http_method method, const char *path); /** - * Add all of the child router's routes to the parent router, under the given - * route prefix. + * Checks whether the two routers have any conflicting parts. */ -lnm_err lnm_http_router_nest(lnm_http_router *parent, - const lnm_http_router *child, const char *prefix); +bool lnm_http_router_conflicts(const lnm_http_router *r1, + const lnm_http_router *r2); + +/** + * Merge two routers, with the result ending up in r1. This is equivalent to + * nesting a router on '/'. + */ +lnm_err lnm_http_router_merge(lnm_http_router *r1, lnm_http_router *r2); + +/** + * Integrate the child router into the parent routing, mounting its paths on the + * given prefix. + */ +lnm_err lnm_http_router_nest(lnm_http_router *parent, lnm_http_router *child, + const char *prefix); lnm_http_route_err lnm_http_router_route(lnm_http_route_match *out, const lnm_http_router *router, diff --git a/src/http/lnm_http_router.c b/src/http/lnm_http_router.c index ce15d9a..552369c 100644 --- a/src/http/lnm_http_router.c +++ b/src/http/lnm_http_router.c @@ -28,6 +28,7 @@ void lnm_http_router_free(lnm_http_router *router) { for (size_t i = 0; i < lnm_http_method_total; i++) { lnm_http_route_free(router->routes[i]); + lnm_http_route_free(router->multi_segment_routes[i]); } free(router); @@ -287,3 +288,145 @@ lnm_http_route_match_get(lnm_http_route_match *match, const char *key) { return &match->key_segments[trie->index]; } + +bool lnm_http_router_conflicts(const lnm_http_router *r1, + const lnm_http_router *r2) { + // First check the literal routes for the routers + for (lnm_http_method m = 0; m < lnm_http_method_total; m++) { + if ((r1->routes[m] != NULL && r2->routes[m] != NULL) || + (r1->multi_segment_routes[m] != NULL && + r2->multi_segment_routes[m] != NULL)) { + return true; + } + } + + if ((r1->single_segment_child != NULL && r2->single_segment_child != NULL) && + lnm_http_router_conflicts(r1->single_segment_child, + r2->single_segment_child)) { + return true; + } + + for (unsigned char c = 0; c < 128; c++) { + if ((r1->exact_children[c] != NULL && r2->exact_children[c] != NULL) && + lnm_http_router_conflicts(r1->exact_children[c], + r2->exact_children[c])) { + return true; + } + } + + return false; +} + +void __lnm_http_router_merge(lnm_http_router *r1, lnm_http_router *r2) { + for (lnm_http_method m = 0; m < lnm_http_method_total; m++) { + if (r2->routes[m] != NULL) { + r1->routes[m] = r2->routes[m]; + } + + if (r2->multi_segment_routes[m] != NULL) { + r1->multi_segment_routes[m] = r2->multi_segment_routes[m]; + } + } + + if (r1->single_segment_child != NULL && r2->single_segment_child != NULL) { + __lnm_http_router_merge(r1->single_segment_child, r2->single_segment_child); + } else if (r2->single_segment_child != NULL) { + r1->single_segment_child = r2->single_segment_child; + } + + for (unsigned char c = 0; c < 128; c++) { + if (r1->exact_children[c] != NULL && r2->exact_children[c] != NULL) { + __lnm_http_router_merge(r1->exact_children[c], r2->exact_children[c]); + } else if (r2->exact_children[c] != NULL) { + r1->exact_children[c] = r2->exact_children[c]; + } + } + + r1->represents_route = r1->represents_route || r2->represents_route; + + free(r2); +} + +lnm_err lnm_http_router_merge(lnm_http_router *r1, lnm_http_router *r2) { + if (lnm_http_router_conflicts(r1, r2)) { + return lnm_err_overlapping_route; + } + + __lnm_http_router_merge(r1, r2); + + return lnm_err_ok; +} + +lnm_err lnm_http_router_nest(lnm_http_router *parent, lnm_http_router *child, + const char *prefix) { + size_t prefix_len = strlen(prefix); + + if (!is_ascii(prefix) || prefix[0] != '/' || prefix[prefix_len - 1] == '/') { + return lnm_err_invalid_route; + } + + lnm_http_router *merge_target = parent; + + // The child router's routes are also mounted on '/', so we stop one earlier + // as we need to merge it with the router representing the '/' + while (*(prefix + 1) != '\0') { + unsigned char c = *prefix; + + if (merge_target->exact_children[c] == NULL) { + LNM_RES(lnm_http_router_init(&merge_target->exact_children[c])); + } + + merge_target = merge_target->exact_children[c]; + prefix++; + } + + // We check whether there are any conflicts between the child and the existing + // router + bool conflicting = merge_target->single_segment_child != NULL && + child->single_segment_child != NULL; + + for (lnm_http_method m = 0; !conflicting && m < lnm_http_method_total; m++) { + conflicting = + conflicting || + (merge_target->routes[m] != NULL && child->routes[m] != NULL) || + (merge_target->multi_segment_routes[m] != NULL && + child->multi_segment_routes[m] != NULL); + } + + for (unsigned char c = 0; !conflicting && c < 128; c++) { + conflicting = conflicting || (merge_target->exact_children[c] != NULL && + child->exact_children[c] != NULL); + } + + if (conflicting) { + return lnm_err_overlapping_route; + } + + // Merge routers + merge_target->represents_route = + merge_target->represents_route || child->represents_route; + + if (child->single_segment_child != NULL) { + merge_target->single_segment_child = child->single_segment_child; + } + + for (lnm_http_method m = 0; m < lnm_http_method_total; m++) { + if (child->routes[m] != NULL) { + merge_target->routes[m] = child->routes[m]; + } + + if (child->multi_segment_routes[m] != NULL) { + merge_target->multi_segment_routes[m] = child->multi_segment_routes[m]; + } + } + + for (unsigned char c = 0; c < 128; c++) { + if (child->exact_children[c] != NULL) { + merge_target->exact_children[c] = child->exact_children[c]; + } + } + + free(child); + + return lnm_err_ok; +} diff --git a/test/routing.c b/test/routing.c index e133f4b..b87a243 100644 --- a/test/routing.c +++ b/test/routing.c @@ -1,3 +1,5 @@ +#include "lnm/common.h" +#include "lnm/http/route.h" #include "test.h" #include "lnm/http/loop.h" @@ -51,10 +53,57 @@ void test_routing_star() { TEST_CHECK(lnm_http_router_route(&match, router, lnm_http_method_get, "/hello/world") == lnm_http_route_err_match); TEST_CHECK(match.key_segments[0].start == 1); TEST_CHECK(match.key_segments[0].len == 11); + + lnm_http_router_free(router); +} + +void test_routing_merge() { + lnm_http_router *rtr1, *rtr2; + lnm_http_route *rt1, *rt2; + lnm_http_route_match match; + + TEST_CHECK(lnm_http_router_init(&rtr1) == lnm_err_ok); + TEST_CHECK(lnm_http_router_init(&rtr2) == lnm_err_ok); + + TEST_CHECK(lnm_http_router_add(&rt1, rtr1, lnm_http_method_get, "/*key") == lnm_err_ok); + TEST_CHECK(lnm_http_router_add(NULL, rtr1, lnm_http_method_get, "/:key/hello") == lnm_err_ok); + TEST_CHECK(lnm_http_router_add(&rt2, rtr2, lnm_http_method_get, "/test2") == lnm_err_ok); + TEST_CHECK(lnm_http_router_add(NULL, rtr2, lnm_http_method_get, "/:key/hello2") == lnm_err_ok); + + TEST_CHECK(lnm_http_router_merge(rtr1, rtr2) == lnm_err_ok); + + TEST_CHECK(lnm_http_router_route(&match, rtr1, lnm_http_method_get, "/some/thing") == lnm_http_route_err_match); + TEST_CHECK(match.route == rt1); + + TEST_CHECK(lnm_http_router_route(&match, rtr1, lnm_http_method_get, "/test2") == lnm_http_route_err_match); + TEST_CHECK(match.route == rt2); +} + +void test_routing_nest() { + lnm_http_router *r1; + TEST_CHECK(lnm_http_router_init(&r1) == lnm_err_ok); + + TEST_CHECK(lnm_http_router_add(NULL, r1, lnm_http_method_get, "/*key") == lnm_err_ok); + + lnm_http_router *r2; + TEST_CHECK(lnm_http_router_init(&r2) == lnm_err_ok); + TEST_CHECK(lnm_http_router_add(NULL, r2, lnm_http_method_get, "/test/test2") == lnm_err_ok); + + TEST_CHECK(lnm_http_router_nest(r2, r1, "/test") == lnm_err_ok); + + lnm_http_route_match match; + TEST_CHECK(lnm_http_router_route(&match, r2, lnm_http_method_get, "/test/test_var/secondvar") == lnm_http_route_err_match); + TEST_CHECK(match.key_segments[0].start == 6); + TEST_CHECK(match.key_segments[0].len == 18); + + TEST_CHECK(lnm_http_router_route(&match, r2, lnm_http_method_get, "/test/test2") == lnm_http_route_err_match); + } TEST_LIST = { { "routing simple", test_routing_simple }, { "routing star", test_routing_star }, + { "routing merge", test_routing_merge }, + /* { "routing nest", test_routing_nest }, */ { NULL, NULL } }; From cf4451740f24f6a1a9fcff50fcd8dd7c089a7ca8 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Sat, 2 Mar 2024 11:26:51 +0100 Subject: [PATCH 2/2] feat(routing): implement router nesting --- src/http/lnm_http_router.c | 64 +++++--------------------------------- test/routing.c | 11 +++---- 2 files changed, 12 insertions(+), 63 deletions(-) diff --git a/src/http/lnm_http_router.c b/src/http/lnm_http_router.c index 552369c..fec6c2e 100644 --- a/src/http/lnm_http_router.c +++ b/src/http/lnm_http_router.c @@ -359,74 +359,24 @@ lnm_err lnm_http_router_merge(lnm_http_router *r1, lnm_http_router *r2) { lnm_err lnm_http_router_nest(lnm_http_router *parent, lnm_http_router *child, const char *prefix) { - size_t prefix_len = strlen(prefix); - - if (!is_ascii(prefix) || prefix[0] != '/' || prefix[prefix_len - 1] == '/') { + if (!is_ascii(prefix) || prefix[0] != '/') { return lnm_err_invalid_route; } - lnm_http_router *merge_target = parent; + lnm_http_router *router = parent; // The child router's routes are also mounted on '/', so we stop one earlier // as we need to merge it with the router representing the '/' - while (*(prefix + 1) != '\0') { + while (*prefix != '\0') { unsigned char c = *prefix; - if (merge_target->exact_children[c] == NULL) { - LNM_RES(lnm_http_router_init(&merge_target->exact_children[c])); + if (router->exact_children[c] == NULL) { + LNM_RES(lnm_http_router_init(&router->exact_children[c])); } - merge_target = merge_target->exact_children[c]; + router = router->exact_children[c]; prefix++; } - // We check whether there are any conflicts between the child and the existing - // router - bool conflicting = merge_target->single_segment_child != NULL && - child->single_segment_child != NULL; - - for (lnm_http_method m = 0; !conflicting && m < lnm_http_method_total; m++) { - conflicting = - conflicting || - (merge_target->routes[m] != NULL && child->routes[m] != NULL) || - (merge_target->multi_segment_routes[m] != NULL && - child->multi_segment_routes[m] != NULL); - } - - for (unsigned char c = 0; !conflicting && c < 128; c++) { - conflicting = conflicting || (merge_target->exact_children[c] != NULL && - child->exact_children[c] != NULL); - } - - if (conflicting) { - return lnm_err_overlapping_route; - } - - // Merge routers - merge_target->represents_route = - merge_target->represents_route || child->represents_route; - - if (child->single_segment_child != NULL) { - merge_target->single_segment_child = child->single_segment_child; - } - - for (lnm_http_method m = 0; m < lnm_http_method_total; m++) { - if (child->routes[m] != NULL) { - merge_target->routes[m] = child->routes[m]; - } - - if (child->multi_segment_routes[m] != NULL) { - merge_target->multi_segment_routes[m] = child->multi_segment_routes[m]; - } - } - - for (unsigned char c = 0; c < 128; c++) { - if (child->exact_children[c] != NULL) { - merge_target->exact_children[c] = child->exact_children[c]; - } - } - - free(child); - - return lnm_err_ok; + return lnm_http_router_merge(router, child); } diff --git a/test/routing.c b/test/routing.c index b87a243..805a856 100644 --- a/test/routing.c +++ b/test/routing.c @@ -80,18 +80,17 @@ void test_routing_merge() { } void test_routing_nest() { - lnm_http_router *r1; + lnm_http_router *r1, *r2; + lnm_http_route_match match; + TEST_CHECK(lnm_http_router_init(&r1) == lnm_err_ok); + TEST_CHECK(lnm_http_router_init(&r2) == lnm_err_ok); TEST_CHECK(lnm_http_router_add(NULL, r1, lnm_http_method_get, "/*key") == lnm_err_ok); - - lnm_http_router *r2; - TEST_CHECK(lnm_http_router_init(&r2) == lnm_err_ok); TEST_CHECK(lnm_http_router_add(NULL, r2, lnm_http_method_get, "/test/test2") == lnm_err_ok); TEST_CHECK(lnm_http_router_nest(r2, r1, "/test") == lnm_err_ok); - lnm_http_route_match match; TEST_CHECK(lnm_http_router_route(&match, r2, lnm_http_method_get, "/test/test_var/secondvar") == lnm_http_route_err_match); TEST_CHECK(match.key_segments[0].start == 6); TEST_CHECK(match.key_segments[0].len == 18); @@ -104,6 +103,6 @@ TEST_LIST = { { "routing simple", test_routing_simple }, { "routing star", test_routing_star }, { "routing merge", test_routing_merge }, - /* { "routing nest", test_routing_nest }, */ + { "routing nest", test_routing_nest }, { NULL, NULL } };