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 != "")
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user