Skip to content
heff

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 BOC
import co
import custodian as Custodian
import std.domain as Domain
import std.http as HTTP
import std.http.bocserver as BocServer
import std.http.server as Server
import std.net as Net
import std.reactor as Reactor
import std.signal as Signal
import std.time as Time
import resource
-- | each request answered at once
hello : {e w : HFunctor}
-> {{bc : BOC.boc w <: e}}
-> Server.Request
-> BocServer.Responder w
-> Co e Unit
hello 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 closed
served : {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 Unit
served 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 are
helloServed : {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 Unit
helloServed = 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 Unit

How 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 Unit

The 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.