diff --git a/lnm/include/lnm/common.h b/lnm/include/lnm/common.h index 381b4c9..4dc789d 100644 --- a/lnm/include/lnm/common.h +++ b/lnm/include/lnm/common.h @@ -17,6 +17,9 @@ } \ } +#define LNM_MIN(x, y) ((x) < (y) ? (x) : (y)) +#define LNM_MAX(x, y) ((x) > (y) ? (x) : (y)) + typedef enum { lnm_err_ok = 0, lnm_err_failed_alloc, @@ -26,4 +29,12 @@ typedef enum { lnm_err_bad_regex } lnm_err; +typedef struct lnm_loop lnm_http_loop; + +typedef struct lnm_loop_conn lnm_http_conn; + +typedef struct lnm_http_step lnm_http_step; + +typedef struct lnm_http_route lnm_http_route; + #endif diff --git a/lnm/include/lnm/http/loop.h b/lnm/include/lnm/http/loop.h index de0edcd..12ff602 100644 --- a/lnm/include/lnm/http/loop.h +++ b/lnm/include/lnm/http/loop.h @@ -7,14 +7,6 @@ #include "lnm/http/req.h" #include "lnm/http/res.h" -typedef struct lnm_loop lnm_http_loop; - -typedef struct lnm_loop_conn lnm_http_conn; - -typedef struct lnm_http_step lnm_http_step; - -typedef struct lnm_http_route lnm_http_route; - typedef enum lnm_http_step_err { lnm_http_step_err_done = 0, lnm_http_step_err_io_needed, diff --git a/lnm/include/lnm/http/res.h b/lnm/include/lnm/http/res.h index 18ae914..8ea7636 100644 --- a/lnm/include/lnm/http/res.h +++ b/lnm/include/lnm/http/res.h @@ -2,9 +2,17 @@ #define LNM_HTTP_RES #include +#include +#include "lnm/common.h" #include "lnm/http/consts.h" +typedef lnm_err (*data_fn)(size_t *written, char *buf, lnm_http_conn *conn, + size_t offset, size_t len); + +/** + * Linked list elements used to store the response headers + */ typedef struct lnm_http_res_header { struct { char *s; @@ -19,12 +27,85 @@ typedef struct lnm_http_res_header { struct lnm_http_res_header *next; } lnm_http_res_header; +typedef enum lnm_http_res_body_type { + lnm_http_res_body_type_file = 0, + lnm_http_res_body_type_buf, + lnm_http_res_body_type_fn, +} lnm_http_res_body_type; + typedef struct lnm_http_res { lnm_http_status status; struct { lnm_http_res_header *head; lnm_http_res_header *current; } headers; + struct { + struct { + char *buf; + FILE *f; + data_fn fn; + } data; + size_t len; + bool owned; + lnm_http_res_body_type type; + } body; + // General-purpose; meaning depends on the current state + size_t written; } lnm_http_res; +/** + * Add a new header of a known type to the response + * + * @param type type of header + * @param value null-terminated string containing the value of the header + * @param value_owned whether to take ownership of the value pointer; if false, + * free'ing the buffer is the caller's responsibility + */ +lnm_err lnm_http_res_add_header(lnm_http_res *res, lnm_http_header type, + char *value, bool value_owned); + +/** + * Add a new header of a known type to the response with a given value length. + * + * @param type type of header + * @param value string of length `value_len` containing the value of the header + * @param value_len length of value + * @param value_owned whether to take ownership of the value pointer; if false, + * free'ing the buffer is the caller's responsibility + */ +lnm_err lnm_http_res_add_header_len(lnm_http_res *res, lnm_http_header type, + char *value, size_t value_len, + bool value_owned); + +/** + * Set the request body to the given file pointer. + * + * @param res response to modify + * @param f file pointer to use as data + * @param len expected length of the file + * @param owned whether to take ownership of the file pointer + */ +void lnm_http_res_body_set_file(lnm_http_res *res, FILE *f, size_t len, + bool owned); + +/** + * Set the request body to the given buffer. + * + * @param res response to modify + * @param buf buffer to use as data + * @param len length of the buffer + * @param owned whether to take ownership of the file pointer + */ +void lnm_http_res_body_set_buf(lnm_http_res *res, char *buf, size_t len, + bool owned); + +/** + * Set the request body to be read from the given data function. + * + * @param res response to modify + * @param fn data reader function + * @param len expected length of the response + */ +void lnm_http_res_body_set_fn(lnm_http_res *res, data_fn fn, size_t len); + #endif diff --git a/lnm/src/http/lnm_http_loop_process.c b/lnm/src/http/lnm_http_loop_process.c index 5fa4f21..c8aed94 100644 --- a/lnm/src/http/lnm_http_loop_process.c +++ b/lnm/src/http/lnm_http_loop_process.c @@ -1,4 +1,5 @@ #include +#include #include #include "lnm/http/consts.h" @@ -116,25 +117,139 @@ void lnm_http_loop_process_steps(lnm_http_conn *conn) { ctx->state = lnm_http_loop_state_first_res; break; } - } while ((step != ctx->cur_step) && (ctx->cur_step != NULL)); + } + // Loop until we either: + // - reach the end of the chain of steps, indicated by NULL + // - have a step that's waiting for I/O + while ((ctx->cur_step != NULL) && (step != ctx->cur_step)); if (ctx->cur_step == NULL) { ctx->state = lnm_http_loop_state_write_headers; } } +// This function is intentionally written inefficiently for now, as it will most +// likely only have to run once for each response +void lnm_http_loop_process_write_status_line(lnm_http_conn *conn) { + lnm_http_loop_ctx *ctx = conn->ctx; + lnm_http_res *res = &ctx->res; + + const char *response_type_name = + lnm_http_status_names[res->status / 100 - 1][res->status % 100]; + + // First we calculate the size of the start of the header + size_t buf_size = + snprintf(NULL, 0, "HTTP/1.1 %i %s\n", res->status, response_type_name); + char buf[buf_size + 1]; + sprintf(buf, "HTTP/1.1 %i %s\n", res->status, response_type_name); + + size_t to_write = + LNM_MIN(buf_size - res->written, LNM_LOOP_BUF_SIZE - conn->w.size); + memcpy(&conn->w.buf[conn->w.size], &buf[res->written], to_write); + + conn->w.size += to_write; + res->written += to_write; + + if (res->written == buf_size) { + res->written = 0; + ctx->state = lnm_http_loop_state_write_headers; + } +} + +void lnm_http_loop_process_write_headers(lnm_http_conn *conn) { + lnm_http_loop_ctx *ctx = conn->ctx; + lnm_http_res *res = &ctx->res; + + lnm_http_res_header *header = res->headers.current; + + // Loop as long as we can still write new data and have headers to write + while ((conn->w.size < LNM_LOOP_BUF_SIZE) && + ((header = res->headers.current) != NULL)) { + size_t buf_len = header->name.len + 2 + header->value.len + 1; + + // Here, we also constantly calculate the entire buffer as we assume each + // header will be written in one go + char buf[buf_len]; + memcpy(buf, header->name.s, header->name.len); + memcpy(&buf[header->name.len + 2], header->value.s, header->value.len); + buf[header->name.len] = ':'; + buf[header->name.len + 1] = ' '; + buf[buf_len - 1] = '\n'; + + size_t to_write = + LNM_MIN(buf_len - res->written, LNM_LOOP_BUF_SIZE - conn->w.size); + memcpy(&conn->w.buf[conn->w.size], &buf[res->written], to_write); + + conn->w.size += to_write; + res->written += to_write; + + if (res->written == buf_len) { + res->written = 0; + res->headers.current = res->headers.current->next; + } + } + + if (res->headers.current == NULL) { + ctx->state = ctx->res.body.len > 0 ? lnm_http_loop_state_write_body + : lnm_http_loop_state_finish; + } +} + +void lnm_http_loop_process_write_body(lnm_http_conn *conn) { + lnm_http_loop_ctx *ctx = conn->ctx; + lnm_http_res *res = &ctx->res; + + size_t to_write = + LNM_MIN(res->body.len - res->written, LNM_LOOP_BUF_SIZE - conn->w.size); + size_t written; + + switch (res->body.type) { + case lnm_http_res_body_type_buf: + memcpy(&conn->w.buf[conn->w.size], &res->body.data.buf[res->written], + to_write); + written = to_write; + break; + case lnm_http_res_body_type_file: + written = fread(&conn->w.buf[conn->w.size], 1, to_write, res->body.data.f); + + if ((written == 0) && (!ferror(res->body.data.f))) { + ctx->state = lnm_http_loop_state_finish; + } + break; + case lnm_http_res_body_type_fn: + if (res->body.data.fn(&written, &conn->w.buf[conn->w.size], conn, + res->written, to_write) != lnm_err_ok) { + ctx->state = lnm_http_loop_state_finish; + } + break; + } + + conn->w.size += written; + res->written += written; + + if (res->written == res->body.len) { + ctx->state = lnm_http_loop_state_finish; + } +} + +void lnm_http_loop_process_finish(lnm_http_conn *conn) {} + void (*process_fns[])(lnm_http_conn *conn) = { lnm_http_loop_process_parse_req, lnm_http_loop_process_route, lnm_http_loop_process_parse_headers, lnm_http_loop_process_steps, + lnm_http_loop_process_write_status_line, + lnm_http_loop_process_write_headers, + lnm_http_loop_process_write_body, + lnm_http_loop_process_finish, }; void lnm_http_loop_process(lnm_http_conn *conn) { lnm_http_loop_ctx *ctx = conn->ctx; lnm_http_loop_state http_loop_state; - lnm_loop_state loop_state; + lnm_loop_state loop_state = conn->state; // We stop processing if: // - the event loop state has changed, as we need to switch to the other I/O @@ -143,7 +258,6 @@ void lnm_http_loop_process(lnm_http_conn *conn) { // it's waiting for I/O do { http_loop_state = ctx->state; - loop_state = conn->state; process_fns[http_loop_state](conn); } while ((conn->state == loop_state) && (http_loop_state != ctx->state)); diff --git a/lnm/src/http/lnm_http_res.c b/lnm/src/http/lnm_http_res.c new file mode 100644 index 0000000..adf65f0 --- /dev/null +++ b/lnm/src/http/lnm_http_res.c @@ -0,0 +1,64 @@ +#include + +#include "lnm/http/res.h" + +lnm_err lnm_http_res_add_header(lnm_http_res *res, lnm_http_header type, + char *value, bool value_owned) { + return lnm_http_res_add_header_len(res, type, value, strlen(value), + value_owned); +} + +lnm_err lnm_http_res_add_header_len(lnm_http_res *res, lnm_http_header type, + char *value, size_t value_len, + bool value_owned) { + lnm_http_res_header *header = calloc(1, sizeof(lnm_http_res_header)); + + if (header == NULL) { + return lnm_err_failed_alloc; + } + + lnm_http_res_header **next_ptr = &res->headers.head; + + while ((*next_ptr) != NULL) { + next_ptr = &(*next_ptr)->next; + } + + *next_ptr = header; + + // Initialize the current pointer to the head of the linked list + if (res->headers.current == NULL) { + res->headers.current = header; + } + + header->name.s = (char *)lnm_http_header_names[type]; + header->name.len = strlen(lnm_http_header_names[type]); + header->name.owned = false; + + header->value.s = value; + header->value.len = value_len; + header->value.owned = value_owned; + + return lnm_err_ok; +} + +void lnm_http_res_body_set_file(lnm_http_res *res, FILE *f, size_t len, + bool owned) { + res->body.data.f = f; + res->body.len = len; + res->body.owned = owned; + res->body.type = lnm_http_res_body_type_file; +} + +void lnm_http_res_body_set_buf(lnm_http_res *res, char *buf, size_t len, + bool owned) { + res->body.data.buf = buf; + res->body.len = len; + res->body.owned = owned; + res->body.type = lnm_http_res_body_type_buf; +} + +void lnm_http_res_body_set_fn(lnm_http_res *res, data_fn fn, size_t len) { + res->body.data.fn = fn; + res->body.len = len; + res->body.type = lnm_http_res_body_type_fn; +}