feat: decouple event loop from http
parent
f3fced908f
commit
68f9227436
|
@ -2,6 +2,6 @@ build/
|
||||||
compile_commands.json
|
compile_commands.json
|
||||||
|
|
||||||
# Data files
|
# Data files
|
||||||
lander.data
|
lander.data*
|
||||||
pastes/
|
pastes/
|
||||||
.cache/
|
.cache/
|
||||||
|
|
|
@ -5,27 +5,9 @@
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.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
|
||||||
*/
|
*/
|
||||||
|
@ -50,46 +32,52 @@ 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;
|
||||||
event_loop_ctx *ctx;
|
void *ctx;
|
||||||
} event_loop_conn;
|
} 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
|
* 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;
|
// 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;
|
} 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
|
* 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.
|
* Place a new connection into the event loop's internal array.
|
||||||
*
|
*
|
||||||
* Returns -1 if the internal realloc failed
|
* 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.
|
* 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.
|
* Run the event loop. This function never returns.
|
||||||
|
|
|
@ -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
|
|
@ -23,13 +23,12 @@ static int event_loop_fd_set_nb(int fd) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
event_loop *event_loop_init(event_loop_gctx *gctx) {
|
event_loop *event_loop_init() {
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
@ -71,7 +70,7 @@ int event_loop_accept(event_loop *el, int fd) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// creating the struct Conn
|
// 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
|
// Close the connectoin if we fail to allocate a connection struct
|
||||||
if (conn == NULL) {
|
if (conn == NULL) {
|
||||||
|
@ -91,6 +90,8 @@ int event_loop_accept(event_loop *el, int fd) {
|
||||||
return -4;
|
return -4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debug("Connection established on fd %i", connfd);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,7 +183,7 @@ void event_loop_run(event_loop *el, int port) {
|
||||||
if (poll_args[i].revents) {
|
if (poll_args[i].revents) {
|
||||||
conn = el->connections[poll_args[i].fd];
|
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) {
|
if (conn->state == event_loop_conn_state_end) {
|
||||||
// client closed normally, or something bad happened.
|
// 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;
|
el->connections[conn->fd] = NULL;
|
||||||
|
|
||||||
close(conn->fd);
|
close(conn->fd);
|
||||||
event_loop_conn_free(conn);
|
debug("Connection closed on fd %i", conn->fd);
|
||||||
|
event_loop_conn_free(el, conn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +1,13 @@
|
||||||
#include "event_loop.h"
|
#include "event_loop.h"
|
||||||
|
|
||||||
event_loop_gctx *event_loop_gctx_init() {
|
event_loop_conn *event_loop_conn_init(event_loop *el) {
|
||||||
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 *conn = calloc(sizeof(event_loop_conn), 1);
|
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;
|
return conn;
|
||||||
}
|
}
|
||||||
|
|
||||||
void event_loop_conn_free(event_loop_conn *conn) {
|
void event_loop_conn_free(event_loop *el, event_loop_conn *conn) {
|
||||||
event_loop_ctx_free(conn->ctx);
|
el->ctx_free(conn->ctx);
|
||||||
free(conn);
|
free(conn);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,8 @@
|
||||||
#include <errno.h>
|
#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 <string.h>
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <unistd.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) {
|
void event_loop_conn_io_res(event_loop_conn *conn) {
|
||||||
while (1) {
|
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
|
* Read new data into the read buffer. This command performs at most one
|
||||||
* successful read syscall.
|
* successful read syscall.
|
||||||
*
|
*
|
||||||
* Returns whether the function should be retried immediately or not.
|
* 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 {
|
do {
|
||||||
// Move remaining data to start of buffer
|
// Move remaining data to start of buffer
|
||||||
memmove(conn->rbuf, &conn->rbuf[conn->rbuf_read],
|
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;
|
conn->rbuf_size += (size_t)res;
|
||||||
|
|
||||||
// This loop allows processing multiple requests from a single read buffer
|
// 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
|
// We can keep reading as long as we're in request mode
|
||||||
while (conn->state == event_loop_conn_state_req);
|
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) {
|
switch (conn->state) {
|
||||||
case event_loop_conn_state_req:
|
case event_loop_conn_state_req:
|
||||||
event_loop_conn_io_req(conn);
|
event_loop_conn_io_req(el, conn);
|
||||||
break;
|
break;
|
||||||
case event_loop_conn_state_res:
|
case event_loop_conn_state_res:
|
||||||
event_loop_conn_io_res(conn);
|
event_loop_conn_io_res(conn);
|
||||||
break;
|
break;
|
||||||
case event_loop_conn_state_end:
|
case event_loop_conn_state_end:
|
||||||
printf("we shouldn't be here\n");
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -1,16 +1,13 @@
|
||||||
#include "http.h"
|
#include "http.h"
|
||||||
|
|
||||||
const char http_404[] = "HTTP/1.1 404 Not Found\n"
|
const char http_404[] = "HTTP/1.1 404 Not Found\n"
|
||||||
"Connection: close\n"
|
|
||||||
"Content-Length: 0\n\n";
|
"Content-Length: 0\n\n";
|
||||||
const size_t http_404_len = sizeof(http_404) - 1;
|
const size_t http_404_len = sizeof(http_404) - 1;
|
||||||
|
|
||||||
const char http_405[] = "HTTP/1.1 405 Method Not Allowed\n"
|
const char http_405[] = "HTTP/1.1 405 Method Not Allowed\n"
|
||||||
"Connection: close\n"
|
|
||||||
"Content-Length: 0\n\n";
|
"Content-Length: 0\n\n";
|
||||||
const size_t http_405_len = sizeof(http_405) - 1;
|
const size_t http_405_len = sizeof(http_405) - 1;
|
||||||
|
|
||||||
const char http_500[] = "HTTP/1.1 500 Internal Server Error\n"
|
const char http_500[] = "HTTP/1.1 500 Internal Server Error\n"
|
||||||
"Connection: close\n"
|
|
||||||
"Content-Length: 0\n\n";
|
"Content-Length: 0\n\n";
|
||||||
const size_t http_500_len = sizeof(http_500) - 1;
|
const size_t http_500_len = sizeof(http_500) - 1;
|
|
@ -1,4 +1,5 @@
|
||||||
#include "http.h"
|
#include "http.h"
|
||||||
|
#include "string.h"
|
||||||
|
|
||||||
void http_write_standard_response(event_loop_conn *conn,
|
void http_write_standard_response(event_loop_conn *conn,
|
||||||
http_response_type type) {
|
http_response_type type) {
|
|
@ -1,6 +1,6 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
#include "event_loop.h"
|
#include "http_loop.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
|
@ -17,9 +17,9 @@ int main() {
|
||||||
|
|
||||||
info("Trie initialized and populated with %i entries", count);
|
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;
|
gctx->trie = trie;
|
||||||
event_loop *el = event_loop_init(gctx);
|
event_loop *el = http_loop_init(gctx);
|
||||||
|
|
||||||
event_loop_run(el, 8000);
|
event_loop_run(el, 8000);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue