chore(lander): removed old networking code
ci/woodpecker/push/build Pipeline was successful Details
ci/woodpecker/push/docker Pipeline failed Details

lnm
Jef Roosens 2023-12-06 23:04:06 +01:00
parent 1a7686003c
commit c59dd29648
Signed by: Jef Roosens
GPG Key ID: B75D4F293C7052DB
15 changed files with 7 additions and 1207 deletions

View File

@ -111,12 +111,14 @@ $(BUILD_DIR)/$(TEST_DIR)/%.c.o: $(TEST_DIR)/%.c
lint: lint:
clang-format -n --Werror $(SRCS) $(SRCS_H) $(SRCS_H_INTERNAL) clang-format -n --Werror $(SRCS) $(SRCS_H) $(SRCS_H_INTERNAL)
make -C lsm lint make -C lsm lint
make -C lnm lint
make -C landerctl lint make -C landerctl lint
.PHONY: fmt .PHONY: fmt
fmt: fmt:
clang-format -i $(SRCS) $(SRCS_H) $(SRCS_H_INTERNAL) clang-format -i $(SRCS) $(SRCS_H) $(SRCS_H_INTERNAL)
make -C lsm fmt make -C lsm fmt
make -C lnm fmt
make -C landerctl fmt make -C landerctl fmt
.PHONY: check .PHONY: check
@ -133,12 +135,14 @@ check:
-j$(shell nproc) \ -j$(shell nproc) \
$(SRCS) $(SRCS)
make -C lsm check make -C lsm check
make -C lnm check
make -C landerctl check make -C landerctl check
.PHONY: clean .PHONY: clean
clean: clean:
rm -rf $(BUILD_DIR) rm -rf $(BUILD_DIR)
$(MAKE) -C lsm clean $(MAKE) -C lsm clean
$(MAKE) -C lnm clean
$(MAKE) -C landerctl clean $(MAKE) -C landerctl clean
.PHONY: bear .PHONY: bear

View File

@ -8,7 +8,7 @@ TEST_DIR = test
THIRDPARTY_DIR = thirdparty THIRDPARTY_DIR = thirdparty
INC_DIRS = include $(THIRDPARTY_DIR)/include lsm/include lnm/include INC_DIRS = include $(THIRDPARTY_DIR)/include lsm/include lnm/include
LIBS = m lsm lnm LIBS = lsm lnm
LIB_DIRS = ./lsm/build ./lnm/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

View File

@ -54,7 +54,7 @@ void lnm_http_loop_process_route(lnm_http_conn *conn) {
for (size_t i = 0; i < gctx->routes.len && match_level < 3; i++) { for (size_t i = 0; i < gctx->routes.len && match_level < 3; i++) {
route = gctx->routes.arr[i]; route = gctx->routes.arr[i];
bool matched_path; bool matched_path = false;
switch (route->type) { switch (route->type) {
case lnm_http_route_type_literal: case lnm_http_route_type_literal:
@ -238,7 +238,7 @@ void lnm_http_loop_process_write_body(lnm_http_conn *conn) {
size_t to_write = size_t to_write =
LNM_MIN(res->body.len - res->written, LNM_LOOP_BUF_SIZE - conn->w.size); LNM_MIN(res->body.len - res->written, LNM_LOOP_BUF_SIZE - conn->w.size);
size_t written; size_t written = 0;
switch (res->body.type) { switch (res->body.type) {
case lnm_http_res_body_type_buf: case lnm_http_res_body_type_buf:

View File

@ -1,192 +0,0 @@
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <poll.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include "event_loop.h"
#include "log.h"
static void event_loop_fd_set_nb(int fd) {
int flags = fcntl(fd, F_GETFL);
flags |= O_NONBLOCK;
fcntl(fd, F_SETFL, flags);
}
event_loop *event_loop_init() {
event_loop *el = calloc(sizeof(event_loop), 1);
// No idea if this is a good starter value
el->connections = calloc(sizeof(event_loop_conn *), 16);
el->connection_count = 16;
return el;
}
int event_loop_put(event_loop *el, event_loop_conn *conn) {
if ((size_t)conn->fd >= el->connection_count) {
event_loop_conn **resized =
realloc(el->connections, sizeof(event_loop_conn *) * (conn->fd + 1));
if (resized == NULL) {
return -1;
}
el->connections = resized;
el->connection_count = conn->fd + 1;
}
el->connections[conn->fd] = conn;
return 0;
}
int event_loop_accept(event_loop *el, int fd) {
struct sockaddr_in client_addr;
socklen_t socklen = sizeof(client_addr);
int connfd = accept(fd, (struct sockaddr *)&client_addr, &socklen);
if (connfd < 0) {
return -1;
}
// set the new connection fd to nonblocking mode
event_loop_fd_set_nb(connfd);
// creating the struct Conn
event_loop_conn *conn = event_loop_conn_init(el);
// Close the connectoin if we fail to allocate a connection struct
if (conn == NULL) {
close(connfd);
return -3;
}
conn->fd = connfd;
conn->state = event_loop_conn_state_req;
int res = event_loop_put(el, conn);
if (res != 0) {
close(connfd);
return -4;
}
debug("Connection established on fd %i", connfd);
return 0;
}
void event_loop_run(event_loop *el, int port) {
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) {
critical(1, "Failed to open listening socket, errno: %i", errno);
}
int val = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
// bind
struct sockaddr_in addr = {.sin_family = AF_INET,
.sin_port = ntohs(port),
.sin_addr.s_addr = ntohl(0)};
int res = bind(fd, (const struct sockaddr *)&addr, sizeof(addr));
if (res) {
critical(1, "Failed to bind listening socket, errno: %i", errno);
}
debug("Listening socket bound to fd %i", fd);
res = listen(fd, SOMAXCONN);
if (res) {
critical(1, "Failed to start listening on listening socket, errno: %i",
errno);
}
// The listening socket is always poll'ed in non-blocking mode as well
event_loop_fd_set_nb(fd);
// TODO don't hardcode the number 32
struct pollfd *poll_args = calloc(sizeof(struct pollfd), 32);
// for convenience, the listening fd is put in the first position
struct pollfd pfd = {fd, POLLIN, 0};
poll_args[0] = pfd;
event_loop_conn *conn;
int events;
info("Starting event loop on port %i", port);
while (1) {
size_t poll_args_count = 1;
// connection fds
for (size_t i = 0; i < el->connection_count; i++) {
conn = el->connections[i];
if (conn == NULL) {
continue;
}
events = (conn->state == event_loop_conn_state_req) ? POLLIN : POLLOUT;
events |= POLLERR;
pfd.fd = conn->fd;
pfd.events = events;
poll_args[poll_args_count] = pfd;
poll_args_count++;
// We do at most 32 connections at a time for now
if (poll_args_count == 32)
break;
}
// poll for active fds
// the timeout argument doesn't matter here
int rv = poll(poll_args, (nfds_t)poll_args_count, -1);
if (rv < 0) {
critical(1, "Poll failed, errno: %i", errno);
}
// process active connections
for (size_t i = 1; i < poll_args_count; i++) {
if (poll_args[i].revents) {
conn = el->connections[poll_args[i].fd];
event_loop_conn_io(el, conn);
if (conn->state == event_loop_conn_state_end) {
// client closed normally, or something bad happened.
// destroy this connection
el->connections[conn->fd] = NULL;
close(conn->fd);
debug("Connection closed on fd %i", conn->fd);
event_loop_conn_free(el, conn);
}
}
}
// try to accept a new connection if the listening fd is active
if (poll_args[0].revents) {
event_loop_accept(el, fd);
}
}
}

View File

@ -1,13 +0,0 @@
#include "event_loop.h"
event_loop_conn *event_loop_conn_init(event_loop *el) {
event_loop_conn *conn = calloc(sizeof(event_loop_conn), 1);
conn->ctx = el->ctx_init(el->gctx);
return conn;
}
void event_loop_conn_free(event_loop *el, event_loop_conn *conn) {
el->ctx_free(conn->ctx);
free(conn);
}

View File

@ -1,98 +0,0 @@
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include "event_loop.h"
void event_loop_conn_io_res(event_loop *el, event_loop_conn *conn) {
do {
ssize_t res = 0;
size_t remain = conn->wbuf_size - conn->wbuf_sent;
do {
res = write(conn->fd, &conn->wbuf[conn->wbuf_sent], remain);
} while (res < 0 && errno == EINTR);
// EAGAIN doesn't mean there was an error, but rather that there's no more
// data right now, but there might be more later, aka "try again later"
if (res < 0 && errno == EAGAIN) {
return;
}
// If it's not EGAIN, there was an error writing so we simply end the
// request
if (res < 0) {
conn->state = event_loop_conn_state_end;
return;
}
conn->wbuf_sent += (size_t)res;
// After writing the entire buffer, we run the write_data command to receive
// new data, or exit the loop
if (conn->wbuf_sent == conn->wbuf_size) {
conn->wbuf_sent = 0;
conn->wbuf_size = 0;
el->write_data(conn);
}
} while (conn->state == event_loop_conn_state_res);
}
/**
* Read new data into the read buffer. This command performs at most one
* successful read syscall.
*
* Returns whether the function should be retried immediately or not.
*/
void event_loop_conn_io_req(event_loop *el, event_loop_conn *conn) {
do {
// Move remaining data to start of buffer
memmove(conn->rbuf, &conn->rbuf[conn->rbuf_read],
conn->rbuf_size - conn->rbuf_read);
conn->rbuf_size -= conn->rbuf_read;
conn->rbuf_read = 0;
ssize_t res;
size_t cap = EVENT_LOOP_BUFFER_SIZE - conn->rbuf_size;
// Try to read at most cap bytes from the file descriptor
do {
res = read(conn->fd, &conn->rbuf[conn->rbuf_size], cap);
} while (res < 0 && errno == EINTR);
// EGAIN means we try again later
if (res < 0 && errno == EAGAIN) {
return;
}
// Any other negative error message means the read errored out. If res is 0,
// we've reached the end of the input which (usually) means the remote peer
// has closed the connection. Either way, we close the connection.
if (res <= 0) {
conn->state = event_loop_conn_state_end;
return;
}
conn->rbuf_size += (size_t)res;
// This loop allows processing multiple requests from a single read buffer
while (el->handle_data(conn))
;
}
// We can keep reading as long as we're in request mode
while (conn->state == event_loop_conn_state_req);
}
void event_loop_conn_io(event_loop *el, event_loop_conn *conn) {
switch (conn->state) {
case event_loop_conn_state_req:
event_loop_conn_io_req(el, conn);
break;
case event_loop_conn_state_res:
event_loop_conn_io_res(el, conn);
break;
case event_loop_conn_state_end:;
}
}

View File

@ -1,133 +0,0 @@
#include <stdlib.h>
#include "http/types.h"
// Very important that this is in the same order as http_request_method
const char *http_method_names[] = {"GET", "POST", "PUT", "PATCH", "DELETE"};
const size_t http_method_names_len =
sizeof(http_method_names) / sizeof(http_method_names[0]);
// clang-format off
const char *http_status_names[][32] = {
// 1xx
{
"Continue", // 100
"Switching Protocols", // 101,
"Processing", // 102
"Early Hints", // 103
},
// 2xx
{
"OK", // 200
"Created", // 201
"Accepted", // 202
"Non-Authoritative Information", // 203
"No Content", // 204
"Reset Content", // 205
"Partial Content", // 206
"Multi-Status", // 207
"Already Reported", // 208
},
// 3xx
{
"Multiple Choices", // 300
"Moved Permanently", // 301
"Found", // 302
"See Other", // 303
"Not Modified", // 304
NULL, // 305
NULL, // 306
"Temporary Redirect", // 307
"Permanent Redirect", // 308
},
// 4xx
{
"Bad Request", // 400
"Unauthorized", // 401
"Payment Required", // 402
"Forbidden", // 403
"Not Found", // 404
"Method Not Allowed", // 405
"Not Acceptable", // 406
"Proxy Authentication Required", // 407
"Request Timeout", // 408
"Conflict", // 409
"Gone", // 410
"Length Required", // 411
"Precondition Failed", // 412
"Content Too Large", // 413
"URI Too Long", // 414
"Unsupported Media Type", // 415
"Range Not Satisfiable", // 416
"Expectation Failed", // 417
"I'm a teapot", // 418
NULL, // 419
NULL, // 420
"Misdirected Request", // 421
"Unprocessable Content", // 422
"Locked", // 423
"Failed Dependency", // 424
"Too Early", // 425
"Upgrade Required", // 426
NULL, // 427
"Precondition Required", // 428
"Too Many Requests", // 429
NULL, // 430
"Request Header Fields Too Large", // 431
},
// 5xx
{
"Internal Server Error", // 500
"Not Implemented", // 501
"Bad Gateway", // 502
"Service Unavailable", // 503
"Gateway Timeout", // 504
"HTTP Version Not Supported", // 505
"Variant Also Negotiates", // 506
"Insufficient Storage", // 507
"Loop Detected", // 508
NULL, // 509
"Not Extended", // 510
"Network Authentication Required" // 511
},
};
const char *http_header_names[] = {
"Connection",
"Location",
"Content-Type",
"Content-Disposition",
"Server",
"Content-Length"
};
const char *http_mime_type_names[][2] = {
{ "aac", "audio/aac" },
{ "bz", "application/x-bzip" },
{ "bz2", "application/x-bzip2" },
{ "css", "text/css" },
{ "csv", "text/csv" },
{ "gz", "application/gzip" },
{ "gif", "image/gif" },
{ "htm", "text/html" },
{ "html", "text/html" },
{ "jar", "application/java-archive" },
{ "jpeg", "image/jpeg" },
{ "jpg", "image/jpeg" },
{ "js", "text/javascript" },
{ "json", "application/json" },
{ "mp3", "audio/mpeg" },
{ "mp4", "video/mp4" },
{ "png", "image/png" },
{ "pdf", "application/pdf" },
{ "rar", "application/vnd.rar" },
{ "sh", "application/x-sh" },
{ "svg", "image/svg+xml" },
{ "tar", "application/x-tar" },
{ "txt", "text/plain" },
{ "wav", "audio/wav" },
{ "7z", "application/x-7z-compressed" },
};
// clang-format on

View File

@ -1,52 +0,0 @@
#include <stdlib.h>
#include <sys/stat.h>
#include "http/res.h"
void http_res_set_body_buf(http_response *res, const char *body,
size_t body_len, bool owned) {
res->body.type = http_body_buf;
res->body.buf = (char *)body;
res->body.expected_len = body_len;
res->body.buf_owned = owned;
}
void http_res_set_body_file(http_response *res, const char *filename) {
struct stat st;
stat(filename, &st);
// TODO error handling
FILE *f = fopen(filename, "r");
res->body.type = http_body_file;
res->body.file = f;
res->body.expected_len = st.st_size;
}
void http_res_add_header(http_response *res, http_header type,
const char *value, bool owned) {
// Extend the header array
if (res->headers.len == res->headers.cap) {
http_response_header *new_arr = realloc(
res->headers.arr, 2 * res->headers.cap * sizeof(http_response_header));
if (new_arr == NULL) {
// TODO error handling
return;
}
res->headers.arr = new_arr;
res->headers.cap *= 2;
}
res->headers.arr[res->headers.len].type = type;
res->headers.arr[res->headers.len].value = value;
res->headers.arr[res->headers.len].owned = owned;
res->headers.len++;
}
void http_res_set_mime_type(http_response *res, http_mime_type mime_type) {
http_res_add_header(res, http_header_content_type,
http_mime_type_names[mime_type][1], false);
}

View File

@ -1,33 +0,0 @@
#include <stdlib.h>
#include "http/types.h"
http_body *http_body_init() { return calloc(sizeof(http_body), 1); }
void http_body_reset(http_body *body) {
if (body->type == http_body_file) {
fclose(body->file);
}
if (body->buf_owned) {
free(body->buf);
}
if (body->fname_owned) {
free(body->fname);
}
body->type = 0;
body->buf = NULL;
body->buf_owned = false;
body->file = NULL;
body->fname = NULL;
body->fname_owned = false;
body->expected_len = 0;
body->len = 0;
}
void http_body_free(http_body *body) {
http_body_reset(body);
free(body);
}

View File

@ -1,101 +0,0 @@
#include <regex.h>
#include "http/types.h"
#include "http_loop.h"
#include "log.h"
const http_step http_default_res_steps[HTTP_LOOP_MAX_STEPS] = {
http_loop_step_write_header, http_loop_step_write_body, NULL};
bool http_loop_handle_request(event_loop_conn *conn) {
// Prevents the request handler function from looping indefinitely without
// ever consuming new data
if (conn->rbuf_size - conn->rbuf_read == 0) {
return false;
}
http_loop_ctx *ctx = conn->ctx;
// If route is defined, we're currently processing a request
if (ctx->route == NULL) {
http_parse_error res = http_loop_parse_request(conn);
if (res == http_parse_error_invalid ||
(res == http_parse_error_incomplete &&
conn->rbuf_size == EVENT_LOOP_BUFFER_SIZE)) {
conn->state = event_loop_conn_state_end;
return false;
}
conn->rbuf_read += ctx->req.len;
// It's fun to respond with extremely specific error messages
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/501
if (res == http_parse_error_unknown_method) {
ctx->res.status = http_method_not_implemented;
conn->state = event_loop_conn_state_res;
} else {
http_loop_route_request(conn);
}
}
if (conn->state == event_loop_conn_state_req) {
http_loop_process_request(conn);
}
// TODO in highly concurrent situations, it might actually be better to always
// return false here, as this allows cycling better through all connections
return conn->state == event_loop_conn_state_req;
}
event_loop *http_loop_init(http_route *routes, size_t route_count,
void *custom_gctx, void *(*custom_ctx_init)(),
void(custom_ctx_reset)(), void(custom_ctx_free)()) {
event_loop *el = event_loop_init();
el->ctx_init = (void *(*)(void *))http_loop_ctx_init;
el->ctx_free = (void (*)(void *))http_loop_ctx_free;
el->handle_data = http_loop_handle_request;
el->write_data = http_loop_handle_response;
http_loop_gctx *gctx = http_loop_gctx_init();
gctx->c = custom_gctx;
gctx->routes = routes;
gctx->route_count = route_count;
gctx->custom_ctx_init = custom_ctx_init;
gctx->custom_ctx_reset = custom_ctx_reset;
gctx->custom_ctx_free = custom_ctx_free;
el->gctx = gctx;
return el;
}
void http_loop_set_api_key(http_loop *hl, const char *api_key) {
http_loop_gctx *gctx = hl->gctx;
gctx->api_key = api_key;
}
void http_loop_run(event_loop *el, int port) {
debug("Compiling RegEx routes");
http_loop_gctx *gctx = el->gctx;
for (size_t i = 0; i < gctx->route_count; i++) {
http_route *route = &gctx->routes[i];
if (route->type == http_route_regex) {
regex_t *r = calloc(sizeof(regex_t), 1);
if (regcomp(r, route->path, REG_EXTENDED) != 0) {
critical(1, "RegEx expression '%s' failed to compile", route->path);
}
route->regex = r;
}
}
debug("RegEx routes compiled successfully");
event_loop_run(el, port);
}

View File

@ -1,62 +0,0 @@
#include <stdio.h>
#include "http/types.h"
#include "http_loop.h"
http_loop_gctx *http_loop_gctx_init() {
http_loop_gctx *gctx = calloc(sizeof(http_loop_gctx), 1);
return gctx;
}
http_loop_ctx *http_loop_ctx_init(http_loop_gctx *g) {
http_loop_ctx *ctx = calloc(sizeof(http_loop_ctx), 1);
ctx->g = g;
ctx->c = g->custom_ctx_init();
// TODO error checking
ctx->res.headers.arr = malloc(4 * sizeof(http_response_header));
ctx->res.headers.cap = 4;
return ctx;
}
void http_loop_ctx_free(http_loop_ctx *ctx) {
http_loop_ctx_reset(ctx);
ctx->g->custom_ctx_free(ctx->c);
if (ctx->res.headers.cap > 0) {
free(ctx->res.headers.arr);
}
free(ctx);
}
void http_loop_ctx_reset(http_loop_ctx *ctx) {
ctx->route = NULL;
ctx->current_step = 0;
if (ctx->res.head != NULL) {
free((void *)ctx->res.head);
ctx->res.head = NULL;
}
http_body_reset(&ctx->req.body);
http_body_reset(&ctx->res.body);
for (size_t i = 0; i < ctx->res.headers.len; i++) {
if (ctx->res.headers.arr[i].owned) {
free((void *)ctx->res.headers.arr[i].value);
}
}
// We don't set the cap as we can just keep the original array for the next
// requests
ctx->res.headers.len = 0;
ctx->res.status = 0;
ctx->res.head_len = 0;
ctx->res.head_written = 0;
ctx->g->custom_ctx_reset(ctx->c);
}

View File

@ -1,149 +0,0 @@
#include <string.h>
#include "http_loop.h"
#include "log.h"
http_parse_error http_loop_parse_request(event_loop_conn *conn) {
http_loop_ctx *ctx = conn->ctx;
// First we try to parse the incoming HTTP request
size_t num_headers = HTTP_MAX_ALLOWED_HEADERS;
http_request *req = &ctx->req;
const char *method;
size_t method_len;
char *path;
size_t path_len;
int res =
phr_parse_request((const char *)&conn->rbuf[conn->rbuf_read],
conn->rbuf_size - conn->rbuf_read, &method, &method_len,
(const char **)&path, &path_len, &req->minor_version,
req->headers, &num_headers, 0);
if (res == -1) {
return http_parse_error_invalid;
} else if (res == -2) {
return http_parse_error_incomplete;
}
req->num_headers = num_headers;
req->len = res;
// Try to parse the method type
bool match = false;
size_t i = 0;
for (i = 0; i < http_method_names_len; i++) {
if (strncmp(method, http_method_names[i], method_len) == 0) {
req->method = i;
match = true;
}
}
if (!match) {
return http_parse_error_unknown_method;
}
// Split path into path & query
i = 0;
bool no_query = true;
while (no_query && i < path_len) {
if (path[i] == '?') {
// Ensure we don't store an invalid pointer if the request simply ends
// with '?'
if (i + 1 < req->path_len) {
req->query = &path[i + 1];
req->query_len = path_len - (i + 1);
}
path_len = i;
no_query = false;
}
i++;
}
// The path needs to be NULL-terminated in order for regex routes to be
// matched properly. We know we can overwrite this char because it's either
// '?' if there's a query, or '\n' because we know the buf contains a valid
// HTTP rqeuest
path[path_len] = '\0';
req->path = path;
req->path_len = path_len;
// Ensure we clear the old request's query
if (no_query) {
req->query = NULL;
req->query_len = 0;
}
return http_parse_error_ok;
}
void http_loop_route_request(event_loop_conn *conn) {
http_loop_ctx *ctx = conn->ctx;
http_loop_gctx *gctx = ctx->g;
info("%s %.*s", http_method_names[ctx->req.method], ctx->req.path_len,
ctx->req.path);
http_route *route;
bool path_matched = false;
for (size_t i = 0; i < gctx->route_count; i++) {
route = &gctx->routes[i];
switch (route->type) {
case http_route_literal:
if (strncmp(route->path, ctx->req.path, ctx->req.path_len) == 0) {
path_matched = true;
if (ctx->req.method == route->method) {
ctx->route = route;
return;
}
}
break;
case http_route_regex:
if (regexec(route->regex, ctx->req.path, HTTP_MAX_REGEX_GROUPS,
ctx->req.regex_groups, 0) == 0) {
path_matched = true;
if (ctx->req.method == route->method) {
ctx->route = route;
return;
}
}
break;
}
}
// Fallthrough case
ctx->res.status = path_matched ? http_method_not_allowed : http_not_found;
conn->state = event_loop_conn_state_res;
}
void http_loop_process_request(event_loop_conn *conn) {
http_loop_ctx *ctx = conn->ctx;
// We keep processing step functions as long as they don't need to wait for
// I/O
while ((conn->state == event_loop_conn_state_req) &&
(ctx->route->steps[ctx->current_step] != NULL) &&
ctx->route->steps[ctx->current_step](conn)) {
ctx->current_step++;
}
// Request processing can stop early by switching the connection state
// Either way, we reset the step counter as it will be used by the response
// steps
if ((conn->state != event_loop_conn_state_req) ||
(ctx->route->steps[ctx->current_step] == NULL)) {
ctx->current_step = 0;
conn->state = event_loop_conn_state_res;
}
}

View File

@ -1,143 +0,0 @@
#include "http_loop.h"
#include "log.h"
static const char *server = "lander/" LANDER_VERSION;
static int num_digits(size_t n) {
int digits = 1;
while (n > 9) {
digits++;
n /= 10;
}
return digits;
}
/*
* This function precalculates the size of the total buffer required using
* snprintf. When this function is called with a buf size of 0, it never tries
* to write any data, but it does return the amount of bytes that would be
* written.
*/
void http_loop_init_header(http_response *res) {
if (res->status == 0) {
res->status = http_ok;
}
http_res_add_header(res, http_header_server, server, false);
char *content_length_header = malloc(num_digits(res->body.expected_len) + 1);
sprintf(content_length_header, "%zu", res->body.expected_len);
http_res_add_header(res, http_header_content_length, content_length_header,
true);
const char *response_type_name =
http_status_names[res->status / 100 - 1][res->status % 100];
// First we calculate the size of the start of the header
int buf_size =
snprintf(NULL, 0, "HTTP/1.1 %i %s\n", res->status, response_type_name);
// We add each header's required size
for (size_t i = 0; i < res->headers.len; i++) {
buf_size += snprintf(NULL, 0, "%s: %s\n",
http_header_names[res->headers.arr[i].type],
res->headers.arr[i].value);
}
// The + 1 is required to store the final null byte, but we will replace it
// with the required final newline
char *buf = malloc(buf_size + 1);
buf_size = sprintf(buf, "HTTP/1.1 %i %s\n", res->status, response_type_name);
for (size_t i = 0; i < res->headers.len; i++) {
buf_size += sprintf(&buf[buf_size], "%s: %s\n",
http_header_names[res->headers.arr[i].type],
res->headers.arr[i].value);
}
buf[buf_size] = '\n';
res->head = buf;
res->head_len = buf_size + 1;
}
bool http_loop_step_write_header(event_loop_conn *conn) {
http_response *res = &((http_loop_ctx *)conn->ctx)->res;
// Create head response
if (res->head == NULL) {
http_loop_init_header(res);
}
// Step has finished its work
if (res->head_written == res->head_len) {
return true;
}
size_t bytes_to_write = MIN(res->head_len - res->head_written,
EVENT_LOOP_BUFFER_SIZE - conn->wbuf_size);
memcpy(&conn->wbuf[conn->wbuf_size], &res->head[res->head_written],
bytes_to_write);
conn->wbuf_size += bytes_to_write;
res->head_written += bytes_to_write;
return false;
}
bool http_loop_step_write_body(event_loop_conn *conn) {
http_response *res = &((http_loop_ctx *)conn->ctx)->res;
if (res->body.expected_len == res->body.len) {
return true;
}
size_t bytes_to_write = MIN(res->body.expected_len - res->body.len,
EVENT_LOOP_BUFFER_SIZE - conn->wbuf_size);
size_t bytes_written;
switch (res->body.type) {
case http_body_buf:
memcpy(&conn->wbuf[conn->wbuf_size], &(res->body.buf)[res->body.len],
bytes_to_write);
conn->wbuf_size += bytes_to_write;
res->body.len += bytes_to_write;
break;
case http_body_file:
bytes_written = fread(&conn->wbuf[conn->wbuf_size], sizeof(uint8_t),
bytes_to_write, res->body.file);
conn->wbuf_size += bytes_written;
res->body.len += bytes_written;
break;
}
return false;
}
void http_loop_handle_response(event_loop_conn *conn) {
http_loop_ctx *ctx = conn->ctx;
// Non-routed requests also need to be processed
const http_step *steps =
ctx->route != NULL ? ctx->route->steps_res : http_default_res_steps;
while ((conn->state == event_loop_conn_state_res) &&
(steps[ctx->current_step] != NULL) && steps[ctx->current_step](conn)) {
ctx->current_step++;
}
// Response processing can stop early be switching the connection state
// After response processing has finished its work, we reset the context to
// prepare for a new request
if ((conn->state != event_loop_conn_state_res) ||
(steps[ctx->current_step] == NULL)) {
http_loop_ctx_reset(ctx);
conn->state = event_loop_conn_state_req;
}
}

View File

@ -1,179 +0,0 @@
#include <math.h>
#include <string.h>
#include "http_loop.h"
#include "lander.h"
// Just a naive pow implementation; might improve later
static uint64_t ipow(uint64_t base, uint64_t power) {
uint64_t res = 1;
while (power > 0) {
res *= base;
power--;
}
return res;
}
/*
* Converts a string to a number, returning true if the string contained a valid
* positive number.
*/
static bool string_to_num(size_t *res, const char *s, size_t len) {
*res = 0;
for (size_t i = 0; i < len; i++) {
int val = s[i] - '0';
if (val < 0 || val > 9) {
return false;
}
*res += (uint64_t)val * ipow(10, (len - 1) - i);
}
return true;
}
bool http_loop_step_parse_content_length(event_loop_conn *conn) {
http_loop_ctx *ctx = conn->ctx;
for (size_t i = 0; i < ctx->req.num_headers; i++) {
const struct phr_header *header = &ctx->req.headers[i];
if (strncmp(header->name, "Content-Length", header->name_len) == 0) {
// If the content length header is present but contains an invalid
// number, we return a bad request error
if (!string_to_num(&ctx->req.body.expected_len, header->value,
header->value_len)) {
ctx->res.status = http_bad_request;
conn->state = event_loop_conn_state_res;
return true;
}
// The content length was actually 0, so we can instantly return here
else if (ctx->req.body.expected_len == 0) {
return true;
}
}
}
// A zero here means there's no content length header
if (ctx->req.body.expected_len == 0) {
ctx->res.status = http_length_required;
conn->state = event_loop_conn_state_res;
}
return true;
}
/*
* Try to find and parse the Content-Length header. This function returns true
* if it was successful. If false is returned, the underlying step should
* immediately exit.
*/
bool try_parse_content_length(event_loop_conn *conn) {
http_loop_ctx *ctx = conn->ctx;
for (size_t i = 0; i < ctx->req.num_headers; i++) {
const struct phr_header *header = &ctx->req.headers[i];
if (strncmp(header->name, "Content-Length", header->name_len) == 0) {
// If the content length header is present but contains an invalid
// number, we return a bad request error
if (!string_to_num(&ctx->req.body.expected_len, header->value,
header->value_len)) {
ctx->res.status = http_bad_request;
conn->state = event_loop_conn_state_res;
return false;
}
// The content length was actually 0, so we can instantly return here
else if (ctx->req.body.expected_len == 0) {
return false;
}
}
}
// A zero here means there's no content length header
if (ctx->req.body.expected_len == 0) {
ctx->res.status = http_length_required;
conn->state = event_loop_conn_state_res;
return false;
}
return true;
}
bool http_loop_step_body_to_buf(event_loop_conn *conn) {
http_loop_ctx *ctx = conn->ctx;
if (ctx->req.body.expected_len == 0) {
if (!try_parse_content_length(conn)) {
return true;
}
ctx->req.body.type = http_body_buf;
ctx->req.body.buf = malloc(ctx->req.body.expected_len * sizeof(char));
ctx->req.body.len = 0;
}
size_t bytes_to_copy = MIN(conn->rbuf_size - conn->rbuf_read,
ctx->req.body.expected_len - ctx->req.body.len);
memcpy(&ctx->req.body.buf[ctx->req.body.len], &conn->rbuf[conn->rbuf_read],
bytes_to_copy);
ctx->req.body.len += bytes_to_copy;
conn->rbuf_read += bytes_to_copy;
return ctx->req.body.len == ctx->req.body.expected_len;
}
bool http_loop_step_body_to_file(event_loop_conn *conn) {
http_loop_ctx *ctx = conn->ctx;
if (ctx->req.body.expected_len == 0) {
if (!try_parse_content_length(conn)) {
return true;
}
ctx->req.body.type = http_body_file;
ctx->req.body.file = fopen(ctx->req.body.fname, "wb");
ctx->req.body.len = 0;
}
size_t bytes_to_write = MIN(conn->rbuf_size - conn->rbuf_read,
ctx->req.body.expected_len - ctx->req.body.len);
size_t bytes_written = fwrite(&conn->rbuf[conn->rbuf_read], sizeof(uint8_t),
bytes_to_write, ctx->req.body.file);
ctx->req.body.len += bytes_written;
conn->rbuf_read += bytes_written;
return ctx->req.body.len == ctx->req.body.expected_len;
}
bool http_loop_step_auth(event_loop_conn *conn) {
http_loop_ctx *ctx = conn->ctx;
for (size_t i = 0; i < ctx->req.num_headers; i++) {
const struct phr_header *header = &ctx->req.headers[i];
if ((strncmp("X-Api-Key", header->name, header->name_len) == 0) &&
(strncmp(header->value, ctx->g->api_key, header->value_len) == 0) &&
(strlen(ctx->g->api_key) == header->value_len)) {
return true;
}
}
ctx->res.status = http_unauthorized;
conn->state = event_loop_conn_state_res;
return true;
}
bool http_loop_step_switch_res(event_loop_conn *conn) {
conn->state = event_loop_conn_state_res;
return true;
}

View File

@ -11,55 +11,6 @@
const char lander_key_charset[] = const char lander_key_charset[] =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
http_route lander_routes[] = {
{.type = http_route_literal,
.method = http_get,
.path = "/",
.steps = {lander_get_index, NULL},
.steps_res = {http_loop_step_write_header, http_loop_step_write_body,
NULL}},
{
.type = http_route_regex,
.method = http_get,
.path = "^/([^/]+)$",
.steps = {lander_get_entry, NULL},
.steps_res = {http_loop_step_write_header, lander_stream_body_to_client,
NULL},
},
{
.type = http_route_regex,
.method = http_delete,
.path = "^/([^/]+)$",
.steps = {http_loop_step_auth, lander_remove_entry, NULL},
.steps_res = {http_loop_step_write_header, http_loop_step_write_body,
NULL},
},
{
.type = http_route_regex,
.method = http_post,
.path = "^/s(l?)/([^/]*)$",
.steps = {http_loop_step_auth, lander_post_redirect,
http_loop_step_body_to_buf, lander_post_redirect_body_to_attr,
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, http_loop_step_parse_content_length,
lander_post_paste, 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 = "^/f(l?)/([^/]*)$",
.steps = {http_loop_step_auth, http_loop_step_parse_content_length,
lander_post_file, lander_stream_body_to_entry, NULL},
.steps_res = {http_loop_step_write_header, http_loop_step_write_body,
NULL}},
};
void *lander_gctx_init() { return calloc(1, sizeof(lander_gctx)); } void *lander_gctx_init() { return calloc(1, sizeof(lander_gctx)); }
lnm_err lander_ctx_init(void **c_ctx, void *gctx) { lnm_err lander_ctx_init(void **c_ctx, void *gctx) {