feat(lander): initial lnm integration test
ci/woodpecker/push/build Pipeline was successful Details

lnm
Jef Roosens 2023-12-02 20:22:05 +01:00
parent 799821d9fc
commit 8ec667af3b
Signed by: Jef Roosens
GPG Key ID: B75D4F293C7052DB
15 changed files with 187 additions and 44 deletions

View File

@ -39,7 +39,11 @@ objs: $(OBJS)
liblsm: liblsm:
$(MAKE) -C lsm $(MAKE) -C lsm
$(BIN): liblsm $(OBJS) .PHONY: liblnm
liblnm:
$(MAKE) -C lnm
$(BIN): liblsm liblnm $(OBJS)
$(CC) -o $@ $(OBJS) $(_LDFLAGS) $(CC) -o $@ $(OBJS) $(_LDFLAGS)
$(BUILD_DIR)/$(SRC_DIR)/%.c.o: $(SRC_DIR)/%.c $(BUILD_DIR)/$(SRC_DIR)/%.c.o: $(SRC_DIR)/%.c
@ -68,7 +72,7 @@ run: $(BIN)
valgrind: $(BIN) valgrind: $(BIN)
LANDER_API_KEY=test \ LANDER_API_KEY=test \
LANDER_DATA_DIR=data \ LANDER_DATA_DIR=data \
valgrind '$(BUILD_DIR)/$(BIN_FILENAME)' valgrind --track-origins=yes '$(BUILD_DIR)/$(BIN_FILENAME)'
.PHONY: test .PHONY: test
test: $(TARGETS_TEST) test: $(TARGETS_TEST)

View File

@ -7,9 +7,9 @@ SRC_DIR = src
TEST_DIR = test TEST_DIR = test
THIRDPARTY_DIR = thirdparty THIRDPARTY_DIR = thirdparty
INC_DIRS = include $(THIRDPARTY_DIR)/include lsm/include INC_DIRS = include $(THIRDPARTY_DIR)/include lsm/include lnm/include
LIBS = m lsm LIBS = m lsm lnm
LIB_DIRS = ./lsm/build LIB_DIRS = ./lsm/build ./lnm/build
# -MMD: generate a .d file for every source file. This file can be imported by # -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 # make and makes make aware that a header file has been changed, ensuring an

View File

@ -1,9 +1,12 @@
#ifndef LANDER #ifndef LANDER
#define LANDER #define LANDER
#include "http_loop.h" #include "lnm/common.h"
#include "lnm/http/loop.h"
#include "lsm/store.h" #include "lsm/store.h"
#include "http_loop.h"
extern http_route lander_routes[6]; extern http_route lander_routes[6];
extern const char lander_key_charset[]; extern const char lander_key_charset[];
@ -31,13 +34,13 @@ typedef enum lander_entry_type : uint8_t {
void *lander_gctx_init(); 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_reset(lander_ctx *ctx);
void lander_ctx_free(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); bool lander_get_entry(event_loop_conn *conn);

View File

@ -79,4 +79,11 @@ uint64_t lnm_ipow(uint64_t base, uint64_t power);
*/ */
uint64_t lnm_atoi(const char *s, size_t len); 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 #endif

View File

@ -7,11 +7,11 @@ extern const char *lnm_http_method_names[];
extern const size_t lnm_http_method_names_len; extern const size_t lnm_http_method_names_len;
typedef enum lnm_http_method { typedef enum lnm_http_method {
http_method_get = 0, lnm_http_method_get = 0,
http_method_post, lnm_http_method_post,
http_method_put, lnm_http_method_put,
http_method_patch, lnm_http_method_patch,
http_method_delete lnm_http_method_delete
} lnm_http_method; } lnm_http_method;
extern const char *lnm_http_status_names[][32]; extern const char *lnm_http_status_names[][32];

View File

@ -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_reset_fn ctx_reset,
lnm_http_ctx_free_fn ctx_free); 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. * Append the given step fn to the step.
* *
* @param out where to store pointer to new `lnm_http_step` * @param out where to store pointer to new `lnm_http_step`
* @param step step to append new step to * @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_err lnm_http_step_append(lnm_http_step **out, lnm_http_step *step,
lnm_http_step_fn fn); 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 hl HTTP loop to modify
* @param route route to add * @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. * 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, lnm_http_loop_state_parse_headers,
// Execute the various steps defined for the route // Execute the various steps defined for the route
lnm_http_loop_state_steps, lnm_http_loop_state_steps,
// Add certain automatically added headers
lnm_http_loop_state_add_headers,
// Write the response status line // Write the response status line
lnm_http_loop_state_write_status_line, lnm_http_loop_state_write_status_line,
// Write the various response headers // Write the various response headers

View File

@ -45,12 +45,12 @@ typedef struct lnm_http_res {
FILE *f; FILE *f;
data_fn fn; data_fn fn;
} data; } data;
size_t len; uint64_t len;
bool owned; bool owned;
lnm_http_res_body_type type; lnm_http_res_body_type type;
} body; } body;
// General-purpose; meaning depends on the current state // General-purpose; meaning depends on the current state
size_t written; uint64_t written;
} lnm_http_res; } lnm_http_res;
/** /**

View File

@ -20,6 +20,9 @@ lnm_err lnm_http_loop_init(lnm_http_loop **out, void *c_gctx,
free(hl)); free(hl));
hl->data_read = lnm_http_loop_process; 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; *out = hl;
return lnm_err_ok; 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_http_step_fn fn) {
LNM_RES(lnm_http_step_init(out, fn)); LNM_RES(lnm_http_step_init(out, fn));
step->next = *out; if (step != NULL) {
step->next = *out;
}
return lnm_err_ok; 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; 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);
}

View File

@ -12,7 +12,7 @@
/* static const lnm_http_loop_state lnm_http_loop_state_first_req = /* static const lnm_http_loop_state lnm_http_loop_state_first_req =
* lnm_http_loop_state_parse_req; */ * lnm_http_loop_state_parse_req; */
static const lnm_http_loop_state lnm_http_loop_state_first_res = 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) { void lnm_http_loop_process_parse_req(lnm_http_conn *conn) {
lnm_http_loop_ctx *ctx = conn->ctx; lnm_http_loop_ctx *ctx = conn->ctx;
@ -65,6 +65,7 @@ void lnm_http_loop_process_route(lnm_http_conn *conn) {
matched_path = matched_path =
regexec(route->route.regex, ctx->req.path.s, regexec(route->route.regex, ctx->req.path.s,
LNM_HTTP_MAX_REGEX_GROUPS, ctx->req.path.groups, 0) == 0; LNM_HTTP_MAX_REGEX_GROUPS, ctx->req.path.groups, 0) == 0;
break;
} }
// Remember the previous match levels // 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) { void lnm_http_loop_process_steps(lnm_http_conn *conn) {
lnm_http_loop_ctx *ctx = conn->ctx; 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; step = ctx->cur_step;
switch (step->fn(conn)) { switch (step->fn(conn)) {
@ -124,16 +128,34 @@ void lnm_http_loop_process_steps(lnm_http_conn *conn) {
break; 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) { 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 // This function is intentionally written inefficiently for now, as it will most
// likely only have to run once for each response // likely only have to run once for each response
void lnm_http_loop_process_write_status_line(lnm_http_conn *conn) { 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 ctx->state = ctx->res.body.len > 0 ? lnm_http_loop_state_write_body
: lnm_http_loop_state_finish; : 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_route,
lnm_http_loop_process_parse_headers, lnm_http_loop_process_parse_headers,
lnm_http_loop_process_steps, lnm_http_loop_process_steps,
lnm_http_loop_state_process_add_headers,
lnm_http_loop_process_write_status_line, lnm_http_loop_process_write_status_line,
lnm_http_loop_process_write_headers, lnm_http_loop_process_write_headers,
lnm_http_loop_process_write_body, lnm_http_loop_process_write_body,
@ -273,6 +302,8 @@ lnm_loop_state state_map[] = {
lnm_loop_state_req, lnm_loop_state_req,
// steps // steps
lnm_loop_state_req, lnm_loop_state_req,
// add_headers
lnm_loop_state_req,
// write_status_line // write_status_line
lnm_loop_state_res, lnm_loop_state_res,
// write_headers // write_headers
@ -301,9 +332,10 @@ void lnm_http_loop_process(lnm_http_conn *conn) {
process_fns[http_loop_state](conn); process_fns[http_loop_state](conn);
} while ((conn->state == loop_state) && } while ((conn->state == loop_state) &&
(conn->state == state_map[loop_state]) && (conn->state == state_map[http_loop_state]) &&
(http_loop_state != ctx->state)); (http_loop_state != ctx->state));
// Check required to prevent overwriting manually set event loop 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;
} }

View File

@ -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; 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, '?'); char *question_mark = strchr(path, '?');
// Only store query if the path doesn't simply end with a question mark // Only store query if the path doesn't simply end with a question mark

View File

@ -43,3 +43,15 @@ uint64_t lnm_atoi(const char *s, size_t len) {
return res; return res;
} }
uint64_t lnm_digits(uint64_t num) {
int digits = 1;
while (num > 9) {
digits++;
num /= 10;
}
return digits;
}

View File

@ -1,6 +1,7 @@
#include <fcntl.h> #include <fcntl.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <poll.h> #include <poll.h>
#include <stdio.h>
#include <unistd.h> #include <unistd.h>
#include "lnm/common.h" #include "lnm/common.h"
@ -43,7 +44,9 @@ lnm_err lnm_loop_accept(lnm_loop *l) {
// Append connection to list of connections // Append connection to list of connections
if ((size_t)conn_fd >= l->conns.len) { if ((size_t)conn_fd >= l->conns.len) {
lnm_loop_conn **new = 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) { if (new == NULL) {
close(conn_fd); close(conn_fd);

View File

@ -1,10 +1,12 @@
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include "lnm/common.h"
#include "lsm/store.h"
#include "http/types.h" #include "http/types.h"
#include "http_loop.h" #include "http_loop.h"
#include "lander.h" #include "lander.h"
#include "lsm/store.h"
const char lander_key_charset[] = const char lander_key_charset[] =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
@ -60,7 +62,17 @@ http_route lander_routes[] = {
void *lander_gctx_init() { return calloc(1, sizeof(lander_gctx)); } 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) { void lander_ctx_reset(lander_ctx *ctx) {
if (ctx->entry != NULL) { if (ctx->entry != NULL) {

View File

@ -1,5 +1,7 @@
#include <stdio.h> #include <stdio.h>
#include "lnm/loop.h"
#include "event_loop.h" #include "event_loop.h"
#include "http/res.h" #include "http/res.h"
#include "http/types.h" #include "http/types.h"
@ -17,14 +19,18 @@ static const char index_page[] =
" </body>\n" " </body>\n"
"</html>\n"; "</html>\n";
bool lander_get_index(event_loop_conn *conn) { lnm_http_step_err lander_get_index(lnm_http_conn *conn) {
http_loop_ctx *ctx = conn->ctx; lnm_http_loop_ctx *ctx = conn->ctx;
http_res_set_body_buf(&ctx->res, index_page, sizeof(index_page) - 1, false); lnm_http_res_body_set_buf(&ctx->res, (char *)index_page,
http_res_set_mime_type(&ctx->res, http_mime_html); sizeof(index_page) - 1, false);
conn->state = event_loop_conn_state_res; /* http_res_set_body_buf(&ctx->res, index_page, sizeof(index_page) - 1,
return true; * 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) { void lander_get_redirect(event_loop_conn *conn) {

View File

@ -2,9 +2,26 @@
#include <stdlib.h> #include <stdlib.h>
#include <time.h> #include <time.h>
#include "lnm/http/loop.h"
#include "lander.h" #include "lander.h"
#include "log.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) \ #define ENV(var, env_var) \
const char *var = getenv(env_var); \ const char *var = getenv(env_var); \
if (var == NULL) { \ if (var == NULL) { \
@ -44,12 +61,15 @@ int main() {
} }
info("Store loaded containing %lu entries", lsm_store_size(c_gctx->store)); 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( /* http_loop *hl = http_loop_init( */
lander_routes, sizeof(lander_routes) / sizeof(lander_routes[0]), c_gctx, /* lander_routes, sizeof(lander_routes) / sizeof(lander_routes[0]),
lander_ctx_init, (void (*)(void *))lander_ctx_reset, * c_gctx, */
(void (*)(void *))lander_ctx_free); /* lander_ctx_init, (void (*)(void *))lander_ctx_reset, */
http_loop_set_api_key(hl, api_key); /* (void (*)(void *))lander_ctx_free); */
/* http_loop_set_api_key(hl, api_key); */
http_loop_run(hl, port); /* http_loop_run(hl, port); */
} }