feat(routing): integrate new router into framework

routing
Jef Roosens 2024-02-23 12:15:43 +01:00
parent d739157fb1
commit e29e02ff85
Signed by: Jef Roosens
GPG Key ID: B75D4F293C7052DB
9 changed files with 201 additions and 364 deletions

View File

@ -21,15 +21,19 @@ lnm_http_step_err slow_step(lnm_http_conn *conn) {
int main() { int main() {
lnm_http_loop *hl; lnm_http_loop *hl;
lnm_http_step *step = NULL; lnm_http_step *step = NULL;
lnm_http_route *route;
lnm_http_loop_init(&hl, NULL, ctx_init, lnm_http_loop_init(&hl, NULL, ctx_init,
ctx_reset, ctx_reset,
ctx_free); ctx_free);
lnm_http_step_append(&step, slow_step, true); lnm_http_router *router;
lnm_http_route_init_literal(&route, lnm_http_method_get, "/", step); lnm_http_router_init(&router);
lnm_http_loop_route_add(hl, route);
lnm_http_route *route;
lnm_http_router_add(&route, router, lnm_http_method_get, "/");
lnm_http_route_step_append(route, slow_step, true);
lnm_http_loop_router_set(hl, router);
lnm_log_init_global(); lnm_log_init_global();
lnm_log_register_stdout(lnm_log_level_debug); lnm_log_register_stdout(lnm_log_level_debug);

View File

@ -7,6 +7,8 @@
#include "lnm/http/req.h" #include "lnm/http/req.h"
#include "lnm/http/res.h" #include "lnm/http/res.h"
#define LNM_HTTP_MAX_KEY_SEGMENTS 4
typedef enum lnm_http_step_err { typedef enum lnm_http_step_err {
lnm_http_step_err_done = 0, lnm_http_step_err_done = 0,
lnm_http_step_err_io_needed, lnm_http_step_err_io_needed,
@ -22,6 +24,55 @@ typedef void (*lnm_http_ctx_reset_fn)(void *c_ctx);
typedef void (*lnm_http_ctx_free_fn)(void *c_ctx); typedef void (*lnm_http_ctx_free_fn)(void *c_ctx);
typedef struct lnm_http_route lnm_http_route;
typedef struct lnm_http_route_match_segment {
size_t start;
size_t len;
} lnm_http_route_match_segment;
typedef struct lnm_http_route_match {
const lnm_http_route *route;
lnm_http_method method;
lnm_http_route_match_segment key_segments[LNM_HTTP_MAX_KEY_SEGMENTS];
} lnm_http_route_match;
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(lnm_http_route_match *out,
const lnm_http_router *router,
lnm_http_method method,
const char *path);
const lnm_http_route_match_segment *
lnm_http_route_match_get(lnm_http_route_match *match, const char *key);
lnm_err lnm_http_route_step_append(lnm_http_route *route, lnm_http_step_fn fn,
bool blocking);
/** /**
* Initialize a new `lnm_http_loop`. * Initialize a new `lnm_http_loop`.
* *
@ -32,47 +83,7 @@ lnm_err lnm_http_loop_init(lnm_http_loop **out, void *c_gctx,
lnm_http_ctx_reset_fn ctx_reset, lnm_http_ctx_reset_fn ctx_reset,
lnm_http_ctx_free_fn ctx_free); lnm_http_ctx_free_fn ctx_free);
/** void lnm_http_loop_router_set(lnm_http_loop *hl, lnm_http_router *router);
* Append the given step fn to the step.
*
* @param out both the previous step to append the new step to, and the output
* variable to which the new step is appended
* @param fn step function
* @param blocking whether the step is blocking or not
*/
lnm_err lnm_http_step_append(lnm_http_step **out, lnm_http_step_fn fn,
bool blocking);
/**
* Initialize a new route of type literal.
*
* @param out where to store pointer to new `lnm_http_route`
* @param path literal path to match
* @param step step to process request with
*/
lnm_err lnm_http_route_init_literal(lnm_http_route **out,
lnm_http_method method, const char *path,
lnm_http_step *step);
/**
* Initialize a new route of type regex.
*
* @param out where to store pointer to new `lnm_http_route`
* @param pattern regex pattern
* @param regex_group_count how many regex groups are contained in the pattern
* @param step step to process request with
*/
lnm_err lnm_http_route_init_regex(lnm_http_route **out, lnm_http_method method,
const char *pattern, int regex_group_count,
lnm_http_step *step);
/**
* Add a new route to the HTTP route.
*
* @param hl HTTP loop to modify
* @param route route to add
*/
lnm_err lnm_http_loop_route_add(lnm_http_loop *hl, lnm_http_route *route);
lnm_err lnm_http_loop_run(lnm_http_loop *hl, uint16_t port, lnm_err lnm_http_loop_run(lnm_http_loop *hl, uint16_t port,
size_t epoll_threads, size_t worker_threads); size_t epoll_threads, size_t worker_threads);
@ -106,10 +117,7 @@ typedef enum lnm_http_loop_state {
} lnm_http_loop_state; } lnm_http_loop_state;
typedef struct lnm_http_loop_gctx { typedef struct lnm_http_loop_gctx {
struct { lnm_http_router *router;
lnm_http_route **arr;
size_t len;
} routes;
lnm_http_ctx_init_fn ctx_init; lnm_http_ctx_init_fn ctx_init;
lnm_http_ctx_reset_fn ctx_reset; lnm_http_ctx_reset_fn ctx_reset;
lnm_http_ctx_free_fn ctx_free; lnm_http_ctx_free_fn ctx_free;
@ -122,7 +130,7 @@ typedef struct lnm_http_loop_ctx {
lnm_http_loop_state state; lnm_http_loop_state state;
lnm_http_req req; lnm_http_req req;
lnm_http_res res; lnm_http_res res;
lnm_http_route *route; const lnm_http_route *route;
lnm_http_step *cur_step; lnm_http_step *cur_step;
lnm_http_loop_gctx *g; lnm_http_loop_gctx *g;
void *c; void *c;

View File

@ -1,55 +0,0 @@
#ifndef LNM_HTTP_ROUTER
#define LNM_HTTP_ROUTER
#define LNM_HTTP_MAX_KEY_SEGMENTS 4
#include "lnm/common.h"
#include "lnm/http/consts.h"
typedef struct lnm_http_route lnm_http_route;
typedef struct lnm_http_route_match_segment {
size_t start;
size_t len;
} lnm_http_route_match_segment;
typedef struct lnm_http_route_match {
const lnm_http_route *route;
lnm_http_method method;
lnm_http_route_match_segment key_segments[LNM_HTTP_MAX_KEY_SEGMENTS];
} lnm_http_route_match;
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(lnm_http_route_match *out,
const lnm_http_router *router,
lnm_http_method method,
const char *path);
const lnm_http_route_match_segment *
lnm_http_route_match_get(lnm_http_route_match *match, const char *key);
#endif

View File

@ -5,42 +5,48 @@
#include "lnm/http/loop.h" #include "lnm/http/loop.h"
struct lnm_http_router {
struct lnm_http_router *exact_children[128];
struct lnm_http_router *single_segment_child;
lnm_http_route *routes[lnm_http_method_total];
bool represents_route;
};
typedef struct lnm_http_route_segment_trie {
struct lnm_http_route_segment_trie *children[128];
size_t index;
bool represents_segment;
} lnm_http_route_segment_trie;
lnm_err lnm_http_route_segment_trie_init(lnm_http_route_segment_trie **out);
void lnm_http_route_segment_trie_free(lnm_http_route_segment_trie *trie);
lnm_err lnm_http_route_key_segment_insert(lnm_http_route *route,
const char *key, size_t key_len,
size_t index);
typedef struct lnm_http_step { typedef struct lnm_http_step {
lnm_http_step_fn fn; lnm_http_step_fn fn;
struct lnm_http_step *next; struct lnm_http_step *next;
bool blocking; bool blocking;
} lnm_http_step; } lnm_http_step;
typedef enum lnm_http_route_type {
lnm_http_route_type_literal = 0,
lnm_http_route_type_regex,
} lnm_http_route_type;
typedef struct lnm_http_route {
union {
regex_t *regex;
const char *s;
} route;
lnm_http_method method;
lnm_http_route_type type;
int regex_group_count;
lnm_http_step *step;
} lnm_http_route;
/**
* Initialize a new empty route.
*
* @param out where to store pointer to new `lnm_http_route`
*/
lnm_err lnm_http_route_init(lnm_http_route **out);
/** /**
* Initialize a first step. * Initialize a first step.
* *
* @param out where to store pointer to new `lnm_http_step` * @param out where to store pointer to new `lnm_http_step`
* @param fn step function associated with the step * @param fn step function associated with the step
*/ */
lnm_err lnm_http_step_init(lnm_http_step **out, lnm_http_step_fn fn); lnm_err lnm_http_step_init(lnm_http_step **out);
struct lnm_http_route {
lnm_http_route_segment_trie *key_segments;
lnm_http_step *step;
};
lnm_err lnm_http_route_init(lnm_http_route **out);
void lnm_http_route_free(lnm_http_route *route);
/** /**
* Initialize a new global context object. * Initialize a new global context object.

View File

@ -1,37 +0,0 @@
#ifndef LNM_HTTP_ROUTER_INTERNAL
#define LNM_HTTP_ROUTER_INTERNAL
#include "lnm/common.h"
#include "lnm/http/consts.h"
#include "lnm/http/router.h"
typedef struct lnm_http_route_segment_trie {
struct lnm_http_route_segment_trie *children[128];
size_t index;
bool represents_segment;
} lnm_http_route_segment_trie;
lnm_err lnm_http_route_segment_trie_init(lnm_http_route_segment_trie **out);
void lnm_http_route_segment_trie_free(lnm_http_route_segment_trie *trie);
lnm_err lnm_http_route_key_segment_insert(lnm_http_route *route,
const char *key, size_t key_len,
size_t index);
struct lnm_http_route {
lnm_http_route_segment_trie *key_segments;
};
struct lnm_http_router {
struct lnm_http_router *exact_children[128];
struct lnm_http_router *single_segment_child;
lnm_http_route *routes[lnm_http_method_total];
bool represents_route;
};
lnm_err lnm_http_route_init(lnm_http_route **out);
void lnm_http_route_free(lnm_http_route *route);
#endif

View File

@ -28,94 +28,9 @@ lnm_err lnm_http_loop_init(lnm_http_loop **out, void *c_gctx,
return lnm_err_ok; return lnm_err_ok;
} }
lnm_err lnm_http_step_append(lnm_http_step **out, lnm_http_step_fn fn, void lnm_http_loop_router_set(lnm_http_loop *hl, lnm_http_router *router) {
bool blocking) {
lnm_http_step *step = calloc(1, sizeof(lnm_http_step));
if (step == NULL) {
return lnm_err_failed_alloc;
}
step->fn = fn;
step->blocking = blocking;
if ((*out) != NULL) {
(*out)->next = step;
}
*out = step;
return lnm_err_ok;
}
lnm_err lnm_http_route_init(lnm_http_route **out) {
lnm_http_route *route = calloc(1, sizeof(lnm_http_route));
if (route == NULL) {
return lnm_err_failed_alloc;
}
*out = route;
return lnm_err_ok;
}
lnm_err lnm_http_route_init_literal(lnm_http_route **out,
lnm_http_method method, const char *path,
lnm_http_step *step) {
LNM_RES(lnm_http_route_init(out));
(*out)->type = lnm_http_route_type_literal;
(*out)->method = method;
(*out)->route.s = path;
(*out)->step = step;
return lnm_err_ok;
}
lnm_err lnm_http_route_init_regex(lnm_http_route **out, lnm_http_method method,
const char *pattern, int regex_group_count,
lnm_http_step *step) {
regex_t *regex = calloc(1, sizeof(regex_t));
if (regex == NULL) {
return lnm_err_failed_alloc;
}
if (regcomp(regex, pattern, REG_EXTENDED) != 0) {
free(regex);
return lnm_err_bad_regex;
}
LNM_RES2(lnm_http_route_init(out), free(regex));
(*out)->method = method;
(*out)->type = lnm_http_route_type_regex;
(*out)->route.regex = regex;
(*out)->regex_group_count = regex_group_count;
(*out)->step = step;
return lnm_err_ok;
}
lnm_err lnm_http_loop_route_add(lnm_http_loop *hl, lnm_http_route *route) {
lnm_http_loop_gctx *gctx = hl->gctx; lnm_http_loop_gctx *gctx = hl->gctx;
gctx->router = router;
lnm_http_route **new_routes =
gctx->routes.len > 0
? realloc(gctx->routes.arr,
(gctx->routes.len + 1) * sizeof(lnm_http_route *))
: malloc(sizeof(lnm_http_route *));
if (new_routes == NULL) {
return lnm_err_failed_alloc;
}
new_routes[gctx->routes.len] = route;
gctx->routes.arr = new_routes;
gctx->routes.len++;
return lnm_err_ok;
} }
lnm_err lnm_http_loop_run(lnm_http_loop *hl, uint16_t port, lnm_err lnm_http_loop_run(lnm_http_loop *hl, uint16_t port,

View File

@ -56,55 +56,24 @@ void lnm_http_loop_process_parse_req(lnm_http_conn *conn) {
void lnm_http_loop_process_route(lnm_http_conn *conn) { void lnm_http_loop_process_route(lnm_http_conn *conn) {
lnm_http_loop_ctx *ctx = conn->ctx; lnm_http_loop_ctx *ctx = conn->ctx;
lnm_http_loop_gctx *gctx = ctx->g; const lnm_http_loop_gctx *gctx = ctx->g;
// 0: no match lnm_http_route_match match;
// 1: matched route, but not method
// 2: fully matched route
int match_level = 0;
lnm_http_route *route;
for (size_t i = 0; i < gctx->routes.len && match_level < 3; i++) { switch (lnm_http_router_route(&match, gctx->router, ctx->req.method,
route = gctx->routes.arr[i]; ctx->req.buf.s + ctx->req.path.o)) {
bool matched_path = false; case lnm_http_route_err_match:
ctx->route = match.route;
switch (route->type) { ctx->cur_step = match.route->step;
case lnm_http_route_type_literal: ctx->state = lnm_http_loop_state_parse_headers;
matched_path = strncmp(route->route.s, ctx->req.buf.s + ctx->req.path.o,
ctx->req.path.len) == 0;
break;
case lnm_http_route_type_regex:
matched_path =
regexec(route->route.regex, ctx->req.buf.s + ctx->req.path.o,
LNM_HTTP_MAX_REGEX_GROUPS, ctx->req.path.groups, 0) == 0;
break;
}
// GET routes also automatically route HEAD requests
bool matched_method = route->method == ctx->req.method ||
(route->method == lnm_http_method_get &&
ctx->req.method == lnm_http_method_head);
int new_match_level = 2 * matched_path + matched_method;
// Remember the previous match levels so we can return the correct status
// message
match_level = match_level < new_match_level ? new_match_level : match_level;
}
switch (match_level) {
case 0:
case 1:
ctx->res.status = lnm_http_status_not_found;
ctx->state = lnm_http_loop_state_first_res;
break; break;
case 2: case lnm_http_route_err_unknown_method:
ctx->res.status = lnm_http_status_method_not_allowed; ctx->res.status = lnm_http_status_method_not_allowed;
ctx->state = lnm_http_loop_state_first_res; ctx->state = lnm_http_loop_state_first_res;
break; break;
case 3: case lnm_http_route_err_unknown_route:
ctx->route = route; ctx->res.status = lnm_http_status_not_found;
ctx->cur_step = route->step; ctx->state = lnm_http_loop_state_first_res;
ctx->state = lnm_http_loop_state_parse_headers;
break; break;
} }
} }

View File

@ -0,0 +1,94 @@
#include "lnm/http/loop_internal.h"
lnm_err lnm_http_route_init(lnm_http_route **out) {
lnm_http_route *route = calloc(1, sizeof(lnm_http_route));
if (route == NULL) {
return lnm_err_failed_alloc;
}
*out = route;
return lnm_err_ok;
}
lnm_err lnm_http_route_segment_trie_init(lnm_http_route_segment_trie **out) {
lnm_http_route_segment_trie *trie =
calloc(1, sizeof(lnm_http_route_segment_trie));
if (trie == NULL) {
return lnm_err_failed_alloc;
}
*out = trie;
return lnm_err_ok;
}
void lnm_http_route_segment_trie_free(lnm_http_route_segment_trie *trie) {
if (trie == NULL) {
return;
}
for (size_t i = 0; i < 128; i++) {
lnm_http_route_segment_trie_free(trie->children[i]);
}
free(trie);
}
lnm_err lnm_http_route_key_segment_insert(lnm_http_route *route,
const char *key, size_t key_len,
size_t index) {
if (route->key_segments == NULL) {
LNM_RES(lnm_http_route_segment_trie_init(&route->key_segments));
}
lnm_http_route_segment_trie *trie = route->key_segments;
for (size_t key_index = 0; key_index < key_len; key_index++) {
unsigned char c = key[key_index];
if (trie->children[c] == NULL) {
LNM_RES(lnm_http_route_segment_trie_init(&trie->children[c]));
}
trie = trie->children[c];
}
if (trie->represents_segment) {
return lnm_err_already_present;
}
trie->represents_segment = true;
trie->index = index;
return lnm_err_ok;
}
lnm_err lnm_http_step_init(lnm_http_step **out) {
lnm_http_step *step = calloc(1, sizeof(lnm_http_step));
if (step == NULL) {
return lnm_err_failed_alloc;
}
*out = step;
return lnm_err_ok;
}
lnm_err lnm_http_route_step_append(lnm_http_route *route, lnm_http_step_fn fn,
bool blocking) {
lnm_http_step **step_ptr = &route->step;
while (*step_ptr != NULL) {
step_ptr = &(*step_ptr)->next;
}
LNM_RES(lnm_http_step_init(step_ptr));
(*step_ptr)->fn = fn;
(*step_ptr)->blocking = blocking;
return lnm_err_ok;
}

View File

@ -1,8 +1,7 @@
#include <string.h> #include <string.h>
#include "lnm/common.h" #include "lnm/common.h"
#include "lnm/http/router.h" #include "lnm/http/loop_internal.h"
#include "lnm/http/router_internal.h"
lnm_err lnm_http_router_init(lnm_http_router **out) { lnm_err lnm_http_router_init(lnm_http_router **out) {
lnm_http_router *router = calloc(1, sizeof(lnm_http_router)); lnm_http_router *router = calloc(1, sizeof(lnm_http_router));
@ -16,18 +15,6 @@ lnm_err lnm_http_router_init(lnm_http_router **out) {
return lnm_err_ok; return lnm_err_ok;
} }
lnm_err lnm_http_route_init(lnm_http_route **out) {
lnm_http_route *route = calloc(1, sizeof(lnm_http_route));
if (route == NULL) {
return lnm_err_failed_alloc;
}
*out = route;
return lnm_err_ok;
}
void lnm_http_route_free(lnm_http_route *route) { void lnm_http_route_free(lnm_http_route *route) {
if (route == NULL) { if (route == NULL) {
return; return;
@ -37,60 +24,6 @@ void lnm_http_route_free(lnm_http_route *route) {
free(route); free(route);
} }
lnm_err lnm_http_route_segment_trie_init(lnm_http_route_segment_trie **out) {
lnm_http_route_segment_trie *trie =
calloc(1, sizeof(lnm_http_route_segment_trie));
if (trie == NULL) {
return lnm_err_failed_alloc;
}
*out = trie;
return lnm_err_ok;
}
void lnm_http_route_segment_trie_free(lnm_http_route_segment_trie *trie) {
if (trie == NULL) {
return;
}
for (size_t i = 0; i < 128; i++) {
lnm_http_route_segment_trie_free(trie->children[i]);
}
free(trie);
}
lnm_err lnm_http_route_key_segment_insert(lnm_http_route *route,
const char *key, size_t key_len,
size_t index) {
if (route->key_segments == NULL) {
LNM_RES(lnm_http_route_segment_trie_init(&route->key_segments));
}
lnm_http_route_segment_trie *trie = route->key_segments;
for (size_t key_index = 0; key_index < key_len; key_index++) {
unsigned char c = key[key_index];
if (trie->children[c] == NULL) {
LNM_RES(lnm_http_route_segment_trie_init(&trie->children[c]));
}
trie = trie->children[c];
}
if (trie->represents_segment) {
return lnm_err_already_present;
}
trie->represents_segment = true;
trie->index = index;
return lnm_err_ok;
}
static bool is_ascii(const char *s) { static bool is_ascii(const char *s) {
while (*s != '\0') { while (*s != '\0') {
if (*s < 0) { if (*s < 0) {