Skip to content
heff

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 co
import custodian as Custodian
import exception
import std.http as HTTP
import std.http.server as Server
import std.net as Net
import std.time as Time
hello : {e : HFunctor} -> Server.Request -> Co e HTTP.Response
hello 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 thrown
helloAt : {e : HFunctor}
-> {{nt : Net.net <: e}}
-> {{clock : Time.time <: e}}
-> {{fail : exception Net.NetError <: e}}
-> {{cu : Custodian.custodian <: e}}
-> Int
-> Co e Unit
helloAt port = do
r <- Net.withListener { host = "127.0.0.1", port = port } 64 (\l -> Server.serve l hello)
either throw pure r

A 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 : String -> Request -> Maybe String

The first header of that name, its case not minded (an ASCII name’s), if there is one.

plain

plain : Int -> String -> HTTP.Response

A 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 Request

A 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 Int
data Head = HeadAt Bytes Int | HeadTooLong | HeadEnded

Got

data Got = Asked Request Bytes | Refused Int | Ended

requestIn

requestIn : {from : Int := 0}
-> Net.Address
-> Bytes
-> Either (Maybe Request, Int) Got

What 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 -> String

A status’s reason, as a status line says it (OK, Not Found); a status not known, none.

responseBytes

responseBytes : String -> Bool -> HTTP.Response -> Bytes

A 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 -> Bool

Whether 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 Bool

serveConnection

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 Bool

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

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

The listener’s connections served, until one fails to be accepted: why thrown, the listener left as it is (serveUntil).