296 lines
8.2 KiB
C
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;
|
|
}
|