lander/src/http_loop/http_loop_req.c

150 lines
3.8 KiB
C

#include <string.h>
#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;
}
}