http-server.c

Basic HTTP server and router in C
git clone git://git.finwo.net/lib/http-server.c
Log | Files | Refs | README

commit 0d8748e79d0f5a30d1f6c4d5b63bdeaeeebba8be
parent 1c5d6a861e00c1e02c8236d225f6a1cb233d9a1b
Author: Yersa Nordman <yersa@finwo.nl>
Date:   Sun, 18 Feb 2024 23:08:40 +0100

Add support for dynamic routes with named params

Diffstat:
Mexample.c | 21+++++++++++++++++++--
Msrc/http-server.c | 79++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
2 files changed, 89 insertions(+), 11 deletions(-)

diff --git a/example.c b/example.c @@ -48,6 +48,22 @@ void route_get_hello(struct http_server_reqdata *reqdata) { return; } +void route_get_hello_named(struct http_server_reqdata *reqdata) { + http_parser_header_set(reqdata->reqres->response, "Content-Type", "text/plain"); + + const char *name = http_parser_tag_get(reqdata->reqres->request, "param:name"); + if (!name) name = "there"; + + reqdata->reqres->response->body = calloc(1, sizeof(struct buf)); + reqdata->reqres->response->body->data = calloc(7 + strlen(name), sizeof(char)); + strcat(reqdata->reqres->response->body->data, "Hello "); + strcat(reqdata->reqres->response->body->data, name); + reqdata->reqres->response->body->len = strlen(reqdata->reqres->response->body->data); + http_server_response_send(reqdata, true); + countDown = countDownOrg; + return; +} + void route_post_port(struct http_server_reqdata *reqdata) { http_parser_header_set(reqdata->reqres->response, "Content-Type", "text/plain"); targetPort = atoi(reqdata->reqres->request->body->data); @@ -82,8 +98,9 @@ int main() { .udata = &opts, }; - http_server_route("GET" , "/hello", route_get_hello); - http_server_route("POST", "/port" , route_post_port); + http_server_route("GET" , "/hello" , route_get_hello); + http_server_route("POST", "/port" , route_post_port); + http_server_route("GET" , "/hello/:name", route_get_hello_named); /* // Launch network management thread */ /* thd_thread thread; */ diff --git a/src/http-server.c b/src/http-server.c @@ -38,12 +38,27 @@ struct fnet_udata { struct hs_route { void *next; const char *method; - const char *path; + char **path; void (*fn)(struct http_server_reqdata*); }; struct hs_route *registered_routes = NULL; +char ** _hs_pathTokens(const char *path) { + char **output = calloc(strlen(path), sizeof(char*)); + + int token_count = 0; + char *dupped = strdup(path); + char *rest = dupped; + char *token; + while((token = strtok_r(rest, "/", &rest))) { + output[token_count++] = strdup(token); + } + free(dupped); + + return output; +} + void _hs_onServing(struct fnet_ev *ev) { struct fnet_udata *ludata = ev->udata; @@ -65,15 +80,61 @@ static void _hs_onRequest(struct http_parser_event *ev) { struct hs_route *route = registered_routes; struct hs_route *selected_route = NULL; - // Method/path matching, should be more intricate later (like /posts/:postId/comments) + // Tokenize the given path only once + char **pathTokens = _hs_pathTokens(ev->request->path); + char **routeTokens; + char *tag = calloc(strlen(ev->request->path), sizeof(char)); + int i; + + // Method/path matching while(route) { - if ( - (!strcmp(ev->request->method, route->method)) && - (!strcmp(ev->request->path , route->path )) - ) { - selected_route = route; + + // Skip route if the method doesn't match + if (strcmp(ev->request->method, route->method)) { + route = route->next; + continue; // Continues while(route) + } + + // Checking if the path matches + routeTokens = route->path; + i = 0; + + while((pathTokens[i] && routeTokens[i])) { + if (routeTokens[i][0] == ':') { i++; continue; } + if (strcmp(pathTokens[i], routeTokens[i])) { + i = -1; + break; // Breaks token-checking + } + i++; + } + + // Content mismatch + if (i == -1) { + route = route->next; + continue; // Continues while(route) + } + + // Length mismatch + if (pathTokens[i] || routeTokens[i]) { + route = route->next; + continue; + } + + // Here = route match + + // Store url params as tag 'param:<name>' = 'path[i] + i = 0; + while((pathTokens[i] && routeTokens[i])) { + if (routeTokens[i][0] != ':') { i++; continue; } + tag[0] = '\0'; + strcat(tag, "param:"); + strcat(tag, routeTokens[i]+1); + http_parser_tag_set(ev->request, tag, pathTokens[i]); + i++; } - route = route->next; + + selected_route = route; + break; } if (!selected_route) { @@ -138,8 +199,8 @@ void http_server_route(const char *method, const char *path, void (*fn)(struct h struct hs_route *route = malloc(sizeof(struct hs_route)); route->next = registered_routes; route->method = method; - route->path = path; route->fn = fn; + route->path = _hs_pathTokens(path); registered_routes = route; }