feat: add context information to requests

c-web-server
Jef Roosens 2023-05-26 22:12:53 +02:00
parent 6d4b94c55e
commit dfd27b579d
Signed by: Jef Roosens
GPG Key ID: B75D4F293C7052DB
8 changed files with 241 additions and 183 deletions

View File

@ -48,7 +48,7 @@ objs: $(OBJS)
.PHONY: bin .PHONY: bin
bin: $(BIN) bin: $(BIN)
$(BIN): $(OBJS) $(BIN): $(OBJS)
$(CC) $(INTERNALCFLAGS) -o $@ $^ $(CC) $(INTERNALCFLAGS) $(LDFLAGS) -o $@ $^
$(BUILD_DIR)/$(SRC_DIR)/%.c.o: $(SRC_DIR)/%.c $(BUILD_DIR)/$(SRC_DIR)/%.c.o: $(SRC_DIR)/%.c
mkdir -p $(dir $@) mkdir -p $(dir $@)

View File

@ -6,36 +6,35 @@
#include <stdlib.h> #include <stdlib.h>
#include "http_req.h" #include "http_req.h"
#include "trie.h"
// Size of the read and write buffers for each connection, in bytes // Size of the read and write buffers for each connection, in bytes
#define EVENT_LOOP_BUFFER_SIZE 1024 #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 * Represents an active connection managed by the event loop
*/ */
typedef struct event_loop_conn event_loop_conn;
typedef enum { typedef enum {
event_loop_conn_state_req = 0, event_loop_conn_state_req = 0,
event_loop_conn_state_res = 1, event_loop_conn_state_res = 1,
event_loop_conn_state_end = 2, event_loop_conn_state_end = 2,
} event_loop_conn_state; } 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 { typedef struct event_loop_conn {
int fd; int fd;
event_loop_conn_state state; 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 // If true, the server will close the connection after the final write buffer
// has been written // has been written
bool close_after_write; bool close_after_write;
/* void (*process_func)(struct event_loop_conn *); */ event_loop_ctx *ctx;
http_request req;
} event_loop_conn; } event_loop_conn;
/* /*
* Initialize a new event_loop_conn struct * 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 { typedef struct event_loop {
event_loop_conn **connections; event_loop_conn **connections;
size_t connection_count; size_t connection_count;
event_loop_gctx *gctx;
} event_loop; } event_loop;
/* /*
* Initialize a new event_loop struct * 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. * 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); 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 #endif

View File

@ -12,7 +12,6 @@
#include "event_loop.h" #include "event_loop.h"
#include "log.h" #include "log.h"
#include "picohttpparser.h"
static int event_loop_fd_set_nb(int fd) { static int event_loop_fd_set_nb(int fd) {
int flags = fcntl(fd, F_GETFL); int flags = fcntl(fd, F_GETFL);
@ -24,12 +23,13 @@ static int event_loop_fd_set_nb(int fd) {
return 0; return 0;
} }
event_loop *event_loop_init() { event_loop *event_loop_init(event_loop_gctx *gctx) {
event_loop *el = calloc(sizeof(event_loop), 1); event_loop *el = calloc(sizeof(event_loop), 1);
// No idea if this is a good starter value // No idea if this is a good starter value
el->connections = calloc(sizeof(event_loop_conn), 16); el->connections = calloc(sizeof(event_loop_conn), 16);
el->connection_count = 16; el->connection_count = 16;
el->gctx = gctx;
return el; return el;
} }
@ -52,7 +52,7 @@ int event_loop_put(event_loop *el, event_loop_conn *conn) {
return 0; 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; struct sockaddr_in client_addr;
socklen_t socklen = sizeof(client_addr); socklen_t socklen = sizeof(client_addr);
int connfd = accept(fd, (struct sockaddr *)&client_addr, &socklen); 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 // 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 // Close the connectoin if we fail to allocate a connection struct
if (conn == NULL) { if (conn == NULL) {
@ -83,7 +83,7 @@ int event_loop_accept(event_loop *loop, int fd) {
conn->fd = connfd; conn->fd = connfd;
conn->state = event_loop_conn_state_req; conn->state = event_loop_conn_state_req;
res = event_loop_put(loop, conn); res = event_loop_put(el, conn);
if (res != 0) { if (res != 0) {
close(connfd); close(connfd);
@ -190,7 +190,7 @@ void event_loop_run(event_loop *el, int port) {
el->connections[conn->fd] = NULL; el->connections[conn->fd] = NULL;
close(conn->fd); close(conn->fd);
free(conn); event_loop_conn_free(conn);
} }
} }
} }

View File

@ -1,165 +1,28 @@
#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 "picohttpparser.h"
#include "event_loop.h" #include "event_loop.h"
#include "http.h"
void event_loop_conn_io_res(event_loop_conn *conn) { event_loop_gctx *event_loop_gctx_init() {
while (1) { event_loop_gctx *gctx = calloc(sizeof(event_loop_gctx), 1);
ssize_t res = 0;
size_t remain = conn->wbuf_size - conn->wbuf_sent;
do { return gctx;
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 event_loop_ctx *event_loop_ctx_init(event_loop_gctx *g) {
// request event_loop_ctx *ctx = calloc(sizeof(event_loop_ctx), 1);
if (res < 0) { ctx->g = g;
conn->state = event_loop_conn_state_end;
return; return ctx;
} }
conn->wbuf_sent += (size_t)res; void event_loop_ctx_free(event_loop_ctx *ctx) { free(ctx); }
// After writing a response, we switch back to request mode to process a event_loop_conn *event_loop_conn_init(event_loop_gctx *g) {
// next request event_loop_conn *conn = calloc(sizeof(event_loop_conn), 1);
if (conn->wbuf_sent == conn->wbuf_size) { conn->ctx = event_loop_ctx_init(g);
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 conn;
}
}
} }
bool event_loop_handle_request(event_loop_conn *conn) { void event_loop_conn_free(event_loop_conn *conn) {
// Prevents the request handler function from looping indefinitely without event_loop_ctx_free(conn->ctx);
// ever consuming new data free(conn);
if (conn->rbuf_size - conn->rbuf_read == 0) {
return false;
}
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;
}
/**
* 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;
}
} }

View File

@ -0,0 +1,157 @@
#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 "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;
}
}

View File

@ -1,5 +1,17 @@
#include <string.h>
#include "event_loop.h" #include "event_loop.h"
#include "http.h"
typedef void (*routing_func)(event_loop_conn *); 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;
}

View File

@ -1,11 +1,25 @@
#include <stdio.h> #include <stdio.h>
#include "event_loop.h" #include "event_loop.h"
#include "log.h"
int main() { int main() {
setvbuf(stdout, NULL, _IONBF, 0); 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); event_loop_run(el, 8000);
} }