refactor(lander): clean up code a bit

lsm
Jef Roosens 2023-11-12 14:48:55 +01:00
parent c026e13c44
commit 64af94ce7a
Signed by: Jef Roosens
GPG Key ID: B75D4F293C7052DB
9 changed files with 103 additions and 457 deletions

View File

@ -7,7 +7,6 @@
#include "http/req.h" #include "http/req.h"
#include "http/res.h" #include "http/res.h"
#include "http/types.h" #include "http/types.h"
#include "trie.h"
// Max amount of steps a route can use // Max amount of steps a route can use
#define HTTP_LOOP_MAX_STEPS 17 #define HTTP_LOOP_MAX_STEPS 17

View File

@ -5,17 +5,15 @@
#include "lsm/store.h" #include "lsm/store.h"
extern http_route lander_routes[6]; extern http_route lander_routes[6];
extern const char lander_key_charset[];
typedef struct lander_gctx { typedef struct lander_gctx {
const char *data_dir; const char *data_dir;
Trie *trie;
lsm_store *store; lsm_store *store;
} lander_gctx; } lander_gctx;
typedef struct lander_ctx { typedef struct lander_ctx {
lsm_entry_handle *entry; lsm_entry_handle *entry;
uint64_t remaining_data;
} lander_ctx; } lander_ctx;
typedef enum lander_attr_type : uint8_t { typedef enum lander_attr_type : uint8_t {
@ -24,6 +22,14 @@ typedef enum lander_attr_type : uint8_t {
lander_attr_type_url = 2, lander_attr_type_url = 2,
} lander_attr_type; } lander_attr_type;
typedef struct {
char *header;
lander_attr_type attr_type;
http_header header_type;
} header_to_attr;
extern header_to_attr header_to_attrs[];
typedef enum lander_entry_type : uint8_t { typedef enum lander_entry_type : uint8_t {
lander_entry_type_redirect = 0, lander_entry_type_redirect = 0,
lander_entry_type_paste = 1, lander_entry_type_paste = 1,
@ -46,21 +52,19 @@ bool lander_post_redirect(event_loop_conn *conn);
bool lander_post_paste(event_loop_conn *conn); bool lander_post_paste(event_loop_conn *conn);
bool lander_post_paste_lsm(event_loop_conn *conn); bool lander_post_paste(event_loop_conn *conn);
bool lander_post_redirect_lsm(event_loop_conn *conn); bool lander_post_redirect(event_loop_conn *conn);
bool lander_stream_body_to_entry(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_stream_body_to_client(event_loop_conn *conn);
bool lander_get_entry_lsm(event_loop_conn *conn);
bool lander_post_redirect_body_to_attr(event_loop_conn *conn); bool lander_post_redirect_body_to_attr(event_loop_conn *conn);
bool lander_remove_entry(event_loop_conn *conn); bool lander_remove_entry(event_loop_conn *conn);
bool lander_post_file_lsm(event_loop_conn *conn); bool lander_post_file(event_loop_conn *conn);
/** /**
* Parse any custom headers and add them as attributes to the context's LSM * Parse any custom headers and add them as attributes to the context's LSM

View File

@ -1,4 +1,5 @@
#include <math.h> #include <math.h>
#include <string.h>
#include "http_loop.h" #include "http_loop.h"
#include "lander.h" #include "lander.h"

View File

@ -5,6 +5,9 @@
#include "lander.h" #include "lander.h"
#include "lsm/store.h" #include "lsm/store.h"
const char lander_key_charset[] =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
http_route lander_routes[] = { http_route lander_routes[] = {
{.type = http_route_literal, {.type = http_route_literal,
.method = http_get, .method = http_get,
@ -16,7 +19,7 @@ http_route lander_routes[] = {
.type = http_route_regex, .type = http_route_regex,
.method = http_get, .method = http_get,
.path = "^/([^/]+)$", .path = "^/([^/]+)$",
.steps = {lander_get_entry_lsm, lander_attrs_to_headers, NULL}, .steps = {lander_get_entry, lander_attrs_to_headers, NULL},
.steps_res = {http_loop_step_write_header, lander_stream_body_to_client, .steps_res = {http_loop_step_write_header, lander_stream_body_to_client,
NULL}, NULL},
}, },
@ -32,7 +35,7 @@ http_route lander_routes[] = {
.type = http_route_regex, .type = http_route_regex,
.method = http_post, .method = http_post,
.path = "^/s(l?)/([^/]*)$", .path = "^/s(l?)/([^/]*)$",
.steps = {http_loop_step_auth, lander_post_redirect_lsm, .steps = {http_loop_step_auth, lander_post_redirect,
http_loop_step_body_to_buf, lander_post_redirect_body_to_attr, http_loop_step_body_to_buf, lander_post_redirect_body_to_attr,
NULL}, NULL},
.steps_res = {http_loop_step_write_header, http_loop_step_write_body, .steps_res = {http_loop_step_write_header, http_loop_step_write_body,
@ -42,27 +45,23 @@ http_route lander_routes[] = {
.method = http_post, .method = http_post,
.path = "^/p(l?)/([^/]*)$", .path = "^/p(l?)/([^/]*)$",
.steps = {http_loop_step_auth, http_loop_step_parse_content_length, .steps = {http_loop_step_auth, http_loop_step_parse_content_length,
lander_post_paste_lsm, lander_stream_body_to_entry, NULL}, lander_post_paste, lander_stream_body_to_entry, NULL},
.steps_res = {http_loop_step_write_header, http_loop_step_write_body, .steps_res = {http_loop_step_write_header, http_loop_step_write_body,
NULL}}, NULL}},
{.type = http_route_regex, {.type = http_route_regex,
.method = http_post, .method = http_post,
.path = "^/f(l?)/([^/]*)$", .path = "^/f(l?)/([^/]*)$",
.steps = {http_loop_step_auth, http_loop_step_parse_content_length, .steps = {http_loop_step_auth, http_loop_step_parse_content_length,
lander_post_file_lsm, lander_headers_to_attrs, lander_post_file, lander_headers_to_attrs,
lander_stream_body_to_entry, NULL}, lander_stream_body_to_entry, NULL},
.steps_res = {http_loop_step_write_header, http_loop_step_write_body, .steps_res = {http_loop_step_write_header, http_loop_step_write_body,
NULL}}, NULL}},
}; };
struct { header_to_attr header_to_attrs[] = {
char *header;
lander_attr_type attr_type;
http_header header_type;
} header_to_attr_type[] = {
{"X-Lander-Content-Type", lander_attr_type_content_type, {"X-Lander-Content-Type", lander_attr_type_content_type,
http_header_content_type}, http_header_content_type},
{NULL, 0}, {NULL, 0, 0},
}; };
void *lander_gctx_init() { return calloc(1, sizeof(lander_gctx)); } void *lander_gctx_init() { return calloc(1, sizeof(lander_gctx)); }
@ -75,60 +74,6 @@ void lander_ctx_reset(lander_ctx *ctx) {
ctx->entry = NULL; ctx->entry = NULL;
} }
ctx->remaining_data = 0;
} }
void lander_ctx_free(lander_ctx *ctx) { free(ctx); } void lander_ctx_free(lander_ctx *ctx) { free(ctx); }
bool lander_headers_to_attrs(event_loop_conn *conn) {
http_loop_ctx *ctx = conn->ctx;
lander_ctx *c_ctx = ctx->c;
for (size_t i = 0; i < ctx->req.num_headers; i++) {
struct phr_header *header = &ctx->req.headers[i];
int j = 0;
while (header_to_attr_type[j].header != NULL) {
if (strncmp(header->name, header_to_attr_type[j].header,
header->name_len) == 0) {
lsm_str *value;
lsm_str_init_copy_n(&value, (char *)header->value, header->value_len);
lsm_entry_attr_insert(c_ctx->entry, header_to_attr_type[j].attr_type,
value);
break;
}
j++;
}
}
return true;
}
bool lander_attrs_to_headers(event_loop_conn *conn) {
http_loop_ctx *ctx = conn->ctx;
lander_ctx *c_ctx = ctx->c;
int j = 0;
lsm_str *value;
while (header_to_attr_type[j].header != NULL) {
if (lsm_entry_attr_get(&value, c_ctx->entry,
header_to_attr_type[j].attr_type) == lsm_error_ok) {
char *buf = malloc(lsm_str_len(value) + 1);
memcpy(buf, lsm_str_ptr(value), lsm_str_len(value));
buf[lsm_str_len(value)] = '\0';
http_res_add_header(&ctx->res, header_to_attr_type[j].header_type, buf,
true);
}
j++;
}
return true;
}

View File

@ -75,7 +75,7 @@ void lander_get_file(event_loop_conn *conn) {
ctx->res.body.expected_len = lsm_entry_data_len(c_ctx->entry); ctx->res.body.expected_len = lsm_entry_data_len(c_ctx->entry);
} }
bool lander_get_entry_lsm(event_loop_conn *conn) { bool lander_get_entry(event_loop_conn *conn) {
http_loop_ctx *ctx = conn->ctx; http_loop_ctx *ctx = conn->ctx;
lander_ctx *c_ctx = ctx->c; lander_ctx *c_ctx = ctx->c;
http_loop_gctx *gctx = ctx->g; http_loop_gctx *gctx = ctx->g;

View File

@ -5,71 +5,15 @@
#include "lsm/store.h" #include "lsm/store.h"
static void randomize_key(char *key, int len) { static void randomize_key(char *key, int len) {
size_t charset_len = strlen(lander_key_charset);
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
key[i] = charset[rand() % charset_len]; key[i] = lander_key_charset[rand() % charset_len];
} }
key[len] = '\0'; 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,
Entry *entry, bool random) {
lander_gctx *c_gctx = ctx->g->c;
// The first match group matches the "long" path
bool secure =
(ctx->req.regex_groups[1].rm_eo - ctx->req.regex_groups[1].rm_so) == 1;
char *key;
int key_len = 0;
TrieExitCode res;
if (random) {
res = trie_add_random(c_gctx->trie, &key, entry, secure);
if (res == Ok) {
key_len = strlen(key);
}
} else {
key = (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;
res = trie_add_len(c_gctx->trie, key, key_len, entry);
}
switch (res) {
case Ok:
break;
case AlreadyPresent:
ctx->res.status = http_conflict;
return false;
default:
ctx->res.status = http_internal_server_error;
return false;
}
// Add a slash to the key and add it as the location header
char *buf = malloc(key_len + 2);
memcpy(&buf[1], 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;
if (key_ptr != NULL) {
*key_ptr = key;
}
if (key_len_ptr != NULL) {
*key_len_ptr = key_len;
}
return true;
}
/** /**
* Insert a new entry into the store. * Insert a new entry into the store.
* *
@ -123,7 +67,7 @@ bool lander_insert_entry(http_loop_ctx *ctx) {
return true; return true;
} }
bool lander_post_redirect_lsm(event_loop_conn *conn) { bool lander_post_redirect(event_loop_conn *conn) {
http_loop_ctx *ctx = conn->ctx; http_loop_ctx *ctx = conn->ctx;
lander_ctx *c_ctx = ctx->c; lander_ctx *c_ctx = ctx->c;
@ -149,7 +93,7 @@ bool lander_post_redirect_body_to_attr(event_loop_conn *conn) {
return true; return true;
} }
bool lander_post_paste_lsm(event_loop_conn *conn) { bool lander_post_paste(event_loop_conn *conn) {
http_loop_ctx *ctx = conn->ctx; http_loop_ctx *ctx = conn->ctx;
lander_ctx *c_ctx = ctx->c; lander_ctx *c_ctx = ctx->c;
@ -164,7 +108,7 @@ bool lander_post_paste_lsm(event_loop_conn *conn) {
return true; return true;
} }
bool lander_post_file_lsm(event_loop_conn *conn) { bool lander_post_file(event_loop_conn *conn) {
http_loop_ctx *ctx = conn->ctx; http_loop_ctx *ctx = conn->ctx;
lander_ctx *c_ctx = ctx->c; lander_ctx *c_ctx = ctx->c;
@ -178,81 +122,3 @@ bool lander_post_file_lsm(event_loop_conn *conn) {
return true; return true;
} }
bool lander_stream_body_to_entry(event_loop_conn *conn) {
http_loop_ctx *ctx = conn->ctx;
lander_ctx *c_ctx = ctx->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_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 =
ctx->req.regex_groups[2].rm_eo == ctx->req.regex_groups[2].rm_so;
// Allocate a new buffer to pass to the trie
char *url = malloc(ctx->req.body.len + 1);
memcpy(url, ctx->req.body.buf, ctx->req.body.len);
url[ctx->req.body.len] = '\0';
Entry *new_entry = entry_new(Redirect, url);
// The entry duplicates the string
free(url);
// We don't check the result here, because we would perform the same action
// either way
char *key;
add_entry(&key, NULL, ctx, new_entry, random);
if (random) {
free(key);
}
conn->state = event_loop_conn_state_res;
return true;
}
bool lander_post_paste(event_loop_conn *conn) {
http_loop_ctx *ctx = conn->ctx;
lander_gctx *c_gctx = ctx->g->c;
bool random =
ctx->req.regex_groups[2].rm_eo == ctx->req.regex_groups[2].rm_so;
char *key;
int key_len;
Entry *new_entry = entry_new(Paste, "");
if (!add_entry(&key, &key_len, ctx, new_entry, random)) {
conn->state = event_loop_conn_state_res;
return true;
}
char *fname = malloc(strlen(c_gctx->data_dir) + 8 + key_len + 1);
sprintf(fname, "%s/pastes/%.*s", c_gctx->data_dir, key_len, key);
ctx->req.body.fname = fname;
ctx->req.body.fname_owned = true;
if (random) {
free(key);
}
return true;
}

View File

@ -0,0 +1,73 @@
#include <string.h>
#include "lander.h"
bool lander_stream_body_to_entry(event_loop_conn *conn) {
http_loop_ctx *ctx = conn->ctx;
lander_ctx *c_ctx = ctx->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_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_headers_to_attrs(event_loop_conn *conn) {
http_loop_ctx *ctx = conn->ctx;
lander_ctx *c_ctx = ctx->c;
for (size_t i = 0; i < ctx->req.num_headers; i++) {
struct phr_header *header = &ctx->req.headers[i];
int j = 0;
while (header_to_attrs[j].header != NULL) {
if (strncmp(header->name, header_to_attrs[j].header, header->name_len) ==
0) {
lsm_str *value;
lsm_str_init_copy_n(&value, (char *)header->value, header->value_len);
lsm_entry_attr_insert(c_ctx->entry, header_to_attrs[j].attr_type,
value);
break;
}
j++;
}
}
return true;
}
bool lander_attrs_to_headers(event_loop_conn *conn) {
http_loop_ctx *ctx = conn->ctx;
lander_ctx *c_ctx = ctx->c;
int j = 0;
lsm_str *value;
while (header_to_attrs[j].header != NULL) {
if (lsm_entry_attr_get(&value, c_ctx->entry,
header_to_attrs[j].attr_type) == lsm_error_ok) {
char *buf = malloc(lsm_str_len(value) + 1);
memcpy(buf, lsm_str_ptr(value), lsm_str_len(value));
buf[lsm_str_len(value)] = '\0';
http_res_add_header(&ctx->res, header_to_attrs[j].header_type, buf, true);
}
j++;
}
return true;
}

View File

@ -34,20 +34,6 @@ int main() {
critical(1, "Invalid TCP port %s", port_str); critical(1, "Invalid TCP port %s", port_str);
} }
/* char file_path[strlen(data_dir) + 12 + 1]; */
/* sprintf(file_path, "%s/lander.data", data_dir); */
/* info("Initializing trie from file '%s'", file_path); */
/* Trie *trie; */
/* TrieExitCode res = trie_init(&trie, file_path); */
/* if (res != Ok) { */
/* critical(1, "An error occured while populating the trie."); */
/* } */
/* info("Trie initialized and populated with %i entries", trie_size(trie)); */
lander_gctx *c_gctx = lander_gctx_init(); lander_gctx *c_gctx = lander_gctx_init();
c_gctx->data_dir = data_dir_s; c_gctx->data_dir = data_dir_s;
@ -60,7 +46,7 @@ int main() {
critical(2, "Failed to load existing store."); critical(2, "Failed to load existing store.");
} }
info("Store loaded containing %lu entries.", lsm_store_size(c_gctx->store)); info("Store loaded containing %lu entries", lsm_store_size(c_gctx->store));
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]), c_gctx,

View File

@ -1,228 +0,0 @@
#include <sys/stat.h>
#include <sys/types.h>
#include "crow.h"
extern "C" {
#include "trie.h"
}
static const std::string file_path = "lander.data";
static const std::string index_page = R"(
<!DOCTYPE html>
<html>
<body>
<h1>r8r.be</h1>
<p>This is the URL shortener and pastebin accompanying my site, <a href="https://rustybever.be">The Rusty Bever</a>.</p>
</body>
</html>
)";
#define ENV(var, env_var) \
const char *_##var = getenv(env_var); \
if (_##var == NULL) { \
printf("Missing environment variable %s.\n", env_var); \
return 1; \
} \
const std::string var = std::string(_##var);
#define AUTH() \
std::string provided_api_key = req.get_header_value("X-Api-Key"); \
if (api_key.compare(provided_api_key) != 0) { \
return crow::response(crow::status::UNAUTHORIZED); \
}
crow::response add_redirect(std::string base_url, Trie *trie, const char *url,
bool secure) {
Entry *new_entry = entry_new(Redirect, url);
// The key already gets copied into the trie, so this pointer is safe to use
// ever after unlocking the trie
trie_wlock(trie);
char *key;
TrieExitCode res = trie_add_random(trie, &key, new_entry, secure);
trie_unlock(trie);
if (res != Ok) {
return crow::response(crow::status::INTERNAL_SERVER_ERROR);
}
std::string out = base_url + key;
free(key);
return crow::response(out);
}
bool store_paste(const char *key, const char *body) {
// Write paste contents to file
std::fstream file;
file.open(std::string("pastes/") + key, std::ios_base::out);
if (!file.is_open()) {
return false;
}
file << body;
file.close();
return true;
}
crow::response add_paste(std::string base_url, Trie *trie, const char *body,
bool secure) {
Entry *new_entry = entry_new(Paste, "");
trie_wlock(trie);
char *key;
TrieExitCode res = trie_add_random(trie, &key, new_entry, secure);
trie_unlock(trie);
if (res != Ok) {
return crow::response(crow::status::INTERNAL_SERVER_ERROR);
}
if (!store_paste(key, body)) {
return crow::response(crow::status::INTERNAL_SERVER_ERROR);
}
std::string out = base_url + key;
free(key);
return crow::response(out);
}
int main() {
// Initialize random seed for generating URLs
srand(time(NULL));
ENV(api_key, "LANDER_API_KEY");
ENV(base_url, "LANDER_BASE_URL");
std::cout << "Initializing trie from file '" << file_path << "'..."
<< std::endl;
// Initialize trie and populate from data file
Trie *trie;
int res = trie_init(&trie, file_path.c_str());
if (res != 0) {
std::cout << "An error occured while initializing the trie." << std::endl;
exit(1);
}
std::cout << "Added " << trie_size(trie) << " entries to trie." << std::endl;
// Create pastes directory if not present
// TODO don't just ignore errors here
mkdir("pastes", 0700);
crow::SimpleApp app;
app.loglevel(crow::LogLevel::Info);
CROW_ROUTE(app, "/").methods(crow::HTTPMethod::Get)(
[]() { return crow::response("html", index_page); });
// Serve an entry
CROW_ROUTE(app, "/<string>")
.methods(crow::HTTPMethod::Get)(
[trie](crow::response &res, std::string key) {
trie_rlock(trie);
Entry *entry;
TrieExitCode status = trie_search(trie, &entry, key.c_str());
if (status == Ok) {
if (entry->type == Redirect) {
res.redirect(entry->string);
} else if (entry->type == Paste) {
res.set_static_file_info("pastes/" + key);
}
} else {
res.code = 404;
}
res.end();
trie_unlock(trie);
});
// Add a new Redirect with a short randomly generated key
CROW_ROUTE(app, "/s/")
.methods(crow::HTTPMethod::Post)(
[api_key, base_url, trie](const crow::request req) {
AUTH();
return add_redirect(base_url, trie, req.body.c_str(), false);
});
// Add a new Redirect with a long randomly generated key
CROW_ROUTE(app, "/sl/")
.methods(crow::HTTPMethod::Post)(
[api_key, base_url, trie](const crow::request req) {
AUTH();
return add_redirect(base_url, trie, req.body.c_str(), true);
});
// Add a new Redirect with a given key
CROW_ROUTE(app, "/s/<string>")
.methods(crow::HTTPMethod::Post)(
[api_key, base_url, trie](const crow::request &req, std::string key) {
AUTH();
Entry *new_entry = entry_new(Redirect, req.body.c_str());
trie_wlock(trie);
TrieExitCode status = trie_add(trie, key.c_str(), new_entry);
trie_unlock(trie);
switch (status) {
case Ok:
return crow::response(base_url + key);
case AlreadyPresent:
return crow::response(crow::status::CONFLICT);
default:
return crow::response(crow::status::INTERNAL_SERVER_ERROR);
}
});
// Add a new Paste with a short randomly generated key
CROW_ROUTE(app, "/p/")
.methods(crow::HTTPMethod::Post)(
[api_key, base_url, trie](const crow::request &req) {
AUTH();
return add_paste(base_url, trie, req.body.c_str(), false);
});
// Add a new Paste with a long randomly generated key
CROW_ROUTE(app, "/pl/")
.methods(crow::HTTPMethod::Post)(
[api_key, base_url, trie](const crow::request &req) {
AUTH();
return add_paste(base_url, trie, req.body.c_str(), true);
});
// Add a paste with a given key
CROW_ROUTE(app, "/p/<string>")
.methods(crow::HTTPMethod::Post)(
[api_key, base_url, trie](const crow::request &req, std::string key) {
AUTH();
Entry *new_entry = entry_new(Paste, "");
trie_wlock(trie);
TrieExitCode status = trie_add(trie, key.c_str(), new_entry);
trie_unlock(trie);
if (status != Ok) {
return crow::response(crow::status::CONFLICT);
}
if (!store_paste(key.c_str(), req.body.c_str())) {
return crow::response(crow::status::INTERNAL_SERVER_ERROR);
}
return crow::response(base_url + key);
});
app.port(18080).multithreaded().run();
}