Add archive helpers and harden task runtime

This commit is contained in:
udo
2026-05-21 00:00:23 +00:00
parent 9f7625c7fd
commit 02e153a6a7
71 changed files with 12817 additions and 305 deletions
+188 -53
View File
@@ -26,6 +26,7 @@ static DTree websocket_exec_inflight_job;
static String websocket_exec_write_buffer = "";
void close_inherited_server_sockets();
u64 request_seed_from_time(f64 time_value);
Request* set_active_request(Request& request)
{
@@ -315,6 +316,25 @@ void websocket_exec_append_command(DTree command)
context->resources.websocket_dispatch_commands.push(command);
}
DTree websocket_exec_make_message_command(String action, String message, bool binary)
{
DTree command;
command["action"] = action;
command["binary"].set_bool(binary);
command["message_b64"] = websocket_ipc_base64_encode(message);
return(command);
}
DTree websocket_exec_make_close_command(String connection_id, u16 status_code = 1000, String reason = "")
{
DTree command;
command["action"] = "close";
command["connection_id"] = connection_id;
command["status_code"] = (f64)status_code;
command["reason"] = reason;
return(command);
}
void websocket_exec_apply_command(DTree command)
{
String action = command["action"].to_string();
@@ -470,7 +490,7 @@ Request websocket_exec_build_event_request(DTree job, String message)
event_request.stats.time_init = time_precise();
event_request.stats.time_start = event_request.stats.time_init;
event_request.random_index = 0;
event_request.random_seed = gen_noise64(*reinterpret_cast<u64*>(&event_request.stats.time_start));
event_request.random_seed = request_seed_from_time(event_request.stats.time_start);
event_request.response_code = "WEBSOCKET";
event_request.header["Content-Type"] = server_state.config["CONTENT_TYPE"];
event_request.in = message;
@@ -747,28 +767,23 @@ u64 ws_connection_count(String scope)
bool ws_send(String message, bool binary, String scope)
{
String normalized_scope = normalize_ws_scope(scope);
if(context && context->resources.websocket_dispatch_capture)
{
DTree command;
command["action"] = "broadcast";
command["scope"] = normalize_ws_scope(scope);
command["binary"].set_bool(binary);
command["message_b64"] = websocket_ipc_base64_encode(message);
DTree command = websocket_exec_make_message_command("broadcast", message, binary);
command["scope"] = normalized_scope;
websocket_exec_append_command(command);
return(true);
}
return(server.websocket_broadcast(normalize_ws_scope(scope), message, binary) > 0);
return(server.websocket_broadcast(normalized_scope, message, binary) > 0);
}
bool ws_send_to(String connection_id, String message, bool binary)
{
if(context && context->resources.websocket_dispatch_capture)
{
DTree command;
command["action"] = "send_to";
DTree command = websocket_exec_make_message_command("send_to", message, binary);
command["connection_id"] = connection_id;
command["binary"].set_bool(binary);
command["message_b64"] = websocket_ipc_base64_encode(message);
websocket_exec_append_command(command);
return(true);
}
@@ -783,17 +798,156 @@ bool ws_close(String connection_id)
return(false);
if(context && context->resources.websocket_dispatch_capture)
{
DTree command;
command["action"] = "close";
command["connection_id"] = connection_id;
command["status_code"] = (f64)1000;
command["reason"] = "";
websocket_exec_append_command(command);
websocket_exec_append_command(websocket_exec_make_close_command(connection_id));
return(true);
}
return(server.websocket_close(connection_id));
}
bool cli_path_is_safe(String command)
{
for(auto& segment : split(command, "/"))
{
if(segment == "..")
return(false);
}
return(true);
}
String cli_resolve_unit_path(Request& request, String command, String& document_root)
{
document_root = first(request.params["DOCUMENT_ROOT"], cwd_get());
if(document_root.length() > 1 && document_root[document_root.length()-1] == '/')
document_root.resize(document_root.length()-1);
String script_filename = document_root + command;
if(file_exists(script_filename))
return(script_filename);
String site_filename = document_root + "/site" + command;
if(file_exists(site_filename))
{
document_root = document_root + "/site";
return(site_filename);
}
return("");
}
u64 request_seed_from_time(f64 time_value)
{
u64 bits = 0;
static_assert(sizeof(bits) == sizeof(time_value));
memcpy(&bits, &time_value, sizeof(bits));
return(gen_noise64(bits));
}
void prepare_request_body_maps(Request& request)
{
request.get = parse_query(request.params["QUERY_STRING"]);
if(request.params["HTTP_COOKIE"].length() > 0)
request.cookies = parse_cookies(request.params["HTTP_COOKIE"]);
String ct_info = request.params["CONTENT_TYPE"];
String ct_type = nibble(ct_info, ";");
if(request.params["REQUEST_METHOD"] == "POST")
{
if(ct_type == "multipart/form-data")
{
nibble("boundary=", ct_info);
request.post = parse_multipart(request.in, String("--")+ct_info, request.uploaded_files);
}
else
{
request.post = parse_query(request.in);
}
}
}
int handle_cli_complete(FastCGIRequest& request)
{
Request* previous_context = set_active_request(request);
server_state.request_count += 1;
request.server = &server_state;
request.resources.is_cli = true;
request.params["UCE_CLI"] = "1";
request.stats.time_start = time_precise();
request.header["Content-Type"] = "text/plain; charset=utf-8";
request.random_index = 0;
request.random_seed = request_seed_from_time(request.stats.time_start);
request.ob_start();
prepare_request_body_maps(request);
String method = trim(request.params["REQUEST_METHOD"]);
String command = trim(first(request.params["DOCUMENT_URI"], request.params["REQUEST_URI"]));
try
{
if(method != "GET" && method != "POST")
{
request.set_status(405, "Method Not Allowed");
request.header["Allow"] = "GET, POST";
print("UCE CLI socket accepts GET and POST commands only\n");
}
else if(command == "/" || command == "/help")
{
print("UCE CLI command socket\n\nAvailable test hooks:\n GET /ping\n GET /status\n");
}
else if(command == "/ping" || command == "/test")
{
print("uce-cli: ok\n");
}
else if(command == "/status")
{
print("pid=", std::to_string(getpid()), "\nclients=", std::to_string(server.client_sockets.size()), "\n");
}
else if(command.length() >= 4 && command.substr(command.length() - 4) == ".uce")
{
if(!cli_path_is_safe(command))
{
request.set_status(400, "Bad Request");
print("invalid UCE CLI unit path\n");
}
else
{
String document_root;
String script_filename = cli_resolve_unit_path(request, command, document_root);
if(script_filename == "")
{
request.set_status(404, "Not Found");
print("UCE CLI unit not found: ", command, "\n");
}
else
{
request.params["DOCUMENT_ROOT"] = document_root;
request.params["SCRIPT_FILENAME"] = script_filename;
request.props = DTree();
compiler_invoke_cli(&request, script_filename);
}
}
}
else
{
request.set_status(404, "Not Found");
print("unknown UCE CLI command: ", command, "\n");
}
}
catch(const std::exception& e)
{
render_request_failure(request, "uncaught exception during CLI request", e.what(), capture_backtrace_string(32, 1), 500);
}
catch(...)
{
render_request_failure(request, "unknown uncaught exception during CLI request", "", capture_backtrace_string(32, 1), 500);
}
for(auto &f : request.uploaded_files)
file_unlink(f.tmp_name);
cleanup_mysql_connections();
restore_active_request(previous_context);
return(request.flags.status);
}
int handle_request(FastCGIRequest& request) {
// This is always the first event to occur. It occurs when the
// server receives all parameters. There may be more data coming on the
@@ -805,20 +959,9 @@ int handle_request(FastCGIRequest& request) {
}
int handle_data(FastCGIRequest& request) {
// This event occurs when data is received on the standard input stream.
// A simple String is used to hold the input stream, so it is the
// responsibility of the application to remember which data it has
// processed. The application may modify it; new data will be appended
// to it by the server. The same goes for the output and error streams:
// the application should append data to them; the server will remove
// all sent data from them.
return 0; // still OK
std::transform(request.in.begin(), request.in.end(),
std::back_inserter(request.err),
std::bind1st(std::plus<char>(), 1));
request.in.clear(); // don't process it again
return 0; // still OK
// Request bodies are accumulated by the FastCGI transport and parsed once
// the input stream is closed in handle_complete().
return 0;
}
int handle_complete(FastCGIRequest& request) {
@@ -833,10 +976,10 @@ int handle_complete(FastCGIRequest& request) {
request.stats.time_start = time_precise();
//request.stats.mem_alloc = 0;
//request.stats.mem_high = 0;
request.header["Content-Type"] = context->server->config["CONTENT_TYPE"];
request.header["Content-Type"] = (request.resources.is_cli ? "text/plain; charset=utf-8" : context->server->config["CONTENT_TYPE"]);
request.get = parse_query(request.params["QUERY_STRING"]);
request.random_index = 0;
request.random_seed = gen_noise64(*reinterpret_cast<u64*>(&request.stats.time_start));
request.random_seed = request_seed_from_time(request.stats.time_start);
request.ob_start();
request_fault_request = &request;
request_fault_active = 1;
@@ -858,27 +1001,12 @@ int handle_complete(FastCGIRequest& request) {
{
try
{
if(request.params["HTTP_COOKIE"].length() > 0)
request.cookies = parse_cookies(request.params["HTTP_COOKIE"]);
String ct_info = request.params["CONTENT_TYPE"];
String ct_type = nibble(";", ct_info);
if(request.params["REQUEST_METHOD"] == "POST")
{
if(ct_type == "multipart/form-data")
{
nibble("boundary=", ct_info);
request.post = parse_multipart(request.in, String("--")+ct_info, request.uploaded_files);
}
else
{
request.post = parse_query(request.in);
}
}
prepare_request_body_maps(request);
request.props = DTree();
compiler_invoke(&request, request.params["SCRIPT_FILENAME"]);
if(request.resources.is_cli)
compiler_invoke_cli(&request, request.params["SCRIPT_FILENAME"]);
else
compiler_invoke(&request, request.params["SCRIPT_FILENAME"]);
}
catch(const std::exception& e)
{
@@ -1142,6 +1270,7 @@ void listen_for_connections()
server.on_request = &handle_request;
server.on_data = &handle_data;
server.on_complete = &handle_complete;
server.on_cli_complete = &handle_cli_complete;
server.on_websocket_message = &handle_websocket_message;
if(worker_accepts_http)
ensure_websocket_executor();
@@ -1176,6 +1305,12 @@ void init_base_process()
chmod(server_state.config["FCGI_SOCKET_PATH"].c_str(), S_IRWXU | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
}
if(server_state.config["CLI_SOCKET_PATH"] != "")
{
server.listen_cli(server_state.config["CLI_SOCKET_PATH"]);
chmod(server_state.config["CLI_SOCKET_PATH"].c_str(), S_IRWXU | S_IRGRP | S_IWGRP);
}
if(server_state.config["HTTP_PORT"] != "")
server.listen_http(int_val(server_state.config["HTTP_PORT"]));