feat: allow matching regex routes

c-web-server
Jef Roosens 2023-05-29 16:55:16 +02:00
parent dd0ba31506
commit 464753d627
Signed by: Jef Roosens
GPG Key ID: B75D4F293C7052DB
5 changed files with 85 additions and 14 deletions

View File

@ -1,12 +1,14 @@
#ifndef HTTP
#define HTTP
#include <regex.h>
#include <stdbool.h>
#include <stdlib.h>
#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;

View File

@ -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

View File

@ -1,5 +1,8 @@
#include "http_loop.h"
#include <regex.h>
#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);
}

View File

@ -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;
}
}

View File

@ -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);
}