From 8ec667af3b0eb2773d15fbae356552e8ba0f60b8 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Sat, 2 Dec 2023 20:22:05 +0100 Subject: [PATCH] feat(lander): initial lnm integration test --- Makefile | 8 +++-- config.mk | 6 ++-- include/lander.h | 9 +++-- lnm/include/lnm/common.h | 7 ++++ lnm/include/lnm/http/consts.h | 10 +++--- lnm/include/lnm/http/loop.h | 16 +++++++-- lnm/include/lnm/http/res.h | 4 +-- lnm/src/http/lnm_http_loop.c | 32 ++++++++++++++++- lnm/src/http/lnm_http_loop_process.c | 54 ++++++++++++++++++++++------ lnm/src/http/lnm_http_req.c | 2 ++ lnm/src/lnm_utils.c | 12 +++++++ lnm/src/loop/lnm_loop.c | 5 ++- src/lander/lander.c | 16 +++++++-- src/lander/lander_get.c | 18 ++++++---- src/main.c | 32 +++++++++++++---- 15 files changed, 187 insertions(+), 44 deletions(-) diff --git a/Makefile b/Makefile index f2c448c..d6d9364 100644 --- a/Makefile +++ b/Makefile @@ -39,7 +39,11 @@ objs: $(OBJS) liblsm: $(MAKE) -C lsm -$(BIN): liblsm $(OBJS) +.PHONY: liblnm +liblnm: + $(MAKE) -C lnm + +$(BIN): liblsm liblnm $(OBJS) $(CC) -o $@ $(OBJS) $(_LDFLAGS) $(BUILD_DIR)/$(SRC_DIR)/%.c.o: $(SRC_DIR)/%.c @@ -68,7 +72,7 @@ run: $(BIN) valgrind: $(BIN) LANDER_API_KEY=test \ LANDER_DATA_DIR=data \ - valgrind '$(BUILD_DIR)/$(BIN_FILENAME)' + valgrind --track-origins=yes '$(BUILD_DIR)/$(BIN_FILENAME)' .PHONY: test test: $(TARGETS_TEST) diff --git a/config.mk b/config.mk index da3e0b8..244a7b2 100644 --- a/config.mk +++ b/config.mk @@ -7,9 +7,9 @@ SRC_DIR = src TEST_DIR = test THIRDPARTY_DIR = thirdparty -INC_DIRS = include $(THIRDPARTY_DIR)/include lsm/include -LIBS = m lsm -LIB_DIRS = ./lsm/build +INC_DIRS = include $(THIRDPARTY_DIR)/include lsm/include lnm/include +LIBS = m lsm lnm +LIB_DIRS = ./lsm/build ./lnm/build # -MMD: generate a .d file for every source file. This file can be imported by # make and makes make aware that a header file has been changed, ensuring an diff --git a/include/lander.h b/include/lander.h index a30c32d..7220559 100644 --- a/include/lander.h +++ b/include/lander.h @@ -1,9 +1,12 @@ #ifndef LANDER #define LANDER -#include "http_loop.h" +#include "lnm/common.h" +#include "lnm/http/loop.h" #include "lsm/store.h" +#include "http_loop.h" + extern http_route lander_routes[6]; extern const char lander_key_charset[]; @@ -31,13 +34,13 @@ typedef enum lander_entry_type : uint8_t { void *lander_gctx_init(); -void *lander_ctx_init(); +lnm_err lander_ctx_init(void **c_ctx, void *gctx); void lander_ctx_reset(lander_ctx *ctx); void lander_ctx_free(lander_ctx *ctx); -bool lander_get_index(event_loop_conn *conn); +lnm_http_step_err lander_get_index(lnm_http_conn *conn); bool lander_get_entry(event_loop_conn *conn); diff --git a/lnm/include/lnm/common.h b/lnm/include/lnm/common.h index 9c95d52..8cc982e 100644 --- a/lnm/include/lnm/common.h +++ b/lnm/include/lnm/common.h @@ -79,4 +79,11 @@ uint64_t lnm_ipow(uint64_t base, uint64_t power); */ uint64_t lnm_atoi(const char *s, size_t len); +/** + * Calculate how many base 10 digits the given number consists of. + * + * @param num number to use + */ +uint64_t lnm_digits(uint64_t num); + #endif diff --git a/lnm/include/lnm/http/consts.h b/lnm/include/lnm/http/consts.h index 37f3239..89622a3 100644 --- a/lnm/include/lnm/http/consts.h +++ b/lnm/include/lnm/http/consts.h @@ -7,11 +7,11 @@ extern const char *lnm_http_method_names[]; extern const size_t lnm_http_method_names_len; typedef enum lnm_http_method { - http_method_get = 0, - http_method_post, - http_method_put, - http_method_patch, - http_method_delete + lnm_http_method_get = 0, + lnm_http_method_post, + lnm_http_method_put, + lnm_http_method_patch, + lnm_http_method_delete } lnm_http_method; extern const char *lnm_http_status_names[][32]; diff --git a/lnm/include/lnm/http/loop.h b/lnm/include/lnm/http/loop.h index 12ff602..482560d 100644 --- a/lnm/include/lnm/http/loop.h +++ b/lnm/include/lnm/http/loop.h @@ -32,12 +32,20 @@ lnm_err lnm_http_loop_init(lnm_http_loop **out, void *c_gctx, lnm_http_ctx_reset_fn ctx_reset, lnm_http_ctx_free_fn ctx_free); +/** + * Initialize a new step. + * + * @param out where to store pointer to new `lnm_http_step` + * @param fn step function + */ +lnm_err lnm_http_step_init(lnm_http_step **out, lnm_http_step_fn fn); + /** * Append the given step fn to the step. * * @param out where to store pointer to new `lnm_http_step` * @param step step to append new step to - * @param fn step funcitonn + * @param fn step function */ lnm_err lnm_http_step_append(lnm_http_step **out, lnm_http_step *step, lnm_http_step_fn fn); @@ -71,7 +79,9 @@ lnm_err lnm_http_route_init_regex(lnm_http_route **out, lnm_http_method method, * @param hl HTTP loop to modify * @param route route to add */ -void lnm_http_loop_route_add(lnm_http_loop *hl, lnm_http_route *route); +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); /** * Represents what state an HTTP loop request is currently in. @@ -85,6 +95,8 @@ typedef enum lnm_http_loop_state { lnm_http_loop_state_parse_headers, // Execute the various steps defined for the route lnm_http_loop_state_steps, + // Add certain automatically added headers + lnm_http_loop_state_add_headers, // Write the response status line lnm_http_loop_state_write_status_line, // Write the various response headers diff --git a/lnm/include/lnm/http/res.h b/lnm/include/lnm/http/res.h index 4364597..b34f5da 100644 --- a/lnm/include/lnm/http/res.h +++ b/lnm/include/lnm/http/res.h @@ -45,12 +45,12 @@ typedef struct lnm_http_res { FILE *f; data_fn fn; } data; - size_t len; + uint64_t len; bool owned; lnm_http_res_body_type type; } body; // General-purpose; meaning depends on the current state - size_t written; + uint64_t written; } lnm_http_res; /** diff --git a/lnm/src/http/lnm_http_loop.c b/lnm/src/http/lnm_http_loop.c index e79d3c3..6cd7d53 100644 --- a/lnm/src/http/lnm_http_loop.c +++ b/lnm/src/http/lnm_http_loop.c @@ -20,6 +20,9 @@ lnm_err lnm_http_loop_init(lnm_http_loop **out, void *c_gctx, free(hl)); hl->data_read = lnm_http_loop_process; + hl->data_write = lnm_http_loop_process; + hl->ctx_init = (lnm_err(*)(void **, void *))lnm_http_loop_ctx_init; + hl->ctx_free = (void (*)(void *))lnm_http_loop_ctx_free; *out = hl; return lnm_err_ok; @@ -42,7 +45,9 @@ lnm_err lnm_http_step_append(lnm_http_step **out, lnm_http_step *step, lnm_http_step_fn fn) { LNM_RES(lnm_http_step_init(out, fn)); - step->next = *out; + if (step != NULL) { + step->next = *out; + } return lnm_err_ok; } @@ -96,3 +101,28 @@ lnm_err lnm_http_route_init_regex(lnm_http_route **out, lnm_http_method method, 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_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_RES(lnm_loop_setup(hl, port)); + return lnm_loop_run(hl); +} diff --git a/lnm/src/http/lnm_http_loop_process.c b/lnm/src/http/lnm_http_loop_process.c index 7fb2cf9..32bc840 100644 --- a/lnm/src/http/lnm_http_loop_process.c +++ b/lnm/src/http/lnm_http_loop_process.c @@ -12,7 +12,7 @@ /* static const lnm_http_loop_state lnm_http_loop_state_first_req = * lnm_http_loop_state_parse_req; */ static const lnm_http_loop_state lnm_http_loop_state_first_res = - lnm_http_loop_state_write_headers; + lnm_http_loop_state_write_status_line; void lnm_http_loop_process_parse_req(lnm_http_conn *conn) { lnm_http_loop_ctx *ctx = conn->ctx; @@ -65,6 +65,7 @@ void lnm_http_loop_process_route(lnm_http_conn *conn) { matched_path = regexec(route->route.regex, ctx->req.path.s, LNM_HTTP_MAX_REGEX_GROUPS, ctx->req.path.groups, 0) == 0; + break; } // Remember the previous match levels @@ -105,9 +106,12 @@ void lnm_http_loop_process_parse_headers(lnm_http_conn *conn) { void lnm_http_loop_process_steps(lnm_http_conn *conn) { lnm_http_loop_ctx *ctx = conn->ctx; - lnm_http_step *step; + lnm_http_step *step = NULL; - do { + // Loop until we either: + // - reach the end of the chain of steps, indicated by NULL + // - have a step that's waiting for I/O + while ((ctx->cur_step != NULL) && (step != ctx->cur_step)) { step = ctx->cur_step; switch (step->fn(conn)) { @@ -124,16 +128,34 @@ void lnm_http_loop_process_steps(lnm_http_conn *conn) { break; } } - // Loop until we either: - // - reach the end of the chain of steps, indicated by NULL - // - have a step that's waiting for I/O - while ((ctx->cur_step != NULL) && (step != ctx->cur_step)); if (ctx->cur_step == NULL) { - ctx->state = lnm_http_loop_state_write_headers; + ctx->state = lnm_http_loop_state_add_headers; } } +void lnm_http_loop_state_process_add_headers(lnm_http_conn *conn) { + lnm_http_loop_ctx *ctx = conn->ctx; + lnm_http_res *res = &ctx->res; + + if (res->body.len > 0) { + uint64_t digits = lnm_digits(res->body.len); + char *buf = malloc(digits + 1); + + if (buf == NULL) { + conn->state = lnm_loop_state_end; + + return; + } + + sprintf(buf, "%lu", res->body.len); + lnm_http_res_add_header_len(res, lnm_http_header_content_length, buf, + digits, true); + } + + ctx->state = lnm_http_loop_state_write_status_line; +} + // This function is intentionally written inefficiently for now, as it will most // likely only have to run once for each response void lnm_http_loop_process_write_status_line(lnm_http_conn *conn) { @@ -199,7 +221,13 @@ void lnm_http_loop_process_write_headers(lnm_http_conn *conn) { } } - if (res->headers.current == NULL) { + // The headers should end with an additional newline. If there's no space left + // in the write buffer, we don't switch states so we can re-try this write + // later + if ((res->headers.current == NULL) && (conn->w.size < LNM_LOOP_BUF_SIZE)) { + conn->w.buf[conn->w.size] = '\n'; + conn->w.size++; + ctx->state = ctx->res.body.len > 0 ? lnm_http_loop_state_write_body : lnm_http_loop_state_finish; } @@ -258,6 +286,7 @@ void (*process_fns[])(lnm_http_conn *conn) = { lnm_http_loop_process_route, lnm_http_loop_process_parse_headers, lnm_http_loop_process_steps, + lnm_http_loop_state_process_add_headers, lnm_http_loop_process_write_status_line, lnm_http_loop_process_write_headers, lnm_http_loop_process_write_body, @@ -273,6 +302,8 @@ lnm_loop_state state_map[] = { lnm_loop_state_req, // steps lnm_loop_state_req, + // add_headers + lnm_loop_state_req, // write_status_line lnm_loop_state_res, // write_headers @@ -301,9 +332,10 @@ void lnm_http_loop_process(lnm_http_conn *conn) { process_fns[http_loop_state](conn); } while ((conn->state == loop_state) && - (conn->state == state_map[loop_state]) && + (conn->state == state_map[http_loop_state]) && (http_loop_state != ctx->state)); // Check required to prevent overwriting manually set event loop state - conn->state = conn->state == loop_state ? state_map[loop_state] : conn->state; + conn->state = + conn->state == loop_state ? state_map[http_loop_state] : conn->state; } diff --git a/lnm/src/http/lnm_http_req.c b/lnm/src/http/lnm_http_req.c index 1588bcd..ba7cccc 100644 --- a/lnm/src/http/lnm_http_req.c +++ b/lnm/src/http/lnm_http_req.c @@ -37,6 +37,8 @@ lnm_http_parse_err lnm_http_req_parse(lnm_http_req *req, char *buf, return lnm_http_parse_err_unknown_method; } + // Path will always end with a newline, which we can safely set to nul + path[path_len] = '\0'; char *question_mark = strchr(path, '?'); // Only store query if the path doesn't simply end with a question mark diff --git a/lnm/src/lnm_utils.c b/lnm/src/lnm_utils.c index ce60803..85094bd 100644 --- a/lnm/src/lnm_utils.c +++ b/lnm/src/lnm_utils.c @@ -43,3 +43,15 @@ uint64_t lnm_atoi(const char *s, size_t len) { return res; } + +uint64_t lnm_digits(uint64_t num) { + int digits = 1; + + while (num > 9) { + digits++; + + num /= 10; + } + + return digits; +} diff --git a/lnm/src/loop/lnm_loop.c b/lnm/src/loop/lnm_loop.c index c550009..f44c829 100644 --- a/lnm/src/loop/lnm_loop.c +++ b/lnm/src/loop/lnm_loop.c @@ -1,6 +1,7 @@ #include #include #include +#include #include #include "lnm/common.h" @@ -43,7 +44,9 @@ lnm_err lnm_loop_accept(lnm_loop *l) { // Append connection to list of connections if ((size_t)conn_fd >= l->conns.len) { lnm_loop_conn **new = - realloc(l->conns.arr, sizeof(lnm_loop_conn *) * (conn_fd + 1)); + l->conns.len == 0 + ? calloc(sizeof(lnm_loop_conn *), conn_fd + 1) + : realloc(l->conns.arr, sizeof(lnm_loop_conn *) * (conn_fd + 1)); if (new == NULL) { close(conn_fd); diff --git a/src/lander/lander.c b/src/lander/lander.c index 5d1c1fe..6d316d2 100644 --- a/src/lander/lander.c +++ b/src/lander/lander.c @@ -1,10 +1,12 @@ #include #include +#include "lnm/common.h" +#include "lsm/store.h" + #include "http/types.h" #include "http_loop.h" #include "lander.h" -#include "lsm/store.h" const char lander_key_charset[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; @@ -60,7 +62,17 @@ http_route lander_routes[] = { void *lander_gctx_init() { return calloc(1, sizeof(lander_gctx)); } -void *lander_ctx_init() { return calloc(1, sizeof(lander_ctx)); } +lnm_err lander_ctx_init(void **c_ctx, void *gctx) { + lander_ctx *ctx = calloc(1, sizeof(lander_ctx)); + + if (ctx == NULL) { + return lnm_err_failed_alloc; + } + + *c_ctx = ctx; + + return lnm_err_ok; +} void lander_ctx_reset(lander_ctx *ctx) { if (ctx->entry != NULL) { diff --git a/src/lander/lander_get.c b/src/lander/lander_get.c index 102c631..3d05c4d 100644 --- a/src/lander/lander_get.c +++ b/src/lander/lander_get.c @@ -1,5 +1,7 @@ #include +#include "lnm/loop.h" + #include "event_loop.h" #include "http/res.h" #include "http/types.h" @@ -17,14 +19,18 @@ static const char index_page[] = " \n" "\n"; -bool lander_get_index(event_loop_conn *conn) { - http_loop_ctx *ctx = conn->ctx; +lnm_http_step_err lander_get_index(lnm_http_conn *conn) { + lnm_http_loop_ctx *ctx = conn->ctx; - http_res_set_body_buf(&ctx->res, index_page, sizeof(index_page) - 1, false); - http_res_set_mime_type(&ctx->res, http_mime_html); + lnm_http_res_body_set_buf(&ctx->res, (char *)index_page, + sizeof(index_page) - 1, false); - conn->state = event_loop_conn_state_res; - return true; + /* http_res_set_body_buf(&ctx->res, index_page, sizeof(index_page) - 1, + * false); */ + /* http_res_set_mime_type(&ctx->res, http_mime_html); */ + + /* conn->state = event_loop_conn_state_res; */ + return lnm_http_step_err_done; } void lander_get_redirect(event_loop_conn *conn) { diff --git a/src/main.c b/src/main.c index 2f52fc1..78ead69 100644 --- a/src/main.c +++ b/src/main.c @@ -2,9 +2,26 @@ #include #include +#include "lnm/http/loop.h" + #include "lander.h" #include "log.h" +lnm_http_loop *loop_init(lander_gctx *gctx) { + lnm_http_loop *hl; + lnm_http_step *step = NULL; + lnm_http_route *route; + lnm_http_loop_init(&hl, gctx, lander_ctx_init, + (lnm_http_ctx_reset_fn)lander_ctx_reset, + (lnm_http_ctx_free_fn)lander_ctx_free); + + lnm_http_step_init(&step, lander_get_index); + lnm_http_route_init_literal(&route, lnm_http_method_get, "/", step); + lnm_http_loop_route_add(hl, route); + + return hl; +} + #define ENV(var, env_var) \ const char *var = getenv(env_var); \ if (var == NULL) { \ @@ -44,12 +61,15 @@ int main() { } info("Store loaded containing %lu entries", lsm_store_size(c_gctx->store)); + lnm_http_loop *hl = loop_init(c_gctx); + lnm_http_loop_run(hl, port); - http_loop *hl = http_loop_init( - lander_routes, sizeof(lander_routes) / sizeof(lander_routes[0]), c_gctx, - lander_ctx_init, (void (*)(void *))lander_ctx_reset, - (void (*)(void *))lander_ctx_free); - http_loop_set_api_key(hl, api_key); + /* http_loop *hl = http_loop_init( */ + /* lander_routes, sizeof(lander_routes) / sizeof(lander_routes[0]), + * c_gctx, */ + /* lander_ctx_init, (void (*)(void *))lander_ctx_reset, */ + /* (void (*)(void *))lander_ctx_free); */ + /* http_loop_set_api_key(hl, api_key); */ - http_loop_run(hl, port); + /* http_loop_run(hl, port); */ }