From 711eaa2bde0a8eabdd297e3b9ed31f1a65792ee3 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Fri, 3 Nov 2023 14:10:14 +0100 Subject: [PATCH] feat(lander): initial integration of lsm --- include/lander.h | 21 +++++++- src/lander/lander.c | 24 ++++++--- src/lander/lander_get.c | 82 ++++++++++++++++++++++++++++ src/lander/lander_post.c | 114 +++++++++++++++++++++++++++++++++++++++ src/main.c | 5 ++ 5 files changed, 238 insertions(+), 8 deletions(-) diff --git a/include/lander.h b/include/lander.h index f44870b..0579a65 100644 --- a/include/lander.h +++ b/include/lander.h @@ -2,18 +2,27 @@ #define LANDER #include "http_loop.h" +#include "lsm/store.h" extern http_route lander_routes[4]; typedef struct lander_gctx { const char *data_dir; Trie *trie; + lsm_store *store; + } lander_gctx; typedef struct lander_ctx { - char *key; + lsm_entry_handle *entry; + uint64_t remaining_data; } lander_ctx; +typedef enum lander_entry_type { + lander_entry_type_redirect = 0, + lander_entry_type_paste = 1, +} lander_entry_type; + void *lander_gctx_init(); void *lander_ctx_init(); @@ -30,4 +39,14 @@ bool lander_post_redirect(event_loop_conn *conn); bool lander_post_paste(event_loop_conn *conn); +bool lander_post_paste_lsm(event_loop_conn *conn); + +bool lander_post_redirect_lsm(event_loop_conn *conn); + +bool lander_stream_body_to_entry(event_loop_conn *conn); + +bool lander_stream_body_to_client(event_loop_conn *conn); + +bool lander_get_entry_lsm(event_loop_conn *conn); + #endif diff --git a/src/lander/lander.c b/src/lander/lander.c index 5a86f76..c4c4ca7 100644 --- a/src/lander/lander.c +++ b/src/lander/lander.c @@ -1,6 +1,8 @@ #include +#include "http_loop.h" #include "lander.h" +#include "lsm/store.h" http_route lander_routes[] = { {.type = http_route_literal, @@ -13,24 +15,24 @@ http_route lander_routes[] = { .type = http_route_regex, .method = http_get, .path = "^/([^/]+)$", - .steps = {lander_get_entry, NULL}, - .steps_res = {http_loop_step_write_header, http_loop_step_write_body, + .steps = {lander_get_entry_lsm, NULL}, + .steps_res = {http_loop_step_write_header, lander_stream_body_to_client, NULL}, }, { .type = http_route_regex, .method = http_post, .path = "^/s(l?)/([^/]*)$", - .steps = {http_loop_step_auth, http_loop_step_body_to_buf, - lander_post_redirect, NULL}, + .steps = {http_loop_step_auth, http_loop_step_parse_content_length, + lander_post_redirect_lsm, lander_stream_body_to_entry, NULL}, .steps_res = {http_loop_step_write_header, http_loop_step_write_body, NULL}, }, {.type = http_route_regex, .method = http_post, .path = "^/p(l?)/([^/]*)$", - .steps = {http_loop_step_auth, lander_post_paste, - http_loop_step_body_to_file, http_loop_step_switch_res, NULL}, + .steps = {http_loop_step_auth, http_loop_step_parse_content_length, + lander_post_paste_lsm, lander_stream_body_to_entry, NULL}, .steps_res = {http_loop_step_write_header, http_loop_step_write_body, NULL}}, }; @@ -39,6 +41,14 @@ void *lander_gctx_init() { return calloc(1, sizeof(lander_gctx)); } void *lander_ctx_init() { return calloc(1, sizeof(lander_ctx)); } -void lander_ctx_reset(lander_ctx *ctx) {} +void lander_ctx_reset(lander_ctx *ctx) { + if (ctx->entry != NULL) { + lsm_entry_close(ctx->entry); + + ctx->entry = NULL; + } + + ctx->remaining_data = 0; +} void lander_ctx_free(lander_ctx *ctx) { free(ctx); } diff --git a/src/lander/lander_get.c b/src/lander/lander_get.c index b139d09..d2fee1e 100644 --- a/src/lander/lander_get.c +++ b/src/lander/lander_get.c @@ -1,6 +1,9 @@ #include +#include "event_loop.h" +#include "http/types.h" #include "lander.h" +#include "lsm/store.h" static const char index_page[] = "\n" @@ -50,3 +53,82 @@ bool lander_get_entry(event_loop_conn *conn) { return true; } + +bool lander_get_entry_lsm(event_loop_conn *conn) { + http_loop_ctx *ctx = conn->ctx; + lander_ctx *c_ctx = ctx->c; + http_loop_gctx *gctx = ctx->g; + lander_gctx *c_gctx = gctx->c; + + const char *key_s = &ctx->req.path[ctx->req.regex_groups[1].rm_so]; + int key_len = ctx->req.regex_groups[1].rm_eo - ctx->req.regex_groups[1].rm_so; + + lsm_str *key; + lsm_str_init_copy_n(&key, (char *)key_s, key_len); + + switch (lsm_store_open_read(&c_ctx->entry, c_gctx->store, key)) { + case lsm_error_ok: + break; + case lsm_error_not_found: + ctx->res.status = http_not_found; + return true; + default: + ctx->res.status = http_internal_server_error; + return true; + } + + lander_entry_type t; + lsm_entry_attr_get_num((uint64_t *)&t, c_ctx->entry, + lsm_attr_type_entry_type); + + if (t == lander_entry_type_redirect) { + // Stream entire redirect data into buffer to set as header + uint64_t data_len = lsm_entry_data_len(c_ctx->entry); + char *buf = malloc(data_len + 1); + uint64_t read = 0; + uint64_t total_read = 0; + + while (total_read < data_len) { + lsm_entry_data_read(&read, &buf[total_read], c_ctx->entry, + data_len - total_read); + total_read += read; + } + + buf[data_len] = '\0'; + + ctx->res.status = http_moved_permanently; + http_res_add_header(&ctx->res, http_header_location, buf, true); + + // We no longer need the entry at this point, so we can unlock it early + // This will also signal to the response code not to read any data from + // the entry + lsm_entry_close(c_ctx->entry); + c_ctx->entry = NULL; + } else { + ctx->res.body.expected_len = lsm_entry_data_len(c_ctx->entry); + } + + return true; +} + +bool lander_stream_body_to_client(event_loop_conn *conn) { + http_loop_ctx *ctx = conn->ctx; + lander_ctx *c_ctx = ctx->c; + + if ((c_ctx->entry == NULL) || + (ctx->res.body.expected_len == ctx->res.body.len)) { + return true; + } + + uint64_t to_write = MIN(EVENT_LOOP_BUFFER_SIZE - conn->wbuf_size, + ctx->res.body.expected_len - ctx->res.body.len); + + uint64_t read = 0; + lsm_entry_data_read(&read, (char *)&conn->wbuf[conn->wbuf_size], c_ctx->entry, + to_write); + + ctx->res.body.len += read; + conn->wbuf_size += read; + + return false; +} diff --git a/src/lander/lander_post.c b/src/lander/lander_post.c index 9288929..da9d1c4 100644 --- a/src/lander/lander_post.c +++ b/src/lander/lander_post.c @@ -1,6 +1,16 @@ #include "http/res.h" +#include "http/types.h" #include "lander.h" #include "log.h" +#include "lsm/store.h" + +static void randomize_key(char *key, int len) { + for (int i = 0; i < len; i++) { + key[i] = charset[rand() % charset_len]; + } + + key[len] = '\0'; +} // TODO entry leaks if key is already present static bool add_entry(char **key_ptr, int *key_len_ptr, http_loop_ctx *ctx, @@ -60,6 +70,110 @@ static bool add_entry(char **key_ptr, int *key_len_ptr, http_loop_ctx *ctx, return true; } +/** + * Insert a new entry into the store. + * + * @return true on success, false otherwise + */ +bool lander_insert_entry(http_loop_ctx *ctx) { + http_loop_gctx *gctx = ctx->g; + lander_gctx *c_gctx = gctx->c; + lander_ctx *c_ctx = ctx->c; + + lsm_str *key; + int key_len; + + if (ctx->req.regex_groups[2].rm_eo == ctx->req.regex_groups[2].rm_so) { + // Generate a random key to insert + bool secure = + (ctx->req.regex_groups[1].rm_eo - ctx->req.regex_groups[1].rm_so) == 1; + key_len = secure ? 16 : 4; + char *key_s = malloc((key_len + 1) * sizeof(char)); + + randomize_key(key_s, key_len); + lsm_str_init(&key, key_s); + } else { + char *key_s = (char *)&ctx->req.path[ctx->req.regex_groups[2].rm_so]; + key_len = ctx->req.regex_groups[2].rm_eo - ctx->req.regex_groups[2].rm_so; + + lsm_str_init_copy_n(&key, key_s, key_len); + } + + // TODO free key on error + switch (lsm_store_insert(&c_ctx->entry, c_gctx->store, key)) { + case lsm_error_already_present: + ctx->res.status = http_conflict; + return false; + case lsm_error_ok: + break; + default: + ctx->res.status = http_internal_server_error; + return false; + } + + // Add location header + char *buf = malloc(key_len + 2); + memcpy(&buf[1], lsm_str_ptr(key), key_len); + buf[0] = '/'; + buf[key_len + 1] = '\0'; + + http_res_add_header(&ctx->res, http_header_location, buf, true); + ctx->res.status = http_created; + + return true; +} + +bool lander_post_redirect_lsm(event_loop_conn *conn) { + http_loop_ctx *ctx = conn->ctx; + lander_ctx *c_ctx = ctx->c; + + if (!lander_insert_entry(ctx)) { + conn->state = event_loop_conn_state_res; + return true; + } + + lsm_entry_attr_insert_num(c_ctx->entry, lsm_attr_type_entry_type, + lander_entry_type_redirect); + + return true; +} + +bool lander_post_paste_lsm(event_loop_conn *conn) { + http_loop_ctx *ctx = conn->ctx; + lander_ctx *c_ctx = ctx->c; + + if (!lander_insert_entry(ctx)) { + conn->state = event_loop_conn_state_res; + return true; + } + + lsm_entry_attr_insert_num(c_ctx->entry, lsm_attr_type_entry_type, + lander_entry_type_paste); + + return true; +} + +bool lander_stream_body_to_entry(event_loop_conn *conn) { + http_loop_ctx *ctx = conn->ctx; + lander_ctx *c_ctx = ctx->c; + http_loop_gctx *gctx = ctx->g; + lander_gctx *c_gctx = gctx->c; + + uint64_t to_append = + MIN(conn->rbuf_size - conn->rbuf_read, + ctx->req.body.expected_len - lsm_entry_data_len(c_ctx->entry)); + + lsm_str *data; + lsm_str_init_copy_n(&data, (char *)&conn->rbuf[conn->rbuf_read], to_append); + lsm_entry_data_append(c_gctx->store, c_ctx->entry, data); + + conn->rbuf_read += to_append; + + lsm_str_free(data); + + return lsm_entry_data_len(c_ctx->entry) == ctx->req.body.expected_len; +} + bool lander_post_redirect(event_loop_conn *conn) { http_loop_ctx *ctx = conn->ctx; bool random = diff --git a/src/main.c b/src/main.c index cbefd01..6d69baf 100644 --- a/src/main.c +++ b/src/main.c @@ -49,6 +49,11 @@ int main() { c_gctx->data_dir = data_dir; c_gctx->trie = trie; + lsm_str *db_path, *data_dir2; + lsm_str_init_copy(&db_path, "data/store.db"); + lsm_str_init_copy(&data_dir2, "data"); + lsm_store_load(&c_gctx->store, db_path, data_dir2); + 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,