vessel/web/web.c
Arija A. 4f0bf80e5f
refact: Remove mem.h
Signed-off-by: Arija A. <ari@ari.lt>
2025-06-21 23:43:31 +03:00

296 lines
8.2 KiB
C

#include "include/conf.h"
#include <vessel/log.h>
#include <vessel/hmap.h>
#include <vessel/hook.h>
#include "include/web.h"
#include "include/request.h"
#include <arpa/inet.h>
VS_DEFINE_HOOKS(vw_Route, vw_RouteHookType, vs_SockWorker *);
static const vw_Route *
vw_find_route(vw_Proxy *proxy, const vw_Route *routes, const vw_RouteType type) {
const vw_Route *found_route = NULL;
for (size_t idx = 0; routes[idx].handler; ++idx) {
if (routes[idx].method && strcmp(routes[idx].method, proxy->req.method) != 0) {
continue;
}
if (routes[idx].type == type) {
if (vw_PathPattern_matchn(
routes[idx]._path_pattern, &proxy->params, proxy->path.path, proxy->path.len) ==
proxy->path.len) {
found_route = &routes[idx];
break;
}
/* else */
vw_PathPattern_matchn_clear(&proxy->params);
}
}
return found_route;
}
static bool vw_Proxy_init(vw_Proxy *proxy, vs_SockWorker *worker) {
if (!proxy || !worker) {
return false;
}
proxy->worker = worker;
proxy->req_error = vw_HTTPRequestParseError_ok;
if (!vw_HTTPRequest_init(&proxy->req, &worker->stream)) {
return false;
}
if (!vw_Path_init(&proxy->path)) {
vw_HTTPRequest_destroy(&proxy->req);
return false;
}
if (!vs_HMap_init(&proxy->params)) {
vw_HTTPRequest_destroy(&proxy->req);
vw_Path_destroy(&proxy->path);
return false;
}
return true;
}
static bool vw_Proxy_destroy(vw_Proxy *proxy) {
const bool req_destroyed = vw_HTTPRequest_destroy(&proxy->req);
const bool path_destroyed = vw_Path_destroy(&proxy->path);
const bool pathpat_destroyed = vw_PathPattern_matchn_destroy(&proxy->params);
return req_destroyed && path_destroyed && pathpat_destroyed;
}
static bool vw_handle_request(vw_Proxy *proxy, vs_SockWorker *worker, vw_Router *router) {
const vw_Route *route = NULL;
const vw_Route *route2 = NULL;
/* Run pre-read hooks */
worker->arg = proxy;
if (!vw_RouteHook_runall(router->hooks, vw_RouteHookType_pre_read, worker, false, NULL)) {
worker->arg = router;
goto finish;
}
worker->arg = router;
/* Read HTTP request */
proxy->req_error = vw_HTTPRequest_read(&proxy->req);
if (proxy->req_error != vw_HTTPRequestParseError_ok) {
if (proxy->req_error == vw_HTTPRequestParseError_read) {
return true;
}
vs_flog_warn(worker->logger,
"Connection %ju sent an invalid HTTP request: %s.",
vs_File_identity(&worker->stream.file),
vw_HTTPRequestParseError_to_str(proxy->req_error));
goto bad_request;
}
/* Parse path */
if (!vw_Path_parse(&proxy->path, proxy->req.path)) {
proxy->req_error = vw_HTTPRequestParseError_format_path;
goto bad_request;
}
/* Run pre-routing hooks */
worker->arg = proxy;
if (!vw_RouteHook_runall(router->hooks, vw_RouteHookType_pre_routing, worker, false, NULL)) {
worker->arg = router;
goto finish;
}
worker->arg = router;
/* Route */
route = vw_find_route(proxy, router->routes, vw_RouteType_normal);
if (!route) /* 404 */ {
proxy->req.res.code = 404;
route = vw_find_route(proxy, router->routes, vw_RouteType_not_found);
}
if (route) {
route2 = vw_find_route(proxy, router->routes, vw_RouteType_before);
if (route2 && !route2->handler(proxy)) {
/* p.res.code = 666; */
route = NULL;
goto finish;
}
}
if (route && !route->handler(proxy)) {
/* p.res.code = 666; */
route = vw_find_route(proxy, router->routes, vw_RouteType_error);
if (route && !route->handler(proxy)) {
goto server_error;
}
route = NULL;
} else if (!vw_RouteHook_runall(
router->hooks, vw_RouteHookType_no_route, worker, false, NULL)) {
/* p.res.code = 404; */
route = NULL;
}
/* After routing it goes to `finish` anyway so no need to jump to it using
* `goto`. */
finish:
vs_flog_info(worker->logger,
"%s (%ju) <= %s%s %s -- %u %s" VS_CLR_RESET,
proxy->ip,
vs_File_identity(&worker->stream.file),
vw_HTTP_code_to_colour(proxy->req.res.code),
proxy->req.method,
proxy->req.path,
proxy->req.res.code,
vw_HTTP_code_to_message(proxy->req.res.code));
if (route) {
route2 = vw_find_route(proxy, router->routes, vw_RouteType_after);
if (route2 && !route2->handler(proxy)) {
/* p.res.code = 666; */
route = NULL;
}
}
return true;
server_error:
if (!vw_RouteHook_runall(router->hooks, vw_RouteHookType_internal_error, worker, false, NULL)) {
vs_flog_warn(worker->logger,
"Failed to run internal server error hooks for connection %ju.",
vs_File_identity(&worker->stream.file));
}
return false; /* Server error */
bad_request:
worker->arg = proxy;
if (!vw_RouteHook_runall(router->hooks, vw_RouteHookType_bad_request, worker, false, NULL)) {
vs_flog_warn(worker->logger,
"Failed to run bad request hooks for connection %ju.",
vs_File_identity(&worker->stream.file));
}
worker->arg = router;
return false; /* Bad request */
}
bool vw_WebServer_post_open(vs_SockWorker *worker) {
bool handled = true;
while (true) {
vw_Proxy proxy = { 0 };
vw_Router *router = (vw_Router *)worker->arg;
if (vs_Stream_poll(&worker->stream) <= 0) {
break;
}
if (!vw_Proxy_init(&proxy, worker)) {
if (!vw_RouteHook_runall(
router->hooks, vw_RouteHookType_internal_error, worker, false, NULL)) {
vs_flog_warn(worker->logger,
"Failed to run internal server error hooks for "
"connection %ju.",
vs_File_identity(&worker->stream.file));
}
return false;
}
proxy.req.path[0] = proxy.req.method[0] = '?';
proxy.req.path[1] = proxy.req.method[1] = '\0';
/* NOTE: Return of hooks and routes means 'continue execution' if true
* else 'terminate execution.' */
/* Extract IP address */
if (!inet_ntop(AF_INET, &(worker->client_addr.sin_addr), proxy.ip, INET_ADDRSTRLEN)) {
vs_log_warn(worker->logger, "Failed to parse client IP address.");
proxy.ip[0] = '\0';
}
handled = vw_handle_request(&proxy, worker, router);
vw_HTTPBody_skip_content(&proxy.req.body);
const bool keepalive = proxy.req.keepalive;
/* Cleanup */
if (!vw_Proxy_destroy(&proxy)) {
vs_flog_warn(worker->logger,
"Failed to destroy proxy for connection %ju.",
vs_File_identity(&worker->stream.file));
}
if (!keepalive) {
break;
}
}
return handled;
}
/* TODO: Make the router constant? */
bool vw_WebServer_init(vs_SockServer *server, vw_Router *router) {
if (!server || !router) {
return false;
}
for (size_t idx = 0; router->routes[idx].handler; ++idx) {
vw_Route *route = &router->routes[idx];
vw_PathPattern *proxy = vw_PathPattern_compile(server->logger, route->pattern);
if (!proxy) {
for (size_t jdx = 0; jdx < idx; ++jdx) {
vw_PathPattern_destroy(router->routes[jdx]._path_pattern);
}
return false;
}
route->_path_pattern = proxy;
}
for (uint16_t idx = 0; idx < server->threads; ++idx) {
server->workers[idx].arg = router;
}
return true;
}
bool vw_WebServer_destroy(vw_Router *router) {
if (!router) {
return false;
}
for (size_t idx = 0; router->routes[idx].handler; ++idx) {
vw_PathPattern_destroy(router->routes[idx]._path_pattern);
}
return true;
}