Status: Superseded by ADR-289 — its D1-D5 are D17-D21 there Related: ADR-008 (views and what tethers them), ADR-011 D4 (a switch chooses the executor and nothing else), ADR-017 (escaping is the template’s contract)
Part III 17.1 shows a server as
http::Server::new()
.route("/") fn { "Hello World" }
.listen(":8080")
and that is the whole of the handler’s signature: it takes nothing. fortunes.nika did not
notice, because the fortunes benchmark needs neither a query parameter nor a status code — which
is exactly how the gap survived. The other six TechEmpower tests need both, and so does every
program anyone would write.
The question is not whether a handler sees the request. It is what shape that takes in a language whose lambdas already have a rule about arguments.
A block lambda takes as many implicit arguments as its body reaches for (Part I, 5.3). Applied here, that is the whole decision:
.route("/") fn { "Hello World" } // mentions none, takes none
.route("/hello") fn { "Hello, {a.query("name") ?? "world"}" }
.route("/fortunes") fn(request) { render(request) } // or name it
Nothing is added to the language and the specification’s own example keeps working, unchanged and for the same reason it worked before. A handler that does not mention the request does not receive it; one that does, does.
a is the request. There is no second implicit argument: a response is returned (D2), not
filled in.
std says which types do.| returns | becomes |
|---|---|
String |
200, text/plain; charset=utf-8 |
html::Raw |
200, text/html; charset=utf-8 |
Response |
itself |
T throws |
the value on success; on failure 500 with a generic body, the error logged |
The last row is a decision and not an omission. An error’s message is written for the operator, and a handler that returns one to the client is how internal paths, query fragments and driver messages end up in a bug bounty report. The error goes to the log with its context; the client gets a status.
html::Raw earning text/html is not a convenience: it is ADR-017 D2 read from the
other end. The type that says “this is already markup” is the type that may be sent as markup, and
a String — which might be anything — may not be.
Response, built where it is returned.fn {
if a.query("id").is_none() {
http::Response(status: 400, body: "id is required")
} else {
lookup(a.query("id")??)
}
}
The anonymous-constructor form of Kap 4.2, with named fields, and no builder chain: a response is a value, and the shape a program most often needs — a status and a body — is one line without ceremony. Headers are a field like the others.
request.path(), request.header("host") and request.query("name") yield &str into the bytes
the connection read. This is ADR-008 applied without a new rule: a handler that uses a parameter
inside the request’s own scope pays nothing, and one that stores it past the response has the
compiler tell it to own the value first. At the request rates this specification argues about, one
allocation per parameter is not a rounding error.
The surface is deliberately small, because each entry is a promise:
pub fn method(&self) -> Method // an enum (Kap 4.4), not a string
pub fn path(&self) -> &str
pub fn query(&self, name: &str) -> &str? // Kap 3.5's nullable, not an empty string
pub fn header(&self, name: &str) -> &str? // case-insensitive, as the protocol is
pub fn body(&self) -> &[u8] // bytes; what they mean is the handler's business
What is not here: no params map that a route pattern fills in (route patterns are their own
decision), no cookie parsing, no multipart. Each is a specification of its own and none of them is
what G6 was about.
async.The same handler runs at every setting; a switch chooses the executor and nothing else
(ADR-011 D4). A handler does I/O — that is what it is for — so it is not sync
(Part II, 12.1), and it carries no async marker and no await, which is Part II 11.1 and the
reason std::fs reads the way it does.
For fortunes.nika. Its [G6] note becomes a reference to this ADR, and fortunes keeps the
signature it had: it is the handler that needs nothing from the request, which is what made it a
poor test of this corner in the first place.
For the implementation. Nothing here can be built before the runtime binding (tokio), which
is the roadmap line after the bootstrap compiler. This ADR is therefore a decision and not a
delivery, and says so in its status: what a handler is no longer waits on that work.
For the six other TechEmpower tests. plaintext and json need D2’s return types, db and
queries need D3’s status codes, and updates needs D4’s query parameters. None of them now needs
a language change to be written.
current_request() a handler calls. A hidden argument passed through thread-local state,
which is a lie under a work-stealing executor and untestable either way.fn(request, response), the two-argument form. It makes the response mutable, which makes
“was it already sent?” a question every middle layer has to ask. Returning the response answers
it once.Request argument that is always required. It would break the specification’s own
fn { "Hello World" } for a handler that has no use for it — and the implicit-argument rule
already says what to do.String as HTML. The one-line demo would be prettier and every string in the
program would be a potential injection. ADR-017 D2 gives markup a type; this is the same line.