diff --git a/include/event_loop.h b/include/event_loop.h index 43d21a7..c18cd41 100644 --- a/include/event_loop.h +++ b/include/event_loop.h @@ -53,6 +53,8 @@ typedef struct event_loop { void (*ctx_free)(void *ctx); // Function that processes incoming data bool (*handle_data)(event_loop_conn *conn); + // Function that writes outgoing data + void (*write_data)(event_loop_conn *conn); } event_loop; /* diff --git a/include/http.h b/include/http.h index 54625ba..11a05e2 100644 --- a/include/http.h +++ b/include/http.h @@ -1,20 +1,13 @@ #ifndef HTTP #define HTTP +#include #include #include "picohttpparser.h" -#define HTTP_MAX_ALLOWED_HEADERS 8 +#define HTTP_MAX_ALLOWED_HEADERS 16 -extern const char http_404[]; -extern const size_t http_404_len; -extern const char http_405[]; -extern const size_t http_405_len; -extern const char http_500[]; -extern const size_t http_500_len; -extern const char http_501[]; -extern const size_t http_501_len; extern const char *http_response_type_names[5][32]; typedef enum http_request_method { @@ -120,9 +113,14 @@ typedef enum http_response_type { typedef struct http_response { http_response_type type; + const char *head; + size_t head_len; + size_t head_written; const char *body; size_t body_len; size_t body_written; + // If false, the body won't be freed + bool owns_body; } http_response; #endif diff --git a/include/http_loop.h b/include/http_loop.h index a928ae5..abca2f8 100644 --- a/include/http_loop.h +++ b/include/http_loop.h @@ -39,7 +39,6 @@ http_loop_gctx *http_loop_gctx_init(); typedef struct http_loop_ctx { http_request req; http_response res; - bool flush; http_route *route; size_t current_step; http_loop_gctx *g; @@ -62,12 +61,18 @@ void http_loop_ctx_free(http_loop_ctx *ctx); */ bool http_loop_handle_request(event_loop_conn *conn); +void http_loop_write_response(event_loop_conn *conn); + http_parse_error http_loop_parse_request(event_loop_conn *conn); -bool http_loop_route_request(event_loop_conn *conn); +void http_loop_route_request(event_loop_conn *conn); void http_loop_process_request(event_loop_conn *conn); +void http_loop_res_set_body(const char *body, size_t body_len); + +void http_loop_res_set_type(http_response_type type); + void http_loop_write_standard_response(event_loop_conn *conn, http_response_type type); diff --git a/src/event_loop/event_loop_io.c b/src/event_loop/event_loop_io.c index 233f852..674c6ec 100644 --- a/src/event_loop/event_loop_io.c +++ b/src/event_loop/event_loop_io.c @@ -4,8 +4,8 @@ #include "event_loop.h" -void event_loop_conn_io_res(event_loop_conn *conn) { - while (1) { +void event_loop_conn_io_res(event_loop *el, event_loop_conn *conn) { + do { ssize_t res = 0; size_t remain = conn->wbuf_size - conn->wbuf_sent; @@ -28,17 +28,15 @@ void event_loop_conn_io_res(event_loop_conn *conn) { conn->wbuf_sent += (size_t)res; - // After writing a response, we switch back to request mode to process a - // next request + // After writing the entire buffer, we run the write_data command to receive + // new data, or exit the loop 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; */ + conn->wbuf_sent = 0; + conn->wbuf_size = 0; - return; + el->write_data(conn); } - } + } while (conn->state == event_loop_conn_state_res); } /** @@ -93,7 +91,7 @@ void event_loop_conn_io(event_loop *el, event_loop_conn *conn) { event_loop_conn_io_req(el, conn); break; case event_loop_conn_state_res: - event_loop_conn_io_res(conn); + event_loop_conn_io_res(el, conn); break; case event_loop_conn_state_end:; } diff --git a/src/http/http.c b/src/http/http.c new file mode 100644 index 0000000..68ef55e --- /dev/null +++ b/src/http/http.c @@ -0,0 +1,6 @@ +#include "http.h" + +// Very important that this is in the same order as http_request_method +const char *request_method_names[] = {"GET", "POST", "PUT", "PATCH", "DELETE"}; +const size_t request_method_names_len = + sizeof(request_method_names) / sizeof(request_method_names[0]); diff --git a/src/http_loop/http_loop.c b/src/http_loop/http_loop.c index 240b684..b89005b 100644 --- a/src/http_loop/http_loop.c +++ b/src/http_loop/http_loop.c @@ -21,22 +21,22 @@ bool http_loop_handle_request(event_loop_conn *conn) { return false; } - // It's fun to respond with extremely specific error messages - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/501 - else if (res == http_parse_error_unknown_method) { - http_loop_write_standard_response(conn, 501); - return false; - } conn->rbuf_read += res; - // If the routing fails, we exit the handler - if (!http_loop_route_request(conn)) { - return false; + // It's fun to respond with extremely specific error messages + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/501 + if (res == http_parse_error_unknown_method) { + ctx->res.type = http_method_not_implemented; + conn->state = event_loop_conn_state_res; + } else { + http_loop_route_request(conn); } } - http_loop_process_request(conn); + if (conn->state == event_loop_conn_state_req) { + http_loop_process_request(conn); + } // TODO in highly concurrent situations, it might actually be better to always // return false here, as this allows cycling better through all connections @@ -49,6 +49,7 @@ event_loop *http_loop_init(http_loop_gctx *gctx) { 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->write_data = http_loop_write_response; el->gctx = gctx; return el; diff --git a/src/http_loop/http_loop_consts.c b/src/http_loop/http_loop_consts.c deleted file mode 100644 index a58cf48..0000000 --- a/src/http_loop/http_loop_consts.c +++ /dev/null @@ -1,21 +0,0 @@ -#include "http.h" - -const char http_404[] = "HTTP/1.1 404 Not Found\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" - "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" - "Content-Length: 0\n\n"; -const size_t http_500_len = sizeof(http_500) - 1; -const char http_501[] = "HTTP/1.1 501 Not Implemented\n" - "Content-Length: 0\n\n"; -const size_t http_501_len = sizeof(http_501) - 1; - -// Very important that this is in the same order as http_request_method -const char *request_method_names[] = {"GET", "POST", "PUT", "PATCH", "DELETE"}; -const size_t request_method_names_len = - sizeof(request_method_names) / sizeof(request_method_names[0]); diff --git a/src/http_loop/http_loop_ctx.c b/src/http_loop/http_loop_ctx.c index 3e08dd1..1efaa2e 100644 --- a/src/http_loop/http_loop_ctx.c +++ b/src/http_loop/http_loop_ctx.c @@ -14,10 +14,30 @@ http_loop_ctx *http_loop_ctx_init(http_loop_gctx *g) { return ctx; } -void http_loop_ctx_free(http_loop_ctx *ctx) { free(ctx); } +void http_loop_ctx_free(http_loop_ctx *ctx) { + http_loop_ctx_reset(ctx); + + free(ctx); +} void http_loop_ctx_reset(http_loop_ctx *ctx) { ctx->route = NULL; ctx->current_step = 0; - ctx->flush = false; + + if (ctx->res.head != NULL) { + free((void *)ctx->res.head); + ctx->res.head = NULL; + } + + if (ctx->res.owns_body && ctx->res.body != NULL) { + free((void *)ctx->res.body); + } + + ctx->res.body = NULL; + + ctx->res.head_len = 0; + ctx->res.head_written = 0; + ctx->res.body_len = 0; + ctx->res.body_written = 0; + ctx->res.owns_body = false; } diff --git a/src/http_loop/http_loop_route.c b/src/http_loop/http_loop_req.c similarity index 88% rename from src/http_loop/http_loop_route.c rename to src/http_loop/http_loop_req.c index ce248ac..bca88a3 100644 --- a/src/http_loop/http_loop_route.c +++ b/src/http_loop/http_loop_req.c @@ -71,24 +71,7 @@ http_parse_error http_loop_parse_request(event_loop_conn *conn) { return http_parse_error_ok; } -void http_loop_process_request(event_loop_conn *conn) { - http_loop_ctx *ctx = conn->ctx; - - // We keep processing step functions as long as they don't need to wait for - // I/O - while ((ctx->route->steps[ctx->current_step] != NULL) && - ctx->route->steps[ctx->current_step](conn)) { - ctx->current_step++; - } - - // If we've reached the end of the list of step functions, we report the - // request as finished by clearing its route - if (ctx->route->steps[ctx->current_step] == NULL) { - http_loop_ctx_reset(ctx); - } -} - -bool http_loop_route_request(event_loop_conn *conn) { +void http_loop_route_request(event_loop_conn *conn) { http_loop_ctx *ctx = conn->ctx; http_loop_gctx *gctx = ctx->g; @@ -104,7 +87,7 @@ bool http_loop_route_request(event_loop_conn *conn) { case http_route_literal: if (strncmp(route->path, ctx->req.path, ctx->req.path_len) == 0) { ctx->route = route; - return true; + return; } // TODO case http_route_regex:; @@ -112,7 +95,28 @@ bool http_loop_route_request(event_loop_conn *conn) { } // Fallthrough is to write a 404 - http_loop_write_standard_response(conn, http_not_found); - - return false; + ctx->res.type = http_not_found; + conn->state = event_loop_conn_state_res; +} + +void http_loop_process_request(event_loop_conn *conn) { + http_loop_ctx *ctx = conn->ctx; + + // We keep processing step functions as long as they don't need to wait for + // I/O + while ((conn->state == event_loop_conn_state_req) && + (ctx->route->steps[ctx->current_step] != NULL) && + ctx->route->steps[ctx->current_step](conn)) { + ctx->current_step++; + } + + if (conn->state != event_loop_conn_state_req) { + return; + } + + // If we've reached the end of the list of step functions, we report the + // request as finished by clearing its route + if (ctx->route->steps[ctx->current_step] == NULL) { + http_loop_ctx_reset(ctx); + } } diff --git a/src/http_loop/http_loop_res.c b/src/http_loop/http_loop_res.c new file mode 100644 index 0000000..b38efd6 --- /dev/null +++ b/src/http_loop/http_loop_res.c @@ -0,0 +1,58 @@ +#include + +#include "http_loop.h" +#include "log.h" + +#define MIN(x, y) (((x) < (y)) ? (x) : (y)) + +void http_loop_write_response(event_loop_conn *conn) { + http_response *res = &((http_loop_ctx *)conn->ctx)->res; + + // Create head response + if (res->head == NULL) { + const char *response_type_name = + http_response_type_names[res->type / 100 - 1][res->type % 100]; + + char *format = "HTTP/1.1 %i %s\n" + "Content-Length: %lu\n\n"; + + // Running snprintf with size 0 prevents it from writing any bytes, while + // still letting it calculate how many bytes it would have written + int buf_size = + snprintf(NULL, 0, format, res->type, response_type_name, res->body_len); + char *buf = malloc(buf_size + 1); + sprintf(buf, format, res->type, response_type_name, res->body_len); + + res->head = buf; + res->head_len = buf_size; + } + + // The final iteration marks the end of the response, after which we reset the + // context so a next request can be processed + if (res->head_written == res->head_len && + res->body_written == res->body_len) { + http_loop_ctx_reset(conn->ctx); + conn->state = event_loop_conn_state_req; + return; + } + + if (res->head_written < res->head_len) { + size_t bytes_to_write = MIN(res->head_len - res->head_written, + EVENT_LOOP_BUFFER_SIZE - conn->wbuf_size); + memcpy(&conn->wbuf[conn->wbuf_size], &res->head[res->head_written], + bytes_to_write); + + conn->wbuf_size += bytes_to_write; + res->head_written += bytes_to_write; + } + + if (res->body_written < res->body_len) { + size_t bytes_to_write = MIN(res->body_len - res->body_written, + EVENT_LOOP_BUFFER_SIZE - conn->wbuf_size); + memcpy(&conn->wbuf[conn->wbuf_size], &res->body[res->body_written], + bytes_to_write); + + conn->wbuf_size += bytes_to_write; + res->body_written += bytes_to_write; + } +} diff --git a/src/http_loop/http_loop_util.c b/src/http_loop/http_loop_util.c deleted file mode 100644 index 4bd25bc..0000000 --- a/src/http_loop/http_loop_util.c +++ /dev/null @@ -1,27 +0,0 @@ -#include "http_loop.h" -#include "string.h" - -void http_loop_write_standard_response(event_loop_conn *conn, - http_response_type type) { - const char *s; - size_t len; - - switch (type) { - case 404: - s = http_404; - len = http_404_len; - break; - case 405: - s = http_405; - len = http_405_len; - case 501: - s = http_501; - len = http_501_len; - } - - memcpy(conn->wbuf, s, len); - - conn->state = event_loop_conn_state_res; - conn->wbuf_size = len; - conn->wbuf_sent = 0; -} diff --git a/src/main.c b/src/main.c index ee31a15..3e3c77e 100644 --- a/src/main.c +++ b/src/main.c @@ -4,12 +4,23 @@ #include "http_loop.h" #include "log.h" -bool http_write_404(event_loop_conn *conn) { - memcpy(conn->wbuf, http_405, http_405_len); +const char index_page[] = + "\n" + "\n" + " \n" + "

r8r.be

\n" + "

This is the URL shortener and pastebin accompanying my site, The Rusty Bever.

\n" + " \n" + "\n"; +bool http_write_404(event_loop_conn *conn) { + http_loop_ctx *ctx = conn->ctx; + ctx->res.type = 200; + ctx->res.body = index_page; + ctx->res.body_len = sizeof(index_page) - 1; + ctx->res.owns_body = false; conn->state = event_loop_conn_state_res; - conn->wbuf_size = http_405_len; - conn->wbuf_sent = 0; return true; }