From dfd27b579dcbc9f17f4ed248e4dfc2be3ffa8905 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Fri, 26 May 2023 22:12:53 +0200 Subject: [PATCH] feat: add context information to requests --- Makefile | 2 +- include/event_loop.h | 56 +++++++---- src/event_loop/event_loop.c | 12 +-- src/event_loop/event_loop_conn.c | 167 +++---------------------------- src/event_loop/event_loop_http.c | 0 src/event_loop/event_loop_io.c | 157 +++++++++++++++++++++++++++++ src/http/http_route.c | 14 ++- src/main.c | 16 ++- 8 files changed, 241 insertions(+), 183 deletions(-) delete mode 100644 src/event_loop/event_loop_http.c create mode 100644 src/event_loop/event_loop_io.c diff --git a/Makefile b/Makefile index 43ad4a7..abde5d1 100644 --- a/Makefile +++ b/Makefile @@ -48,7 +48,7 @@ objs: $(OBJS) .PHONY: bin bin: $(BIN) $(BIN): $(OBJS) - $(CC) $(INTERNALCFLAGS) -o $@ $^ + $(CC) $(INTERNALCFLAGS) $(LDFLAGS) -o $@ $^ $(BUILD_DIR)/$(SRC_DIR)/%.c.o: $(SRC_DIR)/%.c mkdir -p $(dir $@) diff --git a/include/event_loop.h b/include/event_loop.h index 971addc..9376ece 100644 --- a/include/event_loop.h +++ b/include/event_loop.h @@ -6,36 +6,35 @@ #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 */ -typedef struct event_loop_conn event_loop_conn; - typedef enum { event_loop_conn_state_req = 0, event_loop_conn_state_res = 1, event_loop_conn_state_end = 2, } event_loop_conn_state; -/* - * Main struct object representing the event loop - */ -typedef struct event_loop event_loop; - -/* - * Initialize a new event loop - */ -event_loop *event_loop_init(); - -/* - * Run the event loop. This function never returns. - */ -void event_loop_run(event_loop *el, int port); - typedef struct event_loop_conn { int fd; event_loop_conn_state state; @@ -51,24 +50,34 @@ 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; - /* void (*process_func)(struct event_loop_conn *); */ - http_request req; + event_loop_ctx *ctx; } event_loop_conn; /* * Initialize a new event_loop_conn struct */ -event_loop_conn *event_loop_conn_init(); +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; } event_loop; /* * Initialize a new event_loop struct */ -event_loop *event_loop_init(); +event_loop *event_loop_init(event_loop_gctx *g); /* * Place a new connection into the event loop's internal array. @@ -82,6 +91,9 @@ int event_loop_put(event_loop *loop, event_loop_conn *conn); */ int event_loop_accept(event_loop *loop, int fd); -void event_loop_conn_io(event_loop_conn *conn); +/* + * Run the event loop. This function never returns. + */ +void event_loop_run(event_loop *el, int port); #endif diff --git a/src/event_loop/event_loop.c b/src/event_loop/event_loop.c index a58585a..4857d00 100644 --- a/src/event_loop/event_loop.c +++ b/src/event_loop/event_loop.c @@ -12,7 +12,6 @@ #include "event_loop.h" #include "log.h" -#include "picohttpparser.h" static int event_loop_fd_set_nb(int fd) { int flags = fcntl(fd, F_GETFL); @@ -24,12 +23,13 @@ static int event_loop_fd_set_nb(int fd) { return 0; } -event_loop *event_loop_init() { +event_loop *event_loop_init(event_loop_gctx *gctx) { 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; } @@ -52,7 +52,7 @@ int event_loop_put(event_loop *el, event_loop_conn *conn) { return 0; } -int event_loop_accept(event_loop *loop, int fd) { +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); @@ -71,7 +71,7 @@ int event_loop_accept(event_loop *loop, int fd) { } // creating the struct Conn - event_loop_conn *conn = calloc(sizeof(event_loop_conn), 1); + event_loop_conn *conn = event_loop_conn_init(el->gctx); // Close the connectoin if we fail to allocate a connection struct if (conn == NULL) { @@ -83,7 +83,7 @@ int event_loop_accept(event_loop *loop, int fd) { conn->fd = connfd; conn->state = event_loop_conn_state_req; - res = event_loop_put(loop, conn); + res = event_loop_put(el, conn); if (res != 0) { close(connfd); @@ -190,7 +190,7 @@ void event_loop_run(event_loop *el, int port) { el->connections[conn->fd] = NULL; close(conn->fd); - free(conn); + event_loop_conn_free(conn); } } } diff --git a/src/event_loop/event_loop_conn.c b/src/event_loop/event_loop_conn.c index 745c496..1c84e01 100644 --- a/src/event_loop/event_loop_conn.c +++ b/src/event_loop/event_loop_conn.c @@ -1,165 +1,28 @@ -#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) { - ssize_t res = 0; - size_t remain = conn->wbuf_size - conn->wbuf_sent; +event_loop_gctx *event_loop_gctx_init() { + event_loop_gctx *gctx = calloc(sizeof(event_loop_gctx), 1); - 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 a response, we switch back to request mode to process a - // next request - if (conn->wbuf_sent == conn->wbuf_size) { - conn->state = conn->close_after_write ? event_loop_conn_state_end - : event_loop_conn_state_req; - /* c->wbuf_sent = 0; */ - /* c->wbuf_size = 0; */ - - return; - } - } + return gctx; } -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; - } +event_loop_ctx *event_loop_ctx_init(event_loop_gctx *g) { + event_loop_ctx *ctx = calloc(sizeof(event_loop_ctx), 1); + ctx->g = g; - http_parse_error res = - http_parse_request(&conn->req, (const char *)&conn->rbuf[conn->rbuf_read], - conn->rbuf_size - conn->rbuf_read); - - if (res == http_parse_error_ok) { - conn->rbuf_read += conn->req.len; - - memcpy(conn->wbuf, http_404, http_404_len); - - conn->state = event_loop_conn_state_res; - conn->wbuf_size = http_404_len; - conn->wbuf_sent = 0; - - /* event_loop_conn_io_res(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; + return ctx; } -/** - * 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) { - 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; +void event_loop_ctx_free(event_loop_ctx *ctx) { free(ctx); } - ssize_t res; - size_t cap = EVENT_LOOP_BUFFER_SIZE - conn->rbuf_size; +event_loop_conn *event_loop_conn_init(event_loop_gctx *g) { + event_loop_conn *conn = calloc(sizeof(event_loop_conn), 1); + conn->ctx = event_loop_ctx_init(g); - // 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 < 0) { - conn->state = event_loop_conn_state_end; - - return; - } - - // Output of 0 and no error means we've reached the end of the input. We run - // try_one_request one more time and close the connection if this doesn't - // change the result. - if (res == 0) { - conn->state = event_loop_conn_state_end; - - return; - } - - // We switch to processing mode if we've reached the end of the data stream, - // or if the read buffer is filled - /* if (res == 0 || c->rbuf_size == MAX_MSG_SIZE) { */ - /* c->state = STATE_PROCESS; */ - /* return false; */ - /* } */ - - conn->rbuf_size += (size_t)res; - - // This loop allows processing multiple requests from a single read buffer - while (event_loop_handle_request(conn)) - ; - } - // We can keep reading as long as we're in request mode - while (conn->state == event_loop_conn_state_req); + return conn; } -void event_loop_conn_io(event_loop_conn *conn) { - switch (conn->state) { - case event_loop_conn_state_req: - event_loop_conn_io_req(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; - } +void event_loop_conn_free(event_loop_conn *conn) { + event_loop_ctx_free(conn->ctx); + free(conn); } diff --git a/src/event_loop/event_loop_http.c b/src/event_loop/event_loop_http.c deleted file mode 100644 index e69de29..0000000 diff --git a/src/event_loop/event_loop_io.c b/src/event_loop/event_loop_io.c new file mode 100644 index 0000000..da8e940 --- /dev/null +++ b/src/event_loop/event_loop_io.c @@ -0,0 +1,157 @@ +#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) { + 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 a response, we switch back to request mode to process a + // next request + if (conn->wbuf_sent == conn->wbuf_size) { + conn->state = conn->close_after_write ? event_loop_conn_state_end + : event_loop_conn_state_req; + /* c->wbuf_sent = 0; */ + /* c->wbuf_size = 0; */ + + return; + } + } +} + +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) { + 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 < 0) { + conn->state = event_loop_conn_state_end; + + return; + } + + // Output of 0 and no error means we've reached the end of the input. We run + // try_one_request one more time and close the connection if this doesn't + // change the result. + if (res == 0) { + conn->state = event_loop_conn_state_end; + + return; + } + + // We switch to processing mode if we've reached the end of the data stream, + // or if the read buffer is filled + /* if (res == 0 || c->rbuf_size == MAX_MSG_SIZE) { */ + /* c->state = STATE_PROCESS; */ + /* return false; */ + /* } */ + + conn->rbuf_size += (size_t)res; + + // This loop allows processing multiple requests from a single read buffer + while (event_loop_handle_request(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) { + switch (conn->state) { + case event_loop_conn_state_req: + event_loop_conn_io_req(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/http_route.c b/src/http/http_route.c index b6765d9..38627a6 100644 --- a/src/http/http_route.c +++ b/src/http/http_route.c @@ -1,5 +1,17 @@ +#include + #include "event_loop.h" +#include "http.h" typedef void (*routing_func)(event_loop_conn *); -routing_func http_route(); +void http_route(event_loop_conn *conn) { + // TODO routing + + // Fallthrough is to return a 404 + memcpy(conn->wbuf, http_404, http_404_len); + + conn->state = event_loop_conn_state_res; + conn->wbuf_size = http_404_len; + conn->wbuf_sent = 0; +} diff --git a/src/main.c b/src/main.c index 57e0881..5bbe18d 100644 --- a/src/main.c +++ b/src/main.c @@ -1,11 +1,25 @@ #include #include "event_loop.h" +#include "log.h" int main() { setvbuf(stdout, NULL, _IONBF, 0); - event_loop *el = event_loop_init(); + info("Initializing trie"); + + Trie *trie = trie_init(); + int count = trie_populate(trie, "lander.data"); + + if (count < 0) { + critical(1, "An error occured while populating the trie."); + } + + info("Trie initialized and populated with %i entries", count); + + event_loop_gctx *gctx = event_loop_gctx_init(); + gctx->trie = trie; + event_loop *el = event_loop_init(gctx); event_loop_run(el, 8000); }