diff --git a/include/lnm/http/consts.h b/include/lnm/http/consts.h index f543bbb..d3c9b97 100644 --- a/include/lnm/http/consts.h +++ b/include/lnm/http/consts.h @@ -13,6 +13,7 @@ typedef enum lnm_http_method { lnm_http_method_patch, lnm_http_method_delete, lnm_http_method_head, + lnm_http_method_total, } lnm_http_method; extern const char *lnm_http_status_names[][32]; diff --git a/include/lnm/http/router.h b/include/lnm/http/router.h new file mode 100644 index 0000000..7b7d56b --- /dev/null +++ b/include/lnm/http/router.h @@ -0,0 +1,39 @@ +#ifndef LNM_HTTP_ROUTER +#define LNM_HTTP_ROUTER + +#include "lnm/common.h" +#include "lnm/http/consts.h" + +typedef struct lnm_http_route lnm_http_route; + +typedef struct lnm_http_router lnm_http_router; + +typedef enum lnm_http_route_err { + lnm_http_route_err_match = 0, + lnm_http_route_err_unknown_route = 1, + lnm_http_route_err_unknown_method = 2, +} lnm_http_route_err; + +/** + * Allocate and initialize a new http_router. + */ +lnm_err lnm_http_router_init(lnm_http_router **out); + +void lnm_http_router_free(lnm_http_router *router); + +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. + */ +lnm_err lnm_http_router_nest(lnm_http_router *parent, + const lnm_http_router *child, const char *prefix); + +lnm_http_route_err lnm_http_router_route(const lnm_http_route **out, + const lnm_http_router *router, + lnm_http_method method, + const char *path); + +#endif diff --git a/src/_include/lnm/http/router_internal.h b/src/_include/lnm/http/router_internal.h index e4f6bda..a2504fa 100644 --- a/src/_include/lnm/http/router_internal.h +++ b/src/_include/lnm/http/router_internal.h @@ -2,35 +2,19 @@ #define LNM_HTTP_ROUTER_INTERNAL #include "lnm/common.h" +#include "lnm/http/consts.h" +#include "lnm/http/router.h" -typedef struct lnm_http_route { -} lnm_http_route; +struct lnm_http_route {}; -typedef struct lnm_http_router { +struct lnm_http_router { struct lnm_http_router *exact_children[128]; struct lnm_http_router *single_segment_child; - lnm_http_route *route; -} lnm_http_router; - -/** - * Allocate and initialize a new http_router. - */ -lnm_err lnm_http_router_init(lnm_http_router **out); - -void lnm_http_router_free(lnm_http_router *router); + lnm_http_route *routes[lnm_http_method_total]; +}; lnm_err lnm_http_route_init(lnm_http_route **out); void lnm_http_route_free(lnm_http_route *route); -lnm_err lnm_http_router_add(lnm_http_route **out, lnm_http_router *http_router, - const char *path); - -/** - * Add all of the child router's routes to the parent router, under the given - * route prefix. - */ -lnm_err lnm_http_router_nest(lnm_http_router *parent, - const lnm_http_router *child, const char *prefix); - #endif diff --git a/src/http/lnm_http_router.c b/src/http/lnm_http_router.c index f5001f8..6dee3c8 100644 --- a/src/http/lnm_http_router.c +++ b/src/http/lnm_http_router.c @@ -1,6 +1,7 @@ #include #include "lnm/common.h" +#include "lnm/http/router.h" #include "lnm/http/router_internal.h" lnm_err lnm_http_router_init(lnm_http_router **out) { @@ -40,7 +41,7 @@ static bool is_ascii(const char *s) { } lnm_err lnm_http_router_add(lnm_http_route **out, lnm_http_router *http_router, - const char *path) { + lnm_http_method method, const char *path) { if (path[0] != '/' || !is_ascii(path)) { return lnm_err_invalid_route; } @@ -51,6 +52,7 @@ lnm_err lnm_http_router_add(lnm_http_route **out, lnm_http_router *http_router, switch (c) { case ':': LNM_RES(lnm_http_router_init(&http_router->single_segment_child)); + http_router = http_router->single_segment_child; // All other characters in the segment are ignored const char *next_slash_ptr = strchr(path, '/'); @@ -67,7 +69,60 @@ lnm_err lnm_http_router_add(lnm_http_route **out, lnm_http_router *http_router, } } - LNM_RES(lnm_http_route_init(out)); + if (http_router->routes[method] != NULL) { + return lnm_err_overlapping_route; + } + + LNM_RES(lnm_http_route_init(&http_router->routes[method])); + *out = http_router->routes[method]; return lnm_err_ok; } + +lnm_http_route_err lnm_http_router_route(const lnm_http_route **out, + const lnm_http_router *router, + lnm_http_method method, + const char *path) { + if (!is_ascii(path)) { + return lnm_http_route_err_unknown_route; + } + + if (*path == '\0') { + *out = router->routes[method]; + + return *out == NULL ? lnm_http_route_err_unknown_method + : lnm_http_route_err_match; + } + + lnm_http_route_err res = lnm_http_route_err_unknown_route; + lnm_http_router *exact_router = router->exact_children[(unsigned char)*path]; + + if (exact_router != NULL) { + lnm_http_route_err sub_res = + lnm_http_router_route(out, exact_router, method, path + 1); + + if (sub_res == lnm_http_route_err_match) { + return lnm_http_route_err_match; + } + + res = LNM_MAX(res, sub_res); + } + + lnm_http_router *single_segment_router = router->single_segment_child; + + if (single_segment_router != NULL) { + const char *next_slash_ptr = strchr(path, '/'); + path = next_slash_ptr == NULL ? strchr(path, '\0') : next_slash_ptr; + + lnm_http_route_err sub_res = + lnm_http_router_route(out, exact_router, method, path); + + if (sub_res == lnm_http_route_err_match) { + return lnm_http_route_err_match; + } + + res = LNM_MAX(res, sub_res); + } + + return res; +}