#include #include "http_loop.h" #include "log.h" http_parse_error http_loop_parse_request(event_loop_conn *conn) { http_loop_ctx *ctx = conn->ctx; // First we try to parse the incoming HTTP request size_t num_headers = HTTP_MAX_ALLOWED_HEADERS; http_request *req = &ctx->req; const char *method; size_t method_len; char *path; size_t path_len; int res = phr_parse_request((const char *)&conn->rbuf[conn->rbuf_read], conn->rbuf_size - conn->rbuf_read, &method, &method_len, (const char **)&path, &path_len, &req->minor_version, req->headers, &num_headers, 0); if (res == -1) { return http_parse_error_invalid; } else if (res == -2) { return http_parse_error_incomplete; } req->num_headers = num_headers; req->len = res; // Try to parse the method type bool match = false; size_t i = 0; for (i = 0; i < http_method_names_len; i++) { if (strncmp(method, http_method_names[i], method_len) == 0) { req->method = i; match = true; } } if (!match) { return http_parse_error_unknown_method; } // Split path into path & query i = 0; bool no_query = true; while (no_query && i < path_len) { if (path[i] == '?') { // Ensure we don't store an invalid pointer if the request simply ends // with '?' if (i + 1 < req->path_len) { req->query = &path[i + 1]; req->query_len = path_len - (i + 1); } path_len = i; no_query = false; } i++; } // The path needs to be NULL-terminated in order for regex routes to be // matched properly. We know we can overwrite this char because it's either // '?' if there's a query, or '\n' because we know the buf contains a valid // HTTP rqeuest path[path_len] = '\0'; req->path = path; req->path_len = path_len; // Ensure we clear the old request's query if (no_query) { req->query = NULL; req->query_len = 0; } return http_parse_error_ok; } void http_loop_route_request(event_loop_conn *conn) { http_loop_ctx *ctx = conn->ctx; http_loop_gctx *gctx = ctx->g; info("%s %.*s", http_method_names[ctx->req.method], ctx->req.path_len, ctx->req.path); http_route *route; bool path_matched = false; for (size_t i = 0; i < gctx->route_count; i++) { route = &gctx->routes[i]; switch (route->type) { case http_route_literal: if (strncmp(route->path, ctx->req.path, ctx->req.path_len) == 0) { path_matched = true; if (ctx->req.method == route->method) { ctx->route = route; return; } } break; case http_route_regex: if (regexec(route->regex, ctx->req.path, HTTP_MAX_REGEX_GROUPS, ctx->req.regex_groups, 0) == 0) { path_matched = true; if (ctx->req.method == route->method) { ctx->route = route; return; } } break; } } // Fallthrough case ctx->res.status = path_matched ? http_method_not_allowed : 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++; } // Request processing can stop early by switching the connection state // Either way, we reset the step counter as it will be used by the response // steps if ((conn->state != event_loop_conn_state_req) || (ctx->route->steps[ctx->current_step] == NULL)) { ctx->current_step = 0; conn->state = event_loop_conn_state_res; } }