Compare commits
2
Commits
d0efab7db0
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
09f743fdcf | ||
|
|
36d6207bf2 |
@@ -1,8 +1,8 @@
|
|||||||
# UCE WASM Runtime Architecture
|
# UCE WASM Runtime Architecture
|
||||||
|
|
||||||
Status: current as of the W7e native-pipeline removal (June 2026). This document
|
Status: current as of the worker-role isolation and bounded serializer changes
|
||||||
describes the **runtime architecture as built** — the process topology, the
|
(July 2026). This document describes the **runtime architecture as built**. It
|
||||||
wasm membrane, the unified request dispatch, and the central WebSocket broker.
|
covers the process topology, wasm membrane, request dispatch, and WebSocket broker.
|
||||||
Native `.so` unit execution/dlopen fallback has been removed; the parser and
|
Native `.so` unit execution/dlopen fallback has been removed; the parser and
|
||||||
preprocessor remain only as the front-end that emits C++ for wasm side-module
|
preprocessor remain only as the front-end that emits C++ for wasm side-module
|
||||||
compilation.
|
compilation.
|
||||||
@@ -26,8 +26,8 @@ gets invoked*.
|
|||||||
|
|
||||||
```
|
```
|
||||||
┌────────────────────────────┐
|
┌────────────────────────────┐
|
||||||
nginx ──FastCGI──► worker pool (N processes) │ $FCGI_SOCKET_PATH (example `/run/uce/fastcgi.sock`)
|
nginx ──FastCGI──► public workers (N processes)│ $FCGI_SOCKET_PATH (example `/run/uce/fastcgi.sock`)
|
||||||
(port 80 etc.) │ uniform unit renderers │ (FastCGI + CLI)
|
(port 80 etc.) │ public unit renderers │
|
||||||
└─────────────▲──────────────┘
|
└─────────────▲──────────────┘
|
||||||
│ forward render (FastCGI, FCGI_SOCKET_PATH)
|
│ forward render (FastCGI, FCGI_SOCKET_PATH)
|
||||||
│
|
│
|
||||||
@@ -42,6 +42,11 @@ gets invoked*.
|
|||||||
│ dispatcher(s) │ bind addr; forwards to pool
|
│ dispatcher(s) │ bind addr; forwards to pool
|
||||||
└──────────────────┘
|
└──────────────────┘
|
||||||
|
|
||||||
|
trusted CLI/test calls ──► ┌──────────────────┐
|
||||||
|
(CLI socket) │ CLI workers │ isolated module and
|
||||||
|
│ (M processes) │ connector caches
|
||||||
|
└──────────────────┘
|
||||||
|
|
||||||
parent process: spawns/respawns all of the above + the proactive compiler.
|
parent process: spawns/respawns all of the above + the proactive compiler.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -24,16 +24,8 @@ DValue resp = http_request(req);
|
|||||||
print("HTTP ", resp["status"].to_u64(), ", ", resp["body"].to_string().length(), " bytes returned\n");
|
print("HTTP ", resp["status"].to_u64(), ", ", resp["body"].to_string().length(), " bytes returned\n");
|
||||||
|
|
||||||
:example
|
:example
|
||||||
// GitHub token exchange: client_secret stays in stdin body, not argv.
|
// Hardened requests reject IP-literal hosts before they start curl.
|
||||||
DValue token; token["method"]="POST"; token["url"]="https://github.com/login/oauth/access_token";
|
DValue hardened; hardened["method"]="GET"; hardened["url"]="https://127.0.0.1/";
|
||||||
token["headers"]["Accept"]="application/json"; token["headers"]["Content-Type"]="application/x-www-form-urlencoded";
|
for(String key:{"https_only","public_dns_only","pin_dns","isolated_curl","no_redirects"}) hardened["security"][key].set_bool(true);
|
||||||
token["body"]="client_id="+uri_encode(client_id)+"&client_secret="+uri_encode(client_secret)+"&code="+uri_encode(code);
|
DValue rejected=http_request(hardened);
|
||||||
for(String key:{"https_only","public_dns_only","pin_dns","isolated_curl","no_redirects"}) token["security"][key].set_bool(true);
|
print(rejected["error"].to_string(), "\n");
|
||||||
DValue token_response=http_request(token);
|
|
||||||
|
|
||||||
:example
|
|
||||||
// ATProto metadata discovery uses the same generic policy; no provider operation name.
|
|
||||||
DValue meta; meta["method"]="GET"; meta["url"]="https://"+issuer_host+"/.well-known/oauth-authorization-server";
|
|
||||||
meta["headers"]["Accept"]="application/json";
|
|
||||||
for(String key:{"https_only","public_dns_only","pin_dns","isolated_curl","no_redirects"}) meta["security"][key].set_bool(true);
|
|
||||||
DValue metadata=http_request(meta);
|
|
||||||
|
|||||||
@@ -33,3 +33,9 @@ The example uses query-string routing in the same style as the PHP starter, but
|
|||||||
Direct requests to `/examples/uce-starter/index.uce` still work, but self-links are canonicalized back to `/examples/uce-starter/`.
|
Direct requests to `/examples/uce-starter/index.uce` still work, but self-links are canonicalized back to `/examples/uce-starter/`.
|
||||||
|
|
||||||
The demo account pages use a small file-backed user store under `/tmp/uce-starter-data/` with session-based login state.
|
The demo account pages use a small file-backed user store under `/tmp/uce-starter-data/` with session-based login state.
|
||||||
|
|
||||||
|
|
||||||
|
## Optional Datastar Assets
|
||||||
|
|
||||||
|
This starter vendors Datastar v1.0.2 as `js/datastar.js` for future server-rendered interactive islands. It is not loaded by the default page shell. `lib/datastar.uce` contains small Datastar SSE formatting helpers, and `views/datastar-example.uce` is an unlinked opt-in example route.
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,38 @@
|
|||||||
|
String datastar_data_lines(String field, String value)
|
||||||
|
{
|
||||||
|
String out = "";
|
||||||
|
for(String line : split(value, "\n"))
|
||||||
|
out += "data: " + field + " " + line + "\n";
|
||||||
|
return(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
void datastar_sse_headers(Request& context)
|
||||||
|
{
|
||||||
|
context.call["app"]["page_type"] = "blank";
|
||||||
|
context.header["Content-Type"] = "text/event-stream; charset=utf-8";
|
||||||
|
context.header["Cache-Control"] = "no-cache, no-store, must-revalidate";
|
||||||
|
context.header["X-Accel-Buffering"] = "no";
|
||||||
|
}
|
||||||
|
|
||||||
|
String datastar_event(String event_name, StringMap fields)
|
||||||
|
{
|
||||||
|
String out = "event: " + event_name + "\n";
|
||||||
|
for(const auto& field : fields)
|
||||||
|
out += datastar_data_lines(field.first, field.second);
|
||||||
|
out += "\n";
|
||||||
|
return(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
String datastar_patch_elements(String html)
|
||||||
|
{
|
||||||
|
StringMap fields;
|
||||||
|
fields["elements"] = html;
|
||||||
|
return(datastar_event("datastar-patch-elements", fields));
|
||||||
|
}
|
||||||
|
|
||||||
|
String datastar_patch_signals(String signals_json)
|
||||||
|
{
|
||||||
|
StringMap fields;
|
||||||
|
fields["signals"] = signals_json;
|
||||||
|
return(datastar_event("datastar-patch-signals", fields));
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#load "../lib/app.uce"
|
||||||
|
#load "../lib/datastar.uce"
|
||||||
|
|
||||||
|
COMPONENT(Request& context)
|
||||||
|
{
|
||||||
|
if(context.get["stream"] == "1")
|
||||||
|
{
|
||||||
|
datastar_sse_headers(context);
|
||||||
|
print(datastar_patch_elements("<div id=\"datastar-example-result\">Server-rendered UCE Datastar fragment</div>"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
<>
|
||||||
|
<h1>Datastar optional example</h1>
|
||||||
|
<p>Datastar is vendored beside the starter client libraries for future server-rendered islands. It is not loaded globally.</p>
|
||||||
|
<script type="module" src="<?= app_asset_url("js/datastar.js", context) ?>"></script>
|
||||||
|
<button class="btn" data-on:click="@get('<?= app_link("datastar-example", context) ?>&stream=1')">Patch a fragment</button>
|
||||||
|
<div id="datastar-example-result">Waiting for a server patch.</div>
|
||||||
|
</>;
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -22,6 +22,12 @@ RENDER(Request& context)
|
|||||||
file_put_contents(outside_unit, "RENDER(Request& context) {}");
|
file_put_contents(outside_unit, "RENDER(Request& context) {}");
|
||||||
DValue outside_info = unit_info(outside_unit);
|
DValue outside_info = unit_info(outside_unit);
|
||||||
file_unlink(outside_unit);
|
file_unlink(outside_unit);
|
||||||
|
String generated_dir = path_join(context.params["UCE_BIN_DIRECTORY"], "site-tests-generated");
|
||||||
|
mkdir(generated_dir);
|
||||||
|
String generated_unit = path_join(generated_dir, "generated.uce");
|
||||||
|
file_put_contents(generated_unit, "RENDER(Request& context) {}");
|
||||||
|
DValue generated_info = unit_info(generated_unit);
|
||||||
|
file_unlink(generated_unit);
|
||||||
|
|
||||||
ob_start();
|
ob_start();
|
||||||
unit_call("call_helpers.uce", "emit_marker");
|
unit_call("call_helpers.uce", "emit_marker");
|
||||||
@@ -37,6 +43,7 @@ RENDER(Request& context)
|
|||||||
check("unit_info()", info["path"].to_string() != "", json_encode(info));
|
check("unit_info()", info["path"].to_string() != "", json_encode(info));
|
||||||
check("compiler canonicalizes relative unit paths", relative_info["path"].to_string().find("/../") == String::npos && str_ends_with(relative_info["path"].to_string(), "/site/tests/relative-child.uce"), json_encode(relative_info));
|
check("compiler canonicalizes relative unit paths", relative_info["path"].to_string().find("/../") == String::npos && str_ends_with(relative_info["path"].to_string(), "/site/tests/relative-child.uce"), json_encode(relative_info));
|
||||||
check("compiler rejects units outside document root", outside_info["path"].to_string() == "", json_encode(outside_info));
|
check("compiler rejects units outside document root", outside_info["path"].to_string() == "", json_encode(outside_info));
|
||||||
|
check("compiler accepts generated units in BIN_DIRECTORY", generated_info["path"].to_string() == generated_unit, json_encode(generated_info));
|
||||||
check("unit_compile()", unit_compile("call_helpers.uce"), "call_helpers.uce");
|
check("unit_compile()", unit_compile("call_helpers.uce"), "call_helpers.uce");
|
||||||
check("unit_call()", call_output.find("UNIT_CALL_EXPORT_OK") != String::npos, call_output);
|
check("unit_call()", call_output.find("UNIT_CALL_EXPORT_OK") != String::npos, call_output);
|
||||||
check("unit_render()", render_output.find("data-unit-render=\"ok\"") != String::npos, render_output);
|
check("unit_render()", render_output.find("data-unit-render=\"ok\"") != String::npos, render_output);
|
||||||
|
|||||||
+20
-13
@@ -752,22 +752,29 @@ String compiler_normalize_unit_path(Request* context, String file_name)
|
|||||||
if(error || canonical == "")
|
if(error || canonical == "")
|
||||||
return("");
|
return("");
|
||||||
}
|
}
|
||||||
String allowed_root = trim(first(
|
StringList allowed_roots = {
|
||||||
context->params["DOCUMENT_ROOT"],
|
context->params["DOCUMENT_ROOT"],
|
||||||
context->server->config["PRECOMPILE_FILES_IN"],
|
context->server->config["PRECOMPILE_FILES_IN"],
|
||||||
context->server->config["HTTP_DOCUMENT_ROOT"],
|
context->server->config["HTTP_DOCUMENT_ROOT"],
|
||||||
path_join(context->server->config["COMPILER_SYS_PATH"], context->server->config["SITE_DIRECTORY"])
|
path_join(context->server->config["COMPILER_SYS_PATH"], context->server->config["SITE_DIRECTORY"]),
|
||||||
));
|
context->server->config["BIN_DIRECTORY"]
|
||||||
if(allowed_root == "")
|
};
|
||||||
return("");
|
for(String allowed_root : allowed_roots)
|
||||||
if(allowed_root[0] != '/')
|
{
|
||||||
allowed_root = expand_path(allowed_root, context->server->config["COMPILER_SYS_PATH"]);
|
allowed_root = trim(allowed_root);
|
||||||
allowed_root = path_real(allowed_root);
|
if(allowed_root == "")
|
||||||
if(allowed_root == "")
|
continue;
|
||||||
return("");
|
if(allowed_root[0] != '/')
|
||||||
if(allowed_root[allowed_root.length() - 1] != '/')
|
allowed_root = expand_path(allowed_root, context->server->config["COMPILER_SYS_PATH"]);
|
||||||
allowed_root += "/";
|
allowed_root = path_real(allowed_root);
|
||||||
return(canonical + "/" == allowed_root || str_starts_with(canonical, allowed_root) ? canonical : "");
|
if(allowed_root == "")
|
||||||
|
continue;
|
||||||
|
if(allowed_root[allowed_root.length() - 1] != '/')
|
||||||
|
allowed_root += "/";
|
||||||
|
if(canonical + "/" == allowed_root || str_starts_with(canonical, allowed_root))
|
||||||
|
return(canonical);
|
||||||
|
}
|
||||||
|
return("");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool compiler_is_known_unit_file(String file_name)
|
bool compiler_is_known_unit_file(String file_name)
|
||||||
|
|||||||
Reference in New Issue
Block a user