From 464753d627d15f32f0fc4c43984294afef19c0a1 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Mon, 29 May 2023 16:55:16 +0200 Subject: [PATCH] feat: allow matching regex routes --- include/http.h | 3 +++ include/http_loop.h | 2 ++ src/http_loop/http_loop.c | 30 +++++++++++++++++++++++- src/http_loop/http_loop_req.c | 44 ++++++++++++++++++++++++++--------- src/main.c | 20 ++++++++++++++-- 5 files changed, 85 insertions(+), 14 deletions(-) diff --git a/include/http.h b/include/http.h index a5a82a5..f20678f 100644 --- a/include/http.h +++ b/include/http.h @@ -1,12 +1,14 @@ #ifndef HTTP #define HTTP +#include #include #include #include "picohttpparser.h" #define HTTP_MAX_ALLOWED_HEADERS 16 +#define HTTP_MAX_REGEX_GROUPS 4 extern const char *http_response_type_names[5][32]; extern const char *http_header_names[]; @@ -33,6 +35,7 @@ typedef struct http_request { size_t path_len; const char *query; size_t query_len; + regmatch_t regex_groups[HTTP_MAX_REGEX_GROUPS]; struct phr_header headers[HTTP_MAX_ALLOWED_HEADERS]; } http_request; diff --git a/include/http_loop.h b/include/http_loop.h index f3344bb..eee2f6c 100644 --- a/include/http_loop.h +++ b/include/http_loop.h @@ -105,4 +105,6 @@ void http_loop_res_add_header(http_loop_ctx *ctx, http_header type, */ event_loop *http_loop_init(http_loop_gctx *gctx); +void http_loop_run(event_loop *el, int port); + #endif diff --git a/src/http_loop/http_loop.c b/src/http_loop/http_loop.c index 1120e75..ba1131c 100644 --- a/src/http_loop/http_loop.c +++ b/src/http_loop/http_loop.c @@ -1,5 +1,8 @@ -#include "http_loop.h" +#include + #include "http.h" +#include "http_loop.h" +#include "log.h" bool http_loop_handle_request(event_loop_conn *conn) { // Prevents the request handler function from looping indefinitely without @@ -54,3 +57,28 @@ event_loop *http_loop_init(http_loop_gctx *gctx) { return el; } + +void http_loop_run(event_loop *el, int port) { + debug("Compiling RegEx routes"); + + http_loop_gctx *gctx = el->gctx; + http_route *route; + + for (size_t i = 0; i < gctx->route_count; i++) { + route = &gctx->routes[i]; + + if (route->type == http_route_regex) { + regex_t *r = calloc(sizeof(regex_t), 1); + + if (regcomp(r, route->path, 0) != 0) { + critical(1, "RegEx expression '%s' failed to compile", route->path); + } + + route->regex = r; + } + } + + debug("RegEx routes compiled successfully"); + + event_loop_run(el, port); +} diff --git a/src/http_loop/http_loop_req.c b/src/http_loop/http_loop_req.c index 3c6ac6e..a09adeb 100644 --- a/src/http_loop/http_loop_req.c +++ b/src/http_loop/http_loop_req.c @@ -12,11 +12,14 @@ http_parse_error http_loop_parse_request(event_loop_conn *conn) { const char *method; size_t method_len; + char *path; + size_t path_len; - int res = phr_parse_request( - (const char *)&conn->rbuf[conn->rbuf_read], - conn->rbuf_size - conn->rbuf_read, &method, &method_len, &req->path, - &req->path_len, &req->minor_version, req->headers, &num_headers, 0); + int res = + phr_parse_request((const char *)&conn->rbuf[conn->rbuf_read], + conn->rbuf_size - conn->rbuf_read, &method, &method_len, + (const char **)&path, &path_len, &req->minor_version, + req->headers, &num_headers, 0); if (res == -1) { return http_parse_error_invalid; @@ -45,16 +48,16 @@ http_parse_error http_loop_parse_request(event_loop_conn *conn) { i = 0; bool no_query = true; - while (no_query && i < req->path_len) { - if (req->path[i] == '?') { + while (no_query && i < path_len) { + if (path[i] == '?') { // Ensure we don't store an invalid pointer if the request simply ends // with '?' if (i + 1 < req->path_len) { - req->query = &req->path[i + 1]; - req->query_len = req->path_len - (i + 1); + req->query = &path[i + 1]; + req->query_len = path_len - (i + 1); } - req->path_len = i; + path_len = i; no_query = false; } @@ -62,6 +65,15 @@ http_parse_error http_loop_parse_request(event_loop_conn *conn) { i++; } + // The path needs to be NULL-terminated in order for regex routes to be + // matched properly. We know we can overwrite this char because it's either + // '?' if there's a query, or '\n' because we know the buf contains a valid + // HTTP rqeuest + path[path_len] = '\0'; + + req->path = path; + req->path_len = path_len; + // Ensure we clear the old request's query if (no_query) { req->query = NULL; @@ -80,6 +92,7 @@ void http_loop_route_request(event_loop_conn *conn) { http_route *route; bool path_matched = false; + char c; for (size_t i = 0; i < gctx->route_count; i++) { route = &gctx->routes[i]; @@ -95,8 +108,17 @@ void http_loop_route_request(event_loop_conn *conn) { } } break; - // TODO - case http_route_regex:; + case http_route_regex: + if (regexec(route->regex, ctx->req.path, HTTP_MAX_REGEX_GROUPS, + ctx->req.regex_groups, 0) == 0) { + path_matched = true; + + if (ctx->req.method == route->method) { + ctx->route = route; + return; + } + } + break; } } diff --git a/src/main.c b/src/main.c index c490d08..d96a0b3 100644 --- a/src/main.c +++ b/src/main.c @@ -25,10 +25,26 @@ bool lander_get_index(event_loop_conn *conn) { return true; } +bool lander_get_entry(event_loop_conn *conn) { + http_loop_ctx *ctx = conn->ctx; + + size_t match_len = + ctx->req.regex_groups[1].rm_eo - ctx->req.regex_groups[1].rm_so; + info("matched: %.*s", match_len, + &ctx->req.path[ctx->req.regex_groups[1].rm_so]); + conn->state = event_loop_conn_state_res; + + return true; +} + http_route routes[] = {{.type = http_route_literal, .method = http_get, .path = "/", - .steps = {lander_get_index, NULL}}}; + .steps = {lander_get_index, NULL}}, + {.type = http_route_regex, + .method = http_get, + .path = "^/\\([^/]\\+\\)$", + .steps = {lander_get_entry, NULL}}}; int main() { setvbuf(stdout, NULL, _IONBF, 0); @@ -50,5 +66,5 @@ int main() { gctx->route_count = sizeof(routes) / sizeof(routes[0]); event_loop *el = http_loop_init(gctx); - event_loop_run(el, 8000); + http_loop_run(el, 8000); }