feat: add configurable UCE error pages
This commit is contained in:
+107
-1
@@ -18,7 +18,7 @@ const char* UCE_CLI_SYMBOL = "__uce_cli";
|
||||
const char* UCE_SERVE_HTTP_SYMBOL = "__uce_serve_http";
|
||||
const char* UCE_ONCE_SYMBOL = "__uce_once";
|
||||
const char* UCE_INIT_SYMBOL = "__uce_init";
|
||||
const u64 UCE_UNIT_ABI_VERSION = 4;
|
||||
const u64 UCE_UNIT_ABI_VERSION = 5;
|
||||
|
||||
struct SharedUnitFilesystemState
|
||||
{
|
||||
@@ -995,6 +995,15 @@ SharedUnit* compiler_load_shared_unit(Request* context, String file_name, String
|
||||
printf("%s\n", display_messages.c_str());
|
||||
if(compiler_can_write_response(context) && context->stats.invoke_count == 1)
|
||||
{
|
||||
DTree error_info;
|
||||
error_info["type"] = "compiler_error";
|
||||
error_info["status"] = su->compile_status;
|
||||
error_info["source"] = su->file_name;
|
||||
error_info["compiler_output"] = su->compiler_messages;
|
||||
error_info["generated_cpp"] = compiler_generated_cpp_path(su);
|
||||
error_info["details"] = display_messages;
|
||||
if(compiler_render_error_page(context, "page_compiler_error", 500, "UCE Unit Compile Error", error_info))
|
||||
return(0);
|
||||
context->set_status(500, "UCE Unit Compile Error");
|
||||
context->header["Content-Type"] = "text/plain; charset=utf-8";
|
||||
}
|
||||
@@ -1739,9 +1748,106 @@ bool compiler_invoke_component(Request* context, String file_name, String render
|
||||
));
|
||||
}
|
||||
|
||||
String compiler_error_page_unit(Request* context, String config_key)
|
||||
{
|
||||
if(!context || !context->server)
|
||||
return("");
|
||||
String configured = trim(first(
|
||||
context->server->config[config_key],
|
||||
context->server->config[to_upper(config_key)]
|
||||
));
|
||||
if(configured == "")
|
||||
return("");
|
||||
// Resolve relative to where the server was started, not the volatile
|
||||
// per-unit working directory (which is also stale right after a fault).
|
||||
String resolved = compiler_resolve_unit_path(context, configured, process_start_directory());
|
||||
if(resolved == "" || !file_exists(resolved))
|
||||
return("");
|
||||
return(resolved);
|
||||
}
|
||||
|
||||
bool compiler_render_error_page(Request* context, String config_key, s32 status_code, String status_reason, DTree error_info)
|
||||
{
|
||||
if(!compiler_can_write_response(context))
|
||||
return(false);
|
||||
if(!context->server || context->resources.error_page_active)
|
||||
return(false);
|
||||
String unit_file = compiler_error_page_unit(context, config_key);
|
||||
if(unit_file == "")
|
||||
return(false);
|
||||
|
||||
String previous_response_code = context->response_code;
|
||||
s32 previous_status = context->flags.status;
|
||||
String previous_content_type = context->header["Content-Type"];
|
||||
|
||||
context->resources.error_page_active = true;
|
||||
context->call["error"] = error_info;
|
||||
context->set_status(status_code, status_reason);
|
||||
context->header["Content-Type"] = first(context->server->config["CONTENT_TYPE"], "text/html; charset=utf-8");
|
||||
|
||||
ob_start();
|
||||
String error_message = "";
|
||||
bool rendered = compiler_invoke_render(context, unit_file, "render", &error_message);
|
||||
String html = ob_get_close();
|
||||
context->resources.error_page_active = false;
|
||||
|
||||
if(!rendered)
|
||||
{
|
||||
printf("(!) configured %s page %s failed to render: %s\n", config_key.c_str(), unit_file.c_str(), trim(error_message).c_str());
|
||||
context->response_code = previous_response_code;
|
||||
context->flags.status = previous_status;
|
||||
context->header["Content-Type"] = previous_content_type;
|
||||
context->call.remove("error");
|
||||
return(false);
|
||||
}
|
||||
print(html);
|
||||
return(true);
|
||||
}
|
||||
|
||||
bool compiler_unit_compile_pending(Request* context, String file_name)
|
||||
{
|
||||
String normalized = compiler_normalize_unit_path(context, file_name);
|
||||
if(normalized == "" || !file_exists(normalized))
|
||||
return(false);
|
||||
if(compiler_reusable_cached_unit(context, normalized, false, false))
|
||||
return(false);
|
||||
|
||||
SharedUnit probe;
|
||||
setup_unit_paths(context, &probe, normalized);
|
||||
auto state = inspect_shared_unit_filesystem(context, &probe);
|
||||
if(!shared_unit_compile_check(state).needs_compile)
|
||||
return(false);
|
||||
// A persisted, still-current failure is a compiler error, not a build in
|
||||
// progress: let the load path surface it.
|
||||
if(compiler_failure_retry_deferred(context, &probe, state))
|
||||
return(false);
|
||||
return(true);
|
||||
}
|
||||
|
||||
void compiler_invoke(Request* context, String file_name)
|
||||
{
|
||||
printf("(i) compiler_invoke file %s\n", file_name.c_str());
|
||||
|
||||
// Entry-unit hook for the configured "compiling" page: when the unit needs
|
||||
// a (re)build that the proactive compiler can deliver, answer immediately
|
||||
// instead of blocking the worker on a synchronous compile.
|
||||
if(context && context->stats.invoke_count == 0 &&
|
||||
!context->resources.is_cli && !context->resources.error_page_active &&
|
||||
config_bool("PROACTIVE_COMPILE_ENABLED", true) &&
|
||||
compiler_error_page_unit(context, "page_compiling") != "")
|
||||
{
|
||||
String resolved = compiler_resolve_unit_path(context, file_name, "");
|
||||
if(resolved != "" && compiler_unit_compile_pending(context, resolved))
|
||||
{
|
||||
compiler_track_known_unit(context, resolved);
|
||||
DTree error_info;
|
||||
error_info["type"] = "compiling";
|
||||
error_info["source"] = resolved;
|
||||
if(compiler_render_error_page(context, "page_compiling", 503, "UCE Unit Compiling", error_info))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
String error_message = "";
|
||||
if(!compiler_invoke_render(context, file_name, "render", &error_message) && error_message != "")
|
||||
{
|
||||
|
||||
@@ -21,6 +21,9 @@ void compiler_invoke_websocket(Request* context, String file_name);
|
||||
void compiler_invoke_cli(Request* context, String file_name);
|
||||
void compiler_invoke_serve_http(Request* context, String file_name, String handler_name = "");
|
||||
SharedUnit* compiler_load_shared_unit(Request* context, String file_name, String current_path = "", bool opt_so_optional = false);
|
||||
String compiler_error_page_unit(Request* context, String config_key);
|
||||
bool compiler_render_error_page(Request* context, String config_key, s32 status_code, String status_reason, DTree error_info);
|
||||
bool compiler_unit_compile_pending(Request* context, String file_name);
|
||||
String compiler_site_directory(Request* context);
|
||||
StringList compiler_scan_site_units(Request* context);
|
||||
StringList compiler_list_known_units(Request* context);
|
||||
|
||||
@@ -348,6 +348,15 @@ String cwd_get()
|
||||
return(std::filesystem::current_path());
|
||||
}
|
||||
|
||||
String process_start_directory()
|
||||
{
|
||||
// Primed in main() before any unit handler can chdir; fault recovery and
|
||||
// config-relative path resolution anchor to this instead of the volatile
|
||||
// per-unit working directory.
|
||||
static String start_directory = cwd_get();
|
||||
return(start_directory);
|
||||
}
|
||||
|
||||
void cwd_set(String path)
|
||||
{
|
||||
chdir(path.c_str());
|
||||
|
||||
@@ -30,6 +30,7 @@ bool file_append(String file_name, Ts... args)
|
||||
}
|
||||
String cwd_get();
|
||||
void cwd_set(String path);
|
||||
String process_start_directory();
|
||||
time_t file_mtime(String file_name);
|
||||
void file_unlink(String file_name);
|
||||
String expand_path(String path, String relative_to_path = "");
|
||||
|
||||
@@ -224,6 +224,10 @@ struct Request {
|
||||
bool websocket_is_text = false;
|
||||
String current_unit_file = "";
|
||||
String params_buffer;
|
||||
// True while a configured error page (page_compiling /
|
||||
// page_compiler_error / page_runtime_error) is rendering, so a failing
|
||||
// error page can never recurse into another error page.
|
||||
bool error_page_active = false;
|
||||
} resources;
|
||||
|
||||
void ob_start();
|
||||
|
||||
@@ -71,6 +71,30 @@ void render_request_failure(Request& request, String title, String details, Stri
|
||||
Request* previous_context = set_active_request(request);
|
||||
clear_request_output(request);
|
||||
|
||||
if(!request.resources.is_cli)
|
||||
{
|
||||
DTree error_info;
|
||||
error_info["type"] = "runtime_error";
|
||||
error_info["title"] = title;
|
||||
error_info["details"] = details;
|
||||
error_info["source"] = request.params["SCRIPT_FILENAME"];
|
||||
error_info["request_uri"] = first(request.params["REQUEST_URI"], request.params["SCRIPT_FILENAME"]);
|
||||
if(request.server && request.params["SCRIPT_FILENAME"] != "")
|
||||
error_info["generated_cpp"] = compiler_generated_cpp_path(&request, request.params["SCRIPT_FILENAME"]);
|
||||
if(request_fault_signal != 0)
|
||||
{
|
||||
error_info["signal"] = (f64)request_fault_signal;
|
||||
error_info["signal_name"] = signal_name((int)request_fault_signal);
|
||||
}
|
||||
error_info["trace"] = trace;
|
||||
if(compiler_render_error_page(&request, "page_runtime_error", status_code, "Internal Server Error", error_info))
|
||||
{
|
||||
request.err = "UCE runtime error: " + title + (details != "" ? " (" + details + ")" : "");
|
||||
restore_active_request(previous_context);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
String body;
|
||||
body += "UCE runtime error\n";
|
||||
body += "Request: " + first(request.params["REQUEST_URI"], request.params["SCRIPT_FILENAME"]) + "\n";
|
||||
@@ -902,6 +926,9 @@ int handle_complete(FastCGIRequest& request) {
|
||||
failure_title = "fatal signal during request";
|
||||
failure_details = "worker recovered before closing the upstream connection";
|
||||
failure_trace = backtrace_frames_string(request_fault_frames, request_fault_frame_count, 1);
|
||||
// The siglongjmp skipped UnitInvocationScope's destructor, so the
|
||||
// worker may still sit in the crashed unit's source directory.
|
||||
cwd_set(process_start_directory());
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1508,6 +1535,7 @@ int main(int argc, char** argv)
|
||||
// Warm up libgcc's backtrace state so the first in-handler backtrace()
|
||||
// after a fault does not allocate.
|
||||
backtrace(request_fault_frames, 4);
|
||||
process_start_directory();
|
||||
|
||||
init_base_process();
|
||||
ensure_proactive_compiler();
|
||||
|
||||
Reference in New Issue
Block a user