Skip to main content

Module http

Module http 

Source
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.
RecordedResponse
One answer a fake client gives, as a test wrote it.
ScriptedRequest
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.