fix: harden UCE runtime and starter

This commit is contained in:
udo
2026-06-11 13:44:24 +00:00
parent 71ddcaf7d4
commit 7f757654b6
128 changed files with 276200 additions and 872 deletions
+412 -37
View File
@@ -2,61 +2,436 @@
Request
:sig
Request& context;
Request& context
:see
>types
request_context_params
request_script_url
request_base_url
request_query_path
request_query_route
set_status
component
component_render
unit_render
ws_message
unit_call
session_start
set_cookie
parse_query
parse_multipart
ws_message
ws_connection_id
ws_connections
ws_send
0_DTree
StringMap
UploadedFile
:content
`Request& context` is the request-local state object passed into UCE handlers. It carries incoming request data, response state, runtime metadata, and helper trees such as `context.cfg`, `context.props`, and `context.connection`.
`Request& context` is the request-local state object passed into every UCE handler:
## Core Fields
```cpp
RENDER(Request& context) { ... }
COMPONENT(Request& context) { ... }
WS(Request& context) { ... }
CLI(Request& context) { ... }
SERVE_HTTP(Request* req) { ... }
```
- `ServerState* server`: current server state
- `StringMap params`: FastCGI server parameters
- `StringMap get`: current request GET variables
- `StringMap post`: current request POST variables
- `StringMap cookies`: cookies sent by the browser
- `StringMap session`: current session data
- `String session_id`: session cookie ID
- `String session_name`: session cookie name
- `DTree cfg`: request-local configuration tree
- `DTree call`: invocation-local structured data for helpers such as component and unit calls
- `DTree connection`: broker-owned WebSocket connection state that persists across `WS(Request& context)` calls for the same socket
- `std::vector<UploadedFile> uploaded_files`: files uploaded in the current request
- `StringMap header`: headers to send back to the browser
- `StringList set_cookies`: cookies queued for the response
- `u64 random_seed`: current request noise seed
- `u64 random_index`: current request noise index position
It is the main bridge between the runtime and page code. It contains incoming request data, response state, output buffers, per-request scratch trees, session/cookie state, WebSocket metadata, and runtime diagnostics.
## Response Control
## Handler Lifetime
`context.set_status(s32 code[, String reason])` sets the HTTP status line and updates `context.flags.status`. When `reason` is omitted, UCE uses a built-in standard reason phrase for common status codes.
A fresh `Request` is created for each HTTP/CLI/custom-server request. Component and unit calls normally share that same object, so state placed on `context.call`, `context.header`, `context.session`, or `context.cfg` is visible to later components in the same request.
## Flags And Stats
For WebSockets, each incoming message is delivered as its own `Request`, but `context.connection` points at broker-owned per-socket state that persists for the lifetime of that WebSocket connection.
- `bool flags.log_request`: controls whether the request should be logged
- `u32 stats.bytes_written`
- `f64 stats.time_init`
- `f64 stats.time_start`
- `f64 stats.time_end`
`ONCE(Request& context)` hooks run once per request, per resolved unit file, before the first `RENDER`, `COMPONENT`, `CLI`, or matching entrypoint from that unit.
## Common Usage Notes
## Incoming Request Maps
- `context.cfg` is the usual place for structured configuration. Use `context.cfg.get_by_path("path/to/value")` for deep reads.
- `context.props` carries invocation data for component calls and unit calls.
- `context.in` carries the current request body, and for `WS(Request& context)` it is the current WebSocket message payload.
- `context.params["WS_..."]` exposes the current WebSocket message metadata directly on the request parameter map.
- `context.connection` is only meaningful for WebSocket traffic and persists for the lifetime of the connection.
### `context.params` — server/runtime parameters
`unit_render(String file_name, [Request& context])` invokes another UCE file using the current or supplied request context.
Type: `StringMap`
This is the low-level parameter map from FastCGI/direct HTTP plus UCE-populated convenience fields. It is closest to PHP `$_SERVER`.
Common CGI/FastCGI-style keys include:
- `REQUEST_METHOD`: `GET`, `POST`, etc.
- `REQUEST_URI`: raw request URI where available
- `DOCUMENT_URI`: normalized request path where available
- `SCRIPT_NAME`: script path where available
- `SCRIPT_FILENAME`: resolved filesystem path of the active UCE unit
- `QUERY_STRING`: raw query string
- `DOCUMENT_ROOT`: web root used by the frontend/backend
- `CONTENT_TYPE`: request body content type
- `CONTENT_LENGTH`: request body length
- `HTTP_COOKIE`: raw cookie header
- `HTTP_HOST`, `HTTP_USER_AGENT`, `HTTP_ACCEPT`, and other `HTTP_...` headers supplied by the frontend
UCE also populates convenience route/link fields before handlers run:
- `SCRIPT_URL`: canonical script URL; `/index.uce` is collapsed to the containing directory URL
- `BASE_URL`: canonical directory URL for the script
- `ROUTE_PATH`: sanitized first keyless query-string segment, defaulting to `index` when no route was supplied; empty when unsafe input was rejected
- `ROUTE_PAGE`: first segment of `ROUTE_PATH`
- `ROUTE_PATH_RAW`: normalized but untrusted route input, for diagnostics only
- `ROUTE_VALID`: `1` when route input is safe, `0` when the supplied route was rejected
See `request_context_params`, `request_script_url`, `request_base_url`, `request_query_path`, `request_query_route`, and `route_path_sanitize`.
### `context.get`
Type: `StringMap`
Parsed query-string key/value parameters. This is populated from `context.params["QUERY_STRING"]` with `parse_query()`.
```cpp
String theme = first(context.get["theme"], "default");
```
For front-controller route URLs such as `/?dashboard&theme=dark`, the keyless `dashboard` segment is represented by sanitized `ROUTE_PATH`; named parameters such as `theme=dark` are available in `context.get`.
### `context.post`
Type: `StringMap`
Parsed request body parameters for ordinary `POST` requests. URL-encoded bodies are parsed with `parse_query()`. Multipart form data is parsed with `parse_multipart()` and uploaded files are listed in `context.uploaded_files`.
```cpp
if(context.params["REQUEST_METHOD"] == "POST")
String email = context.post["email"];
```
### `context.cookies`
Type: `StringMap`
Cookies sent by the client, parsed from `HTTP_COOKIE`. `set_cookie()` also updates this map after queuing a response cookie, so later code in the same request can observe the new value.
### `context.in`
Type: `String`
Raw request body. For WebSocket handlers, this is the current message payload.
Use this for JSON APIs:
```cpp
DTree body = json_decode(context.in);
```
### `context.uploaded_files`
Type: `std::vector<UploadedFile>`
Each `UploadedFile` contains:
- `file_name`: original submitted filename
- `tmp_name`: temporary server-side upload path
- `size`: uploaded byte count
Use this with multipart form posts.
## Session State
### `context.session`
Type: `StringMap`
Session data loaded by `session_start()`. UCE does not load sessions automatically for every request; call `session_start()` before reading/writing session data.
```cpp
session_start();
context.session["user_id"] = "42";
```
At the end of a successful request, modified session data is saved automatically if a session is active.
### `context.session_id` and `context.session_name`
The active session ID and cookie name after `session_start()`.
Related helpers:
- `session_start()`
- `session_destroy()`
- `session_id_create()`
- `set_cookie()`
## Per-request Structured Trees
### `context.call`
Type: `DTree`
General request-local scratch/configuration tree. It is shared by the page, components, and unit calls participating in the current request. Use it for app-level request state, fragments, router results, page type, page title, and other values that need to be read by later components.
Examples from front-controller style apps:
```cpp
context.call["route"] = request_query_route(context);
context.call["app"]["page_type"] = "html";
context.call["fragments"]["main"] = captured_html;
```
Prefer clear top-level names when state is app-wide (`route`, `fragments`) and nested app names only when the state is truly owned by that app (`app/page_title`, `app/page_type`).
### `context.cfg`
Type: `DTree`
Request-local structured configuration. The runtime does not fill this with application config by default; application code may assign it during boot/setup:
```cpp
context.cfg = get_config();
```
This is separate from `context.server->config`, which is the runtime/server string config from `/etc/uce/settings.cfg`.
Use `get_by_path()` for non-mutating deep reads:
```cpp
String site_name = context.cfg.get_by_path("site/name").to_string();
```
### `context.props`
Type: `DTree`
Invocation-local props for `component()`, `component_render()`, and macro-style `unit_call()` entrypoints. During a component call, the runtime temporarily replaces `context.props` with the props passed to that component and restores the previous value after the call returns.
```cpp
DTree props;
props["title"] = "Dashboard";
print(component("components/card", props, context));
```
### `context.connection`
Type: `DTree`
WebSocket connection-local state. Mutations persist across `WS(Request& context)` calls for the same socket.
```cpp
context.connection["message_count"] = context.connection["message_count"].to_u64() + 1;
```
Only meaningful for WebSocket handlers.
## Response State
### `context.response_code`
Type: `String`
The raw status line. Usually use `context.set_status(...)` instead of writing this directly.
### `context.header`
Type: `StringMap`
Response headers to emit. Header names are case-sensitive as written.
```cpp
context.header["Content-Type"] = "application/json";
context.header["Location"] = "/info/";
```
### `context.set_cookies`
Type: `StringList`
Queued `Set-Cookie` header lines. Prefer `set_cookie()` instead of editing this directly.
### `context.set_status(code[, reason])`
Sets the HTTP response status and `context.flags.status`.
```cpp
context.set_status(404, "Not Found");
context.set_status(302, "Found");
context.header["Location"] = app_link("dashboard", context);
```
Related helpers:
- `redirect(url[, code])`
- `set_cookie(...)`
## Output Buffers
### `context.ob_stack` and `context.ob`
Internal output-buffer stack. Most code should use helpers instead of touching these directly:
- `print(...)`
- `out(...)`
- `ob_start()`
- `ob_get()`
- `ob_get_close()`
- `ob_close()`
Common capture pattern:
```cpp
ob_start();
print(component("views/dashboard", context));
String html = ob_get_close();
context.call["fragments"]["main"] = html;
```
### `context.out` and `context.err`
Runtime output/error artifacts used by some transports and failure paths. Normal page rendering should use `print()` / output buffers.
## Request Flags
`context.flags` contains runtime booleans and the numeric status:
- `log_request`: whether the request should be logged
- `is_finished`: internal completion marker
- `status`: numeric HTTP status set by `set_status()`
- `output_closed`: internal transport state
- `params_closed`: internal transport state
- `input_closed`: internal transport state
Most page code only reads `flags.status`, if anything.
## Request Stats
`context.stats` contains counters/timing for the request:
- `bytes_written`
- `time_init`
- `time_start`
- `time_end`
- `mem_high`
- `mem_alloc`
- `invoke_count`
These are useful for diagnostics, demos, and runtime instrumentation.
## Random / Noise State
- `random_seed`
- `random_index`
Used by UCE noise/random helpers to provide request-local deterministic progression.
Related helpers include functions in the noise/hash area such as `gen_int`, `gen_float`, `gen_noise64`, and `gen_sha1`.
## WebSocket Fields
In `WS(Request& context)`, the runtime mirrors WebSocket metadata into `context.params` and `context.resources`.
Convenience `context.params` keys include:
- `WS_MESSAGE`
- `WS_CONNECTION_ID`
- `WS_SCOPE`
- `WS_CONNECTION_COUNT`
- `WS_OPCODE`
- `WS_MESSAGE_TYPE`
- `WS_DOCUMENT_URI`
Prefer WebSocket helper functions where possible:
- `ws_message()`
- `ws_connection_id()`
- `ws_scope()`
- `ws_opcode()`
- `ws_is_binary()`
- `ws_connections()`
- `ws_connection_count()`
- `ws_send()`
- `ws_send_to()`
- `ws_close()`
Use `context.connection` for per-socket structured state.
## Runtime / Resource Fields
### `context.server`
Pointer to server state. Useful mainly for low-level/runtime code. Runtime config lives at:
```cpp
context.server->config["KEY"]
```
This is a `StringMap`, separate from app-owned `context.cfg`.
### `context.resources`
Internal runtime resources and transport state. Includes sockets, MySQL handles, WebSocket state, current unit file, and parser buffers. Application code should normally use public helpers instead of editing this directly.
Notable fields:
- `is_websocket`
- `is_cli`
- `websocket_connection_id`
- `websocket_scope`
- `websocket_scope_connection_ids`
- `current_unit_file`
## Common Patterns
### Minimal page
```cpp
RENDER(Request& context)
{
<><h1>Hello <?= context.get["name"] ?></h1></>
}
```
### JSON endpoint
```cpp
RENDER(Request& context)
{
context.header["Content-Type"] = "application/json";
DTree response;
response["ok"].set_bool(true);
print(json_encode(response));
}
```
### Redirect
```cpp
RENDER(Request& context)
{
context.set_status(302, "Found");
context.header["Location"] = "/info/";
}
```
### Component props
```cpp
DTree props;
props["title"] = "Welcome";
print(component("components/card", props, context));
```
### Front-controller route
```cpp
context.call["route"] = request_query_route(context);
String route_path = context.call["route"]["l_path"].to_string();
```
Or use runtime-populated params directly:
```cpp
String route_path = context.params["ROUTE_PATH"];
```
## Related Concepts
- PHP: `$_SERVER`, `$_GET`, `$_POST`, `$_COOKIE`, `$_SESSION`, `header()`, and `http_response_code()`
- JavaScript / Node.js: Express `req` and `res`, Fetch `Request`, `Headers`, cookies or session middleware, and per-connection state in WebSocket handlers
- PHP: `$_SERVER`, `$_GET`, `$_POST`, `$_COOKIE`, `$_SESSION`, `header()`, output buffering, and `http_response_code()`
- JavaScript / Node.js: Express `req`/`res`, Fetch `Request`/`Response`, route params, middleware-populated locals, and per-socket WebSocket state