#include #include #include #include "lnm/http/consts.h" #include "lnm/http/loop.h" #include "lnm/http/loop_internal.h" #include "lnm/http/req.h" #include "lnm/log.h" #include "lnm/loop.h" #include "lnm/loop_internal.h" static const char *section = "http"; /* static const lnm_http_loop_state lnm_http_loop_state_first_req = * lnm_http_loop_state_parse_req; */ static const lnm_http_loop_state lnm_http_loop_state_first_res = lnm_http_loop_state_add_headers; void lnm_http_loop_process_parse_req(lnm_http_conn *conn) { lnm_http_loop_ctx *ctx = conn->ctx; lnm_http_parse_err res = lnm_http_req_parse( &ctx->req, &conn->r.buf[conn->r.read], conn->r.size - conn->r.read); switch (res) { case lnm_http_parse_err_ok: conn->r.read += ctx->req.buf.len; ctx->state = lnm_http_loop_state_route; lnm_linfo(section, "%s %.*s HTTP/1.%i", lnm_http_method_names[ctx->req.method], (int)ctx->req.path.len, ctx->req.buf.s + ctx->req.path.o, ctx->req.minor_version); break; case lnm_http_parse_err_incomplete: // If the request is already the size of the read buffer, we close the // request. Otherwise, we wait for anything read if (conn->r.size - conn->r.read == LNM_LOOP_BUF_SIZE) { lnm_linfo(section, "Received request larger than buffer (%i bytes)", LNM_LOOP_BUF_SIZE); conn->state = lnm_loop_state_end; } break; case lnm_http_parse_err_invalid: lnm_linfo(section, "%s", "Received invalid request"); conn->state = lnm_loop_state_end; break; case lnm_http_parse_err_unknown_method: ctx->res.status = lnm_http_status_method_not_implemented; ctx->state = lnm_http_loop_state_first_res; break; } } void lnm_http_loop_process_route(lnm_http_conn *conn) { lnm_http_loop_ctx *ctx = conn->ctx; const lnm_http_loop_gctx *gctx = ctx->g; switch (lnm_http_router_route(&ctx->req.route_match, gctx->router, ctx->req.method, ctx->req.buf.s + ctx->req.path.o)) { case lnm_http_route_err_match: ctx->cur_step = ctx->req.route_match.route->step; ctx->state = lnm_http_loop_state_parse_headers; break; case lnm_http_route_err_unknown_method: ctx->res.status = lnm_http_status_method_not_allowed; ctx->state = lnm_http_loop_state_first_res; break; case lnm_http_route_err_unknown_route: ctx->res.status = lnm_http_status_not_found; ctx->state = lnm_http_loop_state_first_res; break; } } void lnm_http_loop_process_parse_headers(lnm_http_conn *conn) { lnm_http_loop_ctx *ctx = conn->ctx; lnm_http_req *req = &ctx->req; const char *value; size_t value_len; if (lnm_http_req_header_get(&value, &value_len, req, lnm_http_header_content_length) == lnm_err_ok) { req->body.expected_len = lnm_atoi(value, value_len); } ctx->state = lnm_http_loop_state_steps; } void lnm_http_loop_process_steps(lnm_http_conn *conn) { lnm_http_loop_ctx *ctx = conn->ctx; lnm_http_step *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)) { step = ctx->cur_step; if (step->blocking && (conn->state != lnm_loop_state_req_work)) { conn->state = lnm_loop_state_req_work; break; } switch (step->fn(conn)) { case lnm_http_step_err_done: ctx->cur_step = ctx->cur_step->next; break; case lnm_http_step_err_io_needed: // Ensure steps that require more I/O are executed on the event loop conn->state = lnm_loop_state_req_io; break; case lnm_http_step_err_close: conn->state = lnm_loop_state_end; break; case lnm_http_step_err_res: ctx->state = lnm_http_loop_state_first_res; break; } } if (ctx->cur_step == NULL) { conn->state = lnm_loop_state_res_io; ctx->state = lnm_http_loop_state_add_headers; } } void lnm_http_loop_state_process_add_headers(lnm_http_conn *conn) { lnm_http_loop_ctx *ctx = conn->ctx; lnm_http_res *res = &ctx->res; uint64_t digits = lnm_digits(res->body.len); char *buf = malloc(digits + 1); if (buf == NULL) { conn->state = lnm_loop_state_end; return; } sprintf(buf, "%lu", res->body.len); lnm_http_res_add_header_len(res, lnm_http_header_content_length, buf, digits, true); if (ctx->g->server != NULL) { lnm_http_res_add_header(res, lnm_http_header_server, (char *)ctx->g->server, false); } if (res->status == 0) { res->status = lnm_http_status_ok; } lnm_linfo(section, "%i %s", res->status, lnm_http_status_names[res->status / 100 - 1][res->status % 100]); ctx->state = lnm_http_loop_state_write_status_line; } // 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; // 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; } } // The headers should end with an additional newline. If there's no space left // in the write buffer, we don't switch states so we can re-try this write // later if ((res->headers.current == NULL) && (conn->w.size < LNM_LOOP_BUF_SIZE)) { conn->w.buf[conn->w.size] = '\n'; conn->w.size++; // HEAD requests function exactly the same as GET requests, except that they // skip the body writing part ctx->state = ctx->req.method != lnm_http_method_head && 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 = 0; 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) != 0)) { 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) { // First we ensure the write buffer is fully flushed if (conn->w.size > 0) { return; } lnm_http_loop_ctx *ctx = conn->ctx; lnm_http_loop_ctx_reset(ctx); ctx->state = lnm_http_loop_state_parse_req; } 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_state_process_add_headers, lnm_http_loop_process_write_status_line, lnm_http_loop_process_write_headers, lnm_http_loop_process_write_body, lnm_http_loop_process_finish, }; lnm_loop_state state_map[] = { // parse_req lnm_loop_state_req_io, // route lnm_loop_state_req_io, // parse_headers lnm_loop_state_req_io, // steps lnm_loop_state_req_io, // add_headers lnm_loop_state_req_io, // write_status_line lnm_loop_state_res_io, // write_headers lnm_loop_state_res_io, // write_body lnm_loop_state_res_io, // finish lnm_loop_state_res_io, }; 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 = conn->state; // We stop processing if: // - the event loop state has been explicitely changed inside the executed // step, as we need to switch to the other I/O loop // - the event loop state needs to be changed because the next step should be // run in another event loop state // - the process fn returned without changing the HTTP loop state, indicating // it's waiting for I/O do { http_loop_state = ctx->state; process_fns[http_loop_state](conn); } while ((conn->state == state_map[ctx->state]) && (http_loop_state != ctx->state)); // Check required to prevent overwriting manually set event loop state conn->state = conn->state == loop_state ? state_map[ctx->state] : conn->state; // We move the request to a dedicated buffer if the read buffer needs to be // reused if ((conn->state == lnm_loop_state_req_io) && (conn->state == loop_state) && (!ctx->req.buf.owned) && (ctx->req.buf.len > 0)) { char *buf = malloc(ctx->req.buf.len); if (buf == NULL) { lnm_lerror(section, "Failed to allocate request buffer; closing connection %i", conn->fd); conn->state = lnm_loop_state_end; } else { memcpy(buf, ctx->req.buf.s, ctx->req.buf.len); ctx->req.buf.s = buf; ctx->req.buf.owned = true; lnm_ldebug(section, "Allocated request buffer for connection %i", conn->fd); } } }