feat: add context information to requests
This commit is contained in:
parent
6d4b94c55e
commit
dfd27b579d
8 changed files with 241 additions and 183 deletions
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 "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);
|
||||
}
|
||||
|
|
|
|||
157
src/event_loop/event_loop_io.c
Normal file
157
src/event_loop/event_loop_io.c
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,17 @@
|
|||
#include <string.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
|
|
|||
16
src/main.c
16
src/main.c
|
|
@ -1,11 +1,25 @@
|
|||
#include <stdio.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue