///|
/// Dispatches a synthetic HTTP request through the full routing + middleware
/// pipeline and returns `(status_code, headers, body_bytes)`.
pub async fn App::dispatch(
self : App,
http_method : String,
url : String,
headers : Map[String, String],
body : Bytes,
) -> (StatusCode, Map[String, String], Bytes) {
let request_path = @httputil.request_target_path(url)
// Look up route
let (params, handler) = match
self.lookup_http_route(http_method, request_path) {
Found(matched_handler, matched_params) => (matched_params, matched_handler)
MethodNotAllowed(allow) => ({}, method_not_allowed_handler(allow))
Options(allow) => ({}, options_handler(allow))
NotFound => ({}, self.resolve_not_found_handler(request_path))
}
// Construct event
let event = {
req: HttpRequest::from_method_string(http_method, url, headers, body),
res: HttpResponse(status_code=OK),
params,
}
// Execute middleware chain + handler
let responder = execute_middlewares(self.middlewares, event, handler)
finalize_response_body(event.res, responder)
// HEAD responses carry headers but no body bytes on the wire. Match the
// live send_raw_response_async path: synthesize Content-Length from the
// materialized body (unless the handler already set it), THEN drop the
// body. TestClient callers can still assert on Content-Length.
let body = if http_method == "HEAD" {
if !@httputil.has_header_case_insensitive(
event.res.headers,
"Content-Length",
) {
event.res.headers.set(
"Content-Length",
event.res.raw_body.length().to_string(),
)
}
b""
} else {
event.res.raw_body
}
(event.res.status_code, event.res.headers, body)
}