Handler endpoints bypass page routing, return raw fragments

Paths containing (api.) are intercepted before page routing and
dispatched directly to the api function. The handler result is
rendered to SX wire format and returned without layout wrapping.

This fixes the issue where handler URLs went through page routing,
causing the handler result to be passed as a slug to the page
function, and the response to be wrapped in the full page layout.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-01 09:50:16 +00:00
parent 8027f51ef3
commit 4ea43e3659

View File

@@ -2566,9 +2566,50 @@ let http_mode port =
in
write_response fd (http_response ~content_type:"text/plain; charset=utf-8" result); true
end else
(* Handler endpoints: paths containing "(api." are handler calls,
not page renders. Evaluate the handler directly, return fragment. *)
let is_handler_path =
let rec has_sub s sub i =
if i + String.length sub > String.length s then false
else if String.sub s i (String.length sub) = sub then true
else has_sub s sub (i + 1) in
has_sub path "(api." 0 in
let is_sx = path = "/sx/" || path = "/sx"
|| (String.length path > 4 && String.sub path 0 4 = "/sx/") in
if is_sx then begin
if is_sx && is_handler_path then begin
(* Handler dispatch — evaluate handler, return raw fragment *)
let response =
try
let api_fn = env_get env "api" in
(* Extract handler slug from path: ...api.SLUG)... *)
let slug =
let rec find_api s i =
if i + 5 > String.length s then ""
else if String.sub s i 5 = "(api." then
let start = i + 5 in
let end_ = try String.index_from s start ')' with Not_found -> String.length s in
String.sub s start (end_ - start)
else find_api s (i + 1) in
find_api path 0 in
let result = Sx_ref.cek_call api_fn (List [String slug]) in
(* Render to SX wire format or HTML depending on content type *)
let body_str = match result with
| String s | SxExpr s -> s
| Nil -> ""
| _ ->
let call = List [Symbol "aser"; List [Symbol "quote"; result]; Env env] in
(match Sx_ref.eval_expr call (Env env) with
| String s | SxExpr s -> s
| v -> Sx_types.inspect v) in
http_response ~content_type:"text/sx; charset=utf-8" body_str
with e ->
Printf.eprintf "[handler] Error for %s: %s\n%!" path (Printexc.to_string e);
http_response ~status:500 ~content_type:"text/sx; charset=utf-8"
(Printf.sprintf "(div :class \"p-4 text-rose-600\" \"Handler error: %s\")"
(escape_sx_string (Printexc.to_string e)))
in
write_response fd response; true
end else if is_sx then begin
let cache_key = if is_ajax then "ajax:" ^ path else path in
match Hashtbl.find_opt response_cache cache_key with
| Some cached -> write_response fd cached; true