150 lines
3.8 KiB
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;
|
|
}
|
|
}
|