Expand description
http: fetching over the network, and listening on a port.
The Language Card lists the network among the operations that are typed
Host APIs rather than ambient authority, and examples/server/main.cove
shows the shape it expects:
let server = http.listen(8080)?
while server.handle(routes)? {
}
server.close()?Two things there are not ordinary host calls. server is a resource
handle: listen hands back a name for a socket the host keeps, and later
calls are made on that name rather than on the module. And handle is
given a routing table whose entries hold Cove closures, which it has to
run. ADR 0013 is what makes both possible —
crate::host::ResourceHandle for the first and crate::host::Reentry
for the second.
The loop belongs to the program rather than to the host. A serve that
never returned would be a host call outside the reach of the run’s fuel,
its deadline, and its cancellation; handle answers one request and
returns, so the loop around it is ordinary Cove code with ordinary
safepoints.
That is only half of it, because handle itself waits — for a connection,
and then for the request on it — and a host call is a hole in the
safepoint chain for as long as it lasts. So the waiting is bounded the way
crate::host::HostApi says a blocking operation must bound it. A real
listener accepts by polling a nonblocking socket, looks at the run’s
cancellation and at what is left of its deadline between polls, and
answers “nothing more to serve” when either says to stop, which ends the
program’s own loop and lets the run stop at its next safepoint with the
diagnostic the budget owns. One request gets one deadline covering its
line, its headers, and its body together, no longer than what the run has
left. Stopping the run stops the server, and now that is true while it is
idle as well as while it is busy.
Three implementations ship. Http::real speaks HTTP/1.1 over TCP, and
is deliberately small: one request per connection, Connection: close, no
keep-alive, no chunked transfer, and a listener that binds loopback only,
because granting http should not publish a port to the network the
machine is on.
It is small in what it will hold, too, and a reader should know where the
lines are before finding one. A request line longer than eight kibibytes
is answered 414; a header line longer than eight kibibytes, more than a
hundred headers, or more than thirty-two kibibytes of them together are
answered 431; a body over one mebibyte is answered 413. Each bound is
applied while the request is being read rather than after, so what a peer
claims never decides what this host allocates. A Content-Length that is
not a plain count of bytes is answered 400, as are two that disagree,
and a Transfer-Encoding of any kind is answered 501, because a body
whose end this host cannot find is one it will not start. The bounds are
constants, not configuration: this host answers JSON on loopback, and
nothing about that job is served by letting a peer choose how much of this
process it occupies.
The client is bounded on the same argument and by the same number. A
response body over one mebibyte is an error rather than an allocation, and
MAX_RESPONSE_BYTES is where that is said — a server this host reaches
is no more this process’s to trust than a peer that connects to it, and
ADR 0018 already settled
that a host reads what it decided to read rather than what the input asked
it to.
It is bounded in time the same way as well, and by the same mechanism: a
client waiting for a response polls a socket with a short timeout and
looks at the run’s cancellation and deadline between reads, exactly as the
listener polls for a connection. That is what makes a clock.timeout
around a fetch cut the fetch short rather than be reported once the
server has answered — ADR 0024
says a stop is a bound, and a bound the operation under it never observes
is not one. The connect is the step that is bounded without being polled;
connect_within says what that leaves open.
What the client answers is an http.Response: the status the server sent
and the body it carried. A status outside 200-299 is an answer and not a
failure, so a program can tell a 404 it received from a connection it
could not make, which is what a Result<String, Error> could only say in
prose. Http::recorded is the fake: fetch answers from a table of
canned responses and a listener replays a scripted queue of requests, so a
program that serves is testable without a socket. Http::denied refuses
everything and says why.
Structs§
- Http
http: reaching a server, and being one.- Recorded
Response - One answer a fake client gives, as a test wrote it.
- Scripted
Request - One request a fake listener hands to the program, as a test wrote it.
- Served
- What a fake host served, for a test to read back.