std.http.bocserver
For an HTTP/1.1 server on boc’s behaviours: a listener’s connections
served at once, as many as come, each a cown of its own, each step
of each a behaviour — a request parsed and handed to the handler, a
response written, the next request read — and every wait (an
accept, a read, a write) an act awaited (BOC.await), so no domain
and no cown is held while a client is slow. Run under boc’s
scheduledWithReactor, each act waits in the reactor of a domain,
and the connections are served in parallel on as many domains as the
machine has cores. It is the C backend’s alone, as std.net is.
Import it by a name, import std.http.bocserver as BocServer.
import boc as BOCimport coimport custodian as Custodianimport std.domain as Domainimport std.http as HTTPimport std.http.bocserver as BocServerimport std.http.server as Serverimport std.net as Netimport std.reactor as Reactorimport std.signal as Signalimport std.time as Timeimport resource
-- | each request answered at oncehello : {e w : HFunctor} -> {{bc : BOC.boc w <: e}} -> Server.Request -> BocServer.Responder w -> Co e Unithello r respond = respond (Server.plain 200 ("hello, " ^ r.target ^ "\n"))
-- | the listener served until SIGTERM or Ctrl-C: then closed, the-- | requests in hand answered, and every connection closedserved : {e w : HFunctor} -> {s : Type} -> {{bc : BOC.boc w <: e}} -> {{nt : Net.net <: w}} -> {{clock : Time.time <: w}} -> {{sg : Signal.signals <: w}} -> With s Net.TcpListener -> Co e Unitserved l = do BocServer.serve Empty l hello with _ := BOC.await (Signal.wait [Terminate, Interrupt]) do with _ := BOC.await (Net.closeListener l) do pure ()
-- | served on port 8080, its behaviours run on domains, each waiting-- | on the world through a reactor of its own, the scheduler on the-- | program's world itself; done once they arehelloServed : {e : HFunctor} -> {{nt : Net.net <: e}} -> {{clock : Time.time <: e}} -> {{sg : Signal.signals <: e}} -> {{rx : Reactor.reactor <: e}} -> {{dm : Domain.domain <: e}} -> {{cu : Custodian.custodian <: e}} -> Co e UnithelloServed = do r <- Net.withListener { host = "127.0.0.1", port = 8080 } 128 (\l -> BOC.scheduledWithReactor (\why -> pure ()) (served l)) pure ()serve err l handle comes back at once, the server running in its
behaviours: the listener is held by a with around the scheduler,
which ends once the server is done. A connection is had by
std.net’s accepted, in the listener’s region, and closed by the
server: when its client ends it or fails, when a request asks
(HTTP/1.0, Connection: close), or when the server stops. The
handler is given each request in a behaviour on its connection’s
cown, and a responder: it may answer there and then, or later, from
another behaviour — after acts of its own awaited, a request of
std.http, a query — the connection meanwhile waiting, read no
further. Its first response is written; one after it is not. A
handler that never responds leaves its connection open until the
program lets go of it.
Requests are read as std.http.server reads them, by its requestIn,
a piece at a time, each piece an act: a body by Content-Length, a
100 Continue written where the client waits for one, a request
refused as std.http.server refuses it (400, 413, 431, 501,
505), what the client still sends then read, up to a mebibyte, and
let go of before its connection is closed. Requests sent one after
another on a connection without waiting (pipelined) are answered in
turn. A connection whose client sends nothing for idle
milliseconds — 30 seconds unless another is given, by name,
BocServer.serve (idle := 5000) l handle; 0 for no limit — is
closed, within a request or between two; a response not taken in that
time too. A client that is not silent but sends a request’s head a
little at a time, which idle alone would let hold its connection
for ever, is held to headerWithin milliseconds for the whole head —
30 seconds unless another is given, by name; 0 for no limit —
counted from the connection’s accept, or from the response before it
(so on a connection kept open, the wait for the next request counts
too): past that, the connection is closed, unanswered, as one silent
too long is. The time is std.time’s monotonic clock, so the server’s
world has time. A head’s end is looked for in each piece only as
it is read, with the three bytes before it, and the head is parsed
once, when all of it is read.
The server stops where a connection cannot be accepted: the listener
closed (Net.closeListener, as above on a signal), or the program
out of descriptors (EMFILE). Then the connections waiting for a
request are closed at once, and the others once the request in hand
is answered: once the last is closed, nothing of the server is
pending, and scheduledWithReactor ends, if nothing else is. The
handler runs in a behaviour, of the scheduler’s world, and may throw
an err (Empty where it throws nothing): a throw no recover of
the handler’s hears closes that connection at once, unanswered, what
was thrown let go of — a handler that is to say why catches it
itself — and the others are served on. A handler that fails
otherwise (abandons) fails its behaviour, which boc’s failed hears:
its request is not answered, and its connection, read no further, is
closed where the program lets go of it, at the run’s end.
Responder
type Responder (w : HFunctor) = {e : HFunctor} -> {{bc : BOC.boc w <: e}} -> HTTP.Response -> Co e UnitHow a handler answers its request: in whichever world has boc’s behaviours, its own or a later behaviour’s.
serve
serve : {e : HFunctor} -> {w : HFunctor} -> {rg : Type} -> {idle : Int := 30000} -> {headerWithin : Int := 30000} -> (err : Type) -> {{bc : BOC.boc w <: e}} -> {{nt : Net.net <: w}} -> {{clock : Time.time <: w}} -> With rg Net.TcpListener -> (Server.Request -> Responder w -> Co (exception err <+> (BOC.boc w <+> w)) Unit) -> Co e UnitThe listener’s connections served, each request given to handle
with a responder, every wait an act awaited — so as many
connections are served at once as come, none holding a domain while
it waits. Comes back at once, the server running in behaviours of
its own, until a connection cannot be accepted (the listener closed,
EMFILE): then the connections waiting for a request are closed,
and the others once the request in hand is answered. A client that
sends nothing for idle milliseconds, or takes no response in that
time, is closed; so is one that has not sent all of a request’s
head within headerWithin milliseconds of the connection’s accept,
or of the response before it. The handler, of the scheduler’s
world, may throw an err: that connection is closed at once,
unanswered, and what was thrown let go of. For
scheduledWithReactor: under scheduled, each act holds a domain
until it is done.