std.http.server
An HTTP/1.1 server, written over std.net’s sockets: a listener’s
connections accepted one at a time, each one’s requests read in turn
— the request line, the headers, and a body of the length its
Content-Length says — each given to a handler, and its response
written with its length, the connection kept for the next request
unless it is to be closed (HTTP/1.0, or Connection: close), and
closed by the with holding it when it is done. So it is the C
backend’s alone, as std.net is. Import it by a name, import std.http.server as Server, beside import std.http as HTTP, whose
Response a handler gives. A server of many connections at once is
a module of its own: std.http.cmlserver’s serves each by
serveConnection, a cml thread a connection; std.http.bocserver’s
reads each a piece at a time, by requestIn, every wait an act boc’s
behaviours await.
import coimport custodian as Custodianimport exceptionimport std.http as HTTPimport std.http.server as Serverimport std.net as Netimport std.time as Time
hello : {e : HFunctor} -> Server.Request -> Co e HTTP.Responsehello r = pure (Server.plain 200 ("hello, " ^ r.target ^ "\n"))
-- | `hello` served on the port, until a connection cannot be accepted,-- | or the port not listened on: then that is thrownhelloAt : {e : HFunctor} -> {{nt : Net.net <: e}} -> {{clock : Time.time <: e}} -> {{fail : exception Net.NetError <: e}} -> {{cu : Custodian.custodian <: e}} -> Int -> Co e UnithelloAt port = do r <- Net.withListener { host = "127.0.0.1", port = port } 64 (\l -> Server.serve l hello) either throw pure rA request whose body is sent in chunks (Transfer-Encoding) is answered
501 Not Implemented, and its connection closed; a head longer than 64
KiB, 431; a body longer than 64 MiB, 413; a request that is no
HTTP/1.x, 400 or 505. Before it is closed, the server shuts its
side down and reads what the client still sends — a mebibyte of it at
most — so that the client is not reset before it has read the
response.
A connection whose client sends nothing for idle milliseconds — 30
seconds unless a server is given another, by name, Server.serve (idle := 5000) l handle; 0 for no limit — is closed: between requests,
within a request’s head or its body, and while what it still sends is
read before its connection is closed. A response is written within the
same limit. So a client that holds a connection and is silent holds
it no longer than that, by a scheduler that waits on the world through
std.reactor or by none. A client that is not silent, but sends a
request’s head a little at a time, is held to a limit of its own: the
whole head is read within headerWithin milliseconds — 30 seconds
unless another is given, by name; 0 for no limit — of the
connection’s start, or of the response before it, or the connection
is closed, unanswered, as one silent too long is. So a server’s
world has std.time’s clock, time, beside std.net’s net.
Request
type Request = { method : String , target : String , version : String , headers : List (String, String) , body : Bytes , peer : Net.Address }A request, as a server is sent it: its method (GET), its target as
the request line says it (/items?id=3), its version (HTTP/1.1),
its headers (a name and a value each, in their order), its body,
and where it came from. (std.http’s Request is a client’s: a URL,
a time to wait, redirects followed.)
header
header : String -> Request -> Maybe StringThe first header of that name, its case not minded (an ASCII name’s), if there is one.
plain
plain : Int -> String -> HTTP.ResponseA response of text, text/plain, UTF-8.
>>> (plain 404 "no such item").headers[MkPair "Content-Type" "text/plain; charset=utf-8"]parseHead
parseHead : Net.Address -> String -> Maybe RequestA request’s head, its lines without the blank one after them: its method, target, version and headers, its body to come; none where it is no request’s.
>>> maybe "none" (\(r : Request) -> r.method ^ " " ^ r.target) (parseHead { host = "::1", port = 1 } "GET /a?b HTTP/1.1\r\nHost: x\r\nX-Y: z ")"GET /a?b">>> maybe Nil (\(r : Request) -> r.headers) (parseHead { host = "::1", port = 1 } "GET / HTTP/1.1\r\nHost: x\r\nX-Y: z ")[MkPair "Host" "x", MkPair "X-Y" "z"]>>> maybe "none" (\(r : Request) -> r.method) (parseHead { host = "::1", port = 1 } "GET HTTP/1.1")"none"BodyIs
data BodyIs = Length Int | Refuse IntHead
data Head = HeadAt Bytes Int | HeadTooLong | HeadEndedGot
data Got = Asked Request Bytes | Refused Int | EndedrequestIn
requestIn : {from : Int := 0} -> Net.Address -> Bytes -> Either (Maybe Request, Int) GotWhat the bytes read so far of a connection’s next request come to,
read all at once, not waiting for more (a server that reads a piece
at a time asks after each): the request, and what was read after
it (Asked), or the status it is answered with (Refused); else,
not all read yet, Left: its head, where that is read — the
request, its body to come — and how many bytes in all it wants at
least. The head’s end, its blank line, is looked for from from
on, by name (0, the start, unless another is given): a reader that
has looked through what came before, or knows where it is, says so.
>>> either (\(p : (Maybe Request, Int)) -> debug (snd p)) (\g -> "got") (requestIn { host = "::1", port = 1 } b["GET / HTTP/1.1\r\nContent-Length: 3\r\n\r\nab"])"40">>> either (\(p : (Maybe Request, Int)) -> "more") (\g -> "whole") (requestIn { host = "::1", port = 1 } b["GET / HTTP/1.1\r\nContent-Length: 3\r\n\r\nabcGET"])"whole">>> either (\(p : (Maybe Request, Int)) -> "more") (\g -> "whole") (requestIn (from := 14) { host = "::1", port = 1 } b["GET / HTTP/1.1\r\n\r\n"])"whole"reason
reason : Int -> StringA status’s reason, as a status line says it (OK, Not Found); a
status not known, none.
responseBytes
responseBytes : String -> Bool -> HTTP.Response -> BytesA response as it is written: its status line, its headers but its
own length and connection, its length (Content-Length, where its
status has a body), Connection: connection where that is not
empty, and its body where withBody (not for a HEAD).
>>> debug (responseBytes "close" True (plain 200 "hi"))"b[\"HTTP/1.1 200 OK\", 13, 10, \"Content-Type: text/plain; charset=utf-8\", 13, 10, \"Content-Length: 2\", 13, 10, \"Connection: close\", 13, 10, 13, 10, \"hi\"]">>> debug (responseBytes "" False { status = 204, headers = [MkPair "Content-Length" "9"], body = b["x"] })"b[\"HTTP/1.1 204 No Content\", 13, 10, 13, 10]"keptAlive
keptAlive : Request -> BoolWhether a request’s connection is kept after its response, as the
request asks: HTTP/1.1’s unless it says Connection: close,
HTTP/1.0’s where it says Connection: keep-alive.
After
data After = Next Bytes | Done BoolserveConnection
serveConnection : {e : HFunctor} -> {s : Type} -> {idle : Int := 30000} -> {headerWithin : Int := 30000} -> {{nt : Net.net <: e}} -> {{clock : Time.time <: e}} -> (Request -> Bool) -> (Request -> Co e HTTP.Response) -> With s Net.TcpStream -> Co e BoolA connection’s requests answered by handle, one after another,
until it ends, a request asks for it to be closed, or last says
the request is the last the server answers: whether last said so.
The connection is closed by the with holding it, Net.accept’s
or Net.withConnection’s, when its body ends. Where the client
sends nothing for idle milliseconds, it ends there (0: no limit);
where a request’s head is not all read within headerWithin
milliseconds of the connection’s start, or of the response before
it, too (0: no limit).
serveUntil
serveUntil : {e : HFunctor} -> {s : Type} -> {idle : Int := 30000} -> {headerWithin : Int := 30000} -> {{nt : Net.net <: e}} -> {{clock : Time.time <: e}} -> {{ex : exception Net.NetError <: e}} -> {{cu : Custodian.custodian <: e}} -> (Request -> Bool) -> With s Net.TcpListener -> (Request -> Co e HTTP.Response) -> Co e UnitThe listener’s connections accepted, one at a time, and each one’s
requests answered by handle (serveConnection), until last says
a request is the last: answered, its connection closed, and the
listener left as it is. Where a connection fails to be accepted —
the system out of descriptors (EMFILE), say — the server stops
there, why thrown (Net.accept’s NetError), the listener left as
it is: whether to wait and serve again is its caller’s to say.
serve
serve : {e : HFunctor} -> {s : Type} -> {idle : Int := 30000} -> {headerWithin : Int := 30000} -> {{nt : Net.net <: e}} -> {{clock : Time.time <: e}} -> {{ex : exception Net.NetError <: e}} -> {{cu : Custodian.custodian <: e}} -> With s Net.TcpListener -> (Request -> Co e HTTP.Response) -> Co e UnitThe listener’s connections served, until one fails to be accepted:
why thrown, the listener left as it is (serveUntil).