From 68f9227436f996b3e68f57bb49d3f35ebc8a874e Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Sat, 27 May 2023 11:47:39 +0200 Subject: [PATCH] feat: decouple event loop from http --- .gitignore | 2 +- include/event_loop.h | 58 +++++++----------- include/http_loop.h | 33 ++++++++++ src/event_loop/event_loop.c | 12 ++-- src/event_loop/event_loop_conn.c | 23 ++----- src/event_loop/event_loop_io.c | 50 ++-------------- src/http_loop/http_loop.c | 60 +++++++++++++++++++ .../http_loop_consts.c} | 3 - .../http_loop_parse.c} | 0 .../http_loop_route.c} | 0 .../http_loop_util.c} | 1 + src/main.c | 6 +- 12 files changed, 136 insertions(+), 112 deletions(-) create mode 100644 include/http_loop.h create mode 100644 src/http_loop/http_loop.c rename src/{http/http_consts.c => http_loop/http_loop_consts.c} (78%) rename src/{http/http_parse.c => http_loop/http_loop_parse.c} (100%) rename src/{http/http_route.c => http_loop/http_loop_route.c} (100%) rename src/{http/http_util.c => http_loop/http_loop_util.c} (95%) diff --git a/.gitignore b/.gitignore index 240d06c..c015b60 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,6 @@ build/ compile_commands.json # Data files -lander.data +lander.data* pastes/ .cache/ diff --git a/include/event_loop.h b/include/event_loop.h index 9376ece..1830cb2 100644 --- a/include/event_loop.h +++ b/include/event_loop.h @@ -5,27 +5,9 @@ #include #include -#include "http_req.h" -#include "trie.h" - // Size of the read and write buffers for each connection, in bytes #define EVENT_LOOP_BUFFER_SIZE 1024 -typedef struct event_loop_gctx { - Trie *trie; -} event_loop_gctx; - -event_loop_gctx *event_loop_gctx_init(); - -typedef struct event_loop_ctx { - http_request req; - event_loop_gctx *g; -} event_loop_ctx; - -event_loop_ctx *event_loop_ctx_init(event_loop_gctx *g); - -void event_loop_ctx_free(event_loop_ctx *ctx); - /** * Represents an active connection managed by the event loop */ @@ -50,46 +32,52 @@ typedef struct event_loop_conn { // If true, the server will close the connection after the final write buffer // has been written bool close_after_write; - event_loop_ctx *ctx; + void *ctx; } event_loop_conn; -/* - * Initialize a new event_loop_conn struct - */ -event_loop_conn *event_loop_conn_init(event_loop_gctx *g); - -void event_loop_conn_free(event_loop_conn *conn); - -/* - * Process data on a connection - */ -void event_loop_conn_io(event_loop_conn *conn); - /* * Main struct object representing the event loop */ typedef struct event_loop { event_loop_conn **connections; size_t connection_count; - event_loop_gctx *gctx; + // Global context passed to every connection + void *gctx; + // Function to initialize a context for a connection + void *(*ctx_init)(void *gctx); + // Function to free a context for a connection + void (*ctx_free)(void *ctx); + bool (*handle_data)(event_loop_conn *conn); } event_loop; +/* + * Initialize a new event_loop_conn struct + */ +event_loop_conn *event_loop_conn_init(event_loop *el); + +void event_loop_conn_free(event_loop *el, event_loop_conn *conn); + +/* + * Process data on a connection + */ +void event_loop_conn_io(event_loop *el, event_loop_conn *conn); + /* * Initialize a new event_loop struct */ -event_loop *event_loop_init(event_loop_gctx *g); +event_loop *event_loop_init(); /* * Place a new connection into the event loop's internal array. * * Returns -1 if the internal realloc failed */ -int event_loop_put(event_loop *loop, event_loop_conn *conn); +int event_loop_put(event_loop *el, event_loop_conn *conn); /** * Accept a new connection for the given file descriptor. */ -int event_loop_accept(event_loop *loop, int fd); +int event_loop_accept(event_loop *el, int fd); /* * Run the event loop. This function never returns. diff --git a/include/http_loop.h b/include/http_loop.h new file mode 100644 index 0000000..8291c81 --- /dev/null +++ b/include/http_loop.h @@ -0,0 +1,33 @@ +#ifndef LANDER_HTTP_LOOP +#define LANDER_HTTP_LOOP + +#include "event_loop.h" +#include "http_req.h" +#include "trie.h" + +/* + * Global context passed to every connection using the same pointer + */ +typedef struct http_loop_gctx { + Trie *trie; +} http_loop_gctx; + +http_loop_gctx *http_loop_gctx_init(); + +/* + * Invidivual context initialized for every connection + */ +typedef struct http_loop_ctx { + http_request req; + http_loop_gctx *g; +} http_loop_ctx; + +http_loop_ctx *http_loop_ctx_init(http_loop_gctx *g); + +void http_loop_ctx_free(http_loop_ctx *ctx); + +bool http_loop_handle_request(event_loop_conn *conn); + +event_loop *http_loop_init(http_loop_gctx *gctx); + +#endif diff --git a/src/event_loop/event_loop.c b/src/event_loop/event_loop.c index 4857d00..f648ca5 100644 --- a/src/event_loop/event_loop.c +++ b/src/event_loop/event_loop.c @@ -23,13 +23,12 @@ static int event_loop_fd_set_nb(int fd) { return 0; } -event_loop *event_loop_init(event_loop_gctx *gctx) { +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; - el->gctx = gctx; return el; } @@ -71,7 +70,7 @@ int event_loop_accept(event_loop *el, int fd) { } // creating the struct Conn - event_loop_conn *conn = event_loop_conn_init(el->gctx); + event_loop_conn *conn = event_loop_conn_init(el); // Close the connectoin if we fail to allocate a connection struct if (conn == NULL) { @@ -91,6 +90,8 @@ int event_loop_accept(event_loop *el, int fd) { return -4; } + debug("Connection established on fd %i", connfd); + return 0; } @@ -182,7 +183,7 @@ void event_loop_run(event_loop *el, int port) { if (poll_args[i].revents) { conn = el->connections[poll_args[i].fd]; - event_loop_conn_io(conn); + event_loop_conn_io(el, conn); if (conn->state == event_loop_conn_state_end) { // client closed normally, or something bad happened. @@ -190,7 +191,8 @@ void event_loop_run(event_loop *el, int port) { el->connections[conn->fd] = NULL; close(conn->fd); - event_loop_conn_free(conn); + debug("Connection closed on fd %i", conn->fd); + event_loop_conn_free(el, conn); } } } diff --git a/src/event_loop/event_loop_conn.c b/src/event_loop/event_loop_conn.c index 1c84e01..4791d8f 100644 --- a/src/event_loop/event_loop_conn.c +++ b/src/event_loop/event_loop_conn.c @@ -1,28 +1,13 @@ #include "event_loop.h" -event_loop_gctx *event_loop_gctx_init() { - event_loop_gctx *gctx = calloc(sizeof(event_loop_gctx), 1); - - return gctx; -} - -event_loop_ctx *event_loop_ctx_init(event_loop_gctx *g) { - event_loop_ctx *ctx = calloc(sizeof(event_loop_ctx), 1); - ctx->g = g; - - return ctx; -} - -void event_loop_ctx_free(event_loop_ctx *ctx) { free(ctx); } - -event_loop_conn *event_loop_conn_init(event_loop_gctx *g) { +event_loop_conn *event_loop_conn_init(event_loop *el) { event_loop_conn *conn = calloc(sizeof(event_loop_conn), 1); - conn->ctx = event_loop_ctx_init(g); + conn->ctx = el->ctx_init(el->gctx); return conn; } -void event_loop_conn_free(event_loop_conn *conn) { - event_loop_ctx_free(conn->ctx); +void event_loop_conn_free(event_loop *el, event_loop_conn *conn) { + el->ctx_free(conn->ctx); free(conn); } diff --git a/src/event_loop/event_loop_io.c b/src/event_loop/event_loop_io.c index da8e940..ab116d6 100644 --- a/src/event_loop/event_loop_io.c +++ b/src/event_loop/event_loop_io.c @@ -1,19 +1,8 @@ #include -#include -#include -#include -#include -#include -#include -#include #include -#include #include -#include "picohttpparser.h" - #include "event_loop.h" -#include "http.h" void event_loop_conn_io_res(event_loop_conn *conn) { while (1) { @@ -52,43 +41,13 @@ void event_loop_conn_io_res(event_loop_conn *conn) { } } -bool event_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_parse_error res = http_parse_request( - &conn->ctx->req, (const char *)&conn->rbuf[conn->rbuf_read], - conn->rbuf_size - conn->rbuf_read); - - if (res == http_parse_error_ok) { - // Perform the request - http_route(conn); - } - // Both in the case of an invalid HTTP request or one that's larger than the - // read buffer, we cannot determine when the next, possibly valid, HTTP - // request begins in the data stream. Therefore, we close the connection, - // even if additional pipelined requests are incoming. - else 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; - } - - // 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; -} - /** * 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_conn *conn) { +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], @@ -135,23 +94,22 @@ void event_loop_conn_io_req(event_loop_conn *conn) { conn->rbuf_size += (size_t)res; // This loop allows processing multiple requests from a single read buffer - while (event_loop_handle_request(conn)) + 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_conn *conn) { +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(conn); + event_loop_conn_io_req(el, conn); break; case event_loop_conn_state_res: event_loop_conn_io_res(conn); break; case event_loop_conn_state_end: - printf("we shouldn't be here\n"); break; } } diff --git a/src/http_loop/http_loop.c b/src/http_loop/http_loop.c new file mode 100644 index 0000000..7f796de --- /dev/null +++ b/src/http_loop/http_loop.c @@ -0,0 +1,60 @@ +#include "http_loop.h" +#include "http.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; + + return ctx; +} + +void http_loop_ctx_free(http_loop_ctx *ctx) { free(ctx); } + +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; + + http_parse_error res = + http_parse_request(&ctx->req, (const char *)&conn->rbuf[conn->rbuf_read], + conn->rbuf_size - conn->rbuf_read); + + if (res == http_parse_error_ok) { + // Perform the request + http_route(conn); + } + // Both in the case of an invalid HTTP request or one that's larger than the + // read buffer, we cannot determine when the next, possibly valid, HTTP + // request begins in the data stream. Therefore, we close the connection, + // even if additional pipelined requests are incoming. + else 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; + } + + // 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_loop_gctx *gctx) { + 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->gctx = gctx; + + return el; +} diff --git a/src/http/http_consts.c b/src/http_loop/http_loop_consts.c similarity index 78% rename from src/http/http_consts.c rename to src/http_loop/http_loop_consts.c index b929c6d..9190d0a 100644 --- a/src/http/http_consts.c +++ b/src/http_loop/http_loop_consts.c @@ -1,16 +1,13 @@ #include "http.h" const char http_404[] = "HTTP/1.1 404 Not Found\n" - "Connection: close\n" "Content-Length: 0\n\n"; const size_t http_404_len = sizeof(http_404) - 1; const char http_405[] = "HTTP/1.1 405 Method Not Allowed\n" - "Connection: close\n" "Content-Length: 0\n\n"; const size_t http_405_len = sizeof(http_405) - 1; const char http_500[] = "HTTP/1.1 500 Internal Server Error\n" - "Connection: close\n" "Content-Length: 0\n\n"; const size_t http_500_len = sizeof(http_500) - 1; diff --git a/src/http/http_parse.c b/src/http_loop/http_loop_parse.c similarity index 100% rename from src/http/http_parse.c rename to src/http_loop/http_loop_parse.c diff --git a/src/http/http_route.c b/src/http_loop/http_loop_route.c similarity index 100% rename from src/http/http_route.c rename to src/http_loop/http_loop_route.c diff --git a/src/http/http_util.c b/src/http_loop/http_loop_util.c similarity index 95% rename from src/http/http_util.c rename to src/http_loop/http_loop_util.c index 4b916e7..0289b62 100644 --- a/src/http/http_util.c +++ b/src/http_loop/http_loop_util.c @@ -1,4 +1,5 @@ #include "http.h" +#include "string.h" void http_write_standard_response(event_loop_conn *conn, http_response_type type) { diff --git a/src/main.c b/src/main.c index 5bbe18d..3fa415d 100644 --- a/src/main.c +++ b/src/main.c @@ -1,6 +1,6 @@ #include -#include "event_loop.h" +#include "http_loop.h" #include "log.h" int main() { @@ -17,9 +17,9 @@ int main() { info("Trie initialized and populated with %i entries", count); - event_loop_gctx *gctx = event_loop_gctx_init(); + http_loop_gctx *gctx = http_loop_gctx_init(); gctx->trie = trie; - event_loop *el = event_loop_init(gctx); + event_loop *el = http_loop_init(gctx); event_loop_run(el, 8000); }