Frame brokered HTTP responses once

This commit is contained in:
root 2026-07-18 01:33:57 +00:00
parent 6e4ef8ed7a
commit f89e33e241
6 changed files with 26 additions and 2 deletions

View File

@ -62,6 +62,15 @@ forced by Wasmtime: an `Engine`/`Store` cannot be safely re-created across
runtime. So the brokers hold the long-lived connection and units respond to
events the same way they respond to a page request — through a clean worker.
The broker-to-worker hop explicitly sets `GATEWAY_INTERFACE=CGI/1.1`. After
the Wasm core decodes request parameters it derives the guest's default status
syntax from that marker (`Status:` for FastCGI, `HTTP/1.1` for direct HTTP).
The FastCGI transport clears completed stdout/stderr as soon as their records
are queued and emits no stream records after `FCGI_END_REQUEST`. Together these
boundaries prevent a forwarded dynamic response from being parsed as body and
wrapped in a second HTTP response. The TCP and custom-server tests reject an
inner status line and require the exact dynamic body.
---
## 2. The membrane and the DValue ABI

View File

@ -421,6 +421,11 @@ void cli_run_tcp_smoke()
cli_test_case("uce_tcp_smoke:http websocket port 8080", fd8080 != 0, "TCP connect " + String(fd8080 != 0 ? "succeeded" : "failed") + " on port 8080");
if(fd8080 != 0)
socket_close(fd8080);
CliHttpResponse direct = cli_direct_http("/tests/pool-isolation.uce?token=raw-http-framing");
StringList direct_parts = split(trim(direct.body), "|");
bool one_response = direct.status == 200 && direct_parts.size() == 3 && direct_parts[0] == "fresh" && direct_parts[1] == "raw-http-framing" && direct.body.find("HTTP/1.1 ") == String::npos;
cli_test_case("uce_tcp_smoke:raw HTTP dynamic response is framed once", one_response,
one_response ? "single status/header block and exact dynamic body" : "status=" + std::to_string(direct.status) + " body=" + cli_truncate(direct.body));
}
void cli_run_wasm_kill(bool include_kill)

View File

@ -66,7 +66,7 @@ RENDER(Request& context)
socket_close(stopped_sockfd);
mark(
"server_start_http() / SERVE_HTTP:named / server_stop()",
(custom_http_pid != 0 && write_ok && response.find("custom-http-named") != String::npos && response.find("query=alpha=1") != String::npos && stopped && listener_closed) ? "pass" : "fail",
(custom_http_pid != 0 && write_ok && response.find("custom-http-named") != String::npos && response.find("query=alpha=1") != String::npos && response.find("\r\nHTTP/1.1 ") == String::npos && stopped && listener_closed) ? "pass" : "fail",
response.substr(0, response.length() > 260 ? 260 : response.length()) + " stopped=" + (stopped ? "true" : "false") + " listener_closed=" + (listener_closed ? "true" : "false")
);
}

View File

@ -1388,6 +1388,8 @@ FastCGIServer::request_write_fgci(Connection& connection, RequestID id,
//switch_to_system_alloc();
write_data_fcgi(connection.output_buffer, id, request.out, FCGI_STDOUT);
write_data_fcgi(connection.output_buffer, id, request.err, FCGI_STDERR);
request.out.clear();
request.err.clear();
FCGI_EndRequestRecord complete;
bzero(&complete, sizeof(complete));

View File

@ -855,7 +855,13 @@ static String custom_server_dispatcher_key = "";
int forward_request_to_worker(FastCGIRequest& request, u32 timeout_seconds = 30)
{
String fcgi_socket = first(server_state.config["FCGI_SOCKET_PATH"], "/run/uce.sock");
FcgiForwardResult fwd = fcgi_forward_request(fcgi_socket, request.params, request.in, timeout_seconds);
StringMap forward_params = request.params;
// The broker accepted raw HTTP, but this hop is FastCGI. Without the CGI
// marker Request::set_status() emits an HTTP status line that the FastCGI
// reply parser correctly treats as body; the broker would then wrap that
// complete response in a second HTTP response.
forward_params["GATEWAY_INTERFACE"] = "CGI/1.1";
FcgiForwardResult fwd = fcgi_forward_request(fcgi_socket, forward_params, request.in, timeout_seconds);
request.ob_start();
if(!fwd.ok)
{

View File

@ -830,6 +830,8 @@ int uce_wasm_apply_context(const char* buf, size_t len)
if(applied.key("server_config"))
apply_map(applied.key("server_config"), wasm_server.config);
apply_map(applied.key("params"), wasm_request.params);
wasm_request.response_code = wasm_request.params["GATEWAY_INTERFACE"] != "" ?
"Status: 200 OK" : "HTTP/1.1 200 OK";
apply_map(applied.key("get"), wasm_request.get);
apply_map(applied.key("post"), wasm_request.post);
apply_map(applied.key("cookies"), wasm_request.cookies);