Reference

std/http/server

std/http/src/server.trb

The HTTP/1.1 server: a listener, a handler that is a function from a request to a task of a response, an accept loop per worker on the one listening socket, and one task per connection that serves its requests one after another (docs/design/NETWORK.md sections 4 and 8).

alias Handler

type Handler = (request: Request) => Task<Result<Response, HttpError>>

What serves a request: a function from the request to a task of the response. A web framework is one of these.

type ServerLimits

type ServerLimits

What a server allows a client, each against an attack of its own. Times are milliseconds, because a field's default is a constant and a Duration is none.

field headBytes

headBytes: Int = 65536

The request line and every field together: nginx allows 8 KiB a line and 32 KiB in all; a browser with many cookies needs more.

field headFields

headFields: Int = 100

Fields of one head: the count Go and Node refuse above.

field headMilliseconds

headMilliseconds: Int = 10000

The time from the first byte of a request to the end of its head: a client that sends it a byte a minute (slowloris) is cut off.

field idleMilliseconds

idleMilliseconds: Int = 60000

How long a kept-alive connection may wait for its next request.

field drainBytes

drainBytes: Int = 65536

What of a request's body the handler did not read is read and dropped up to this; more closes the connection.

type Server

shared type Server with Close

An HTTP/1.1 server. It listens as soon as it is made; Server.serve accepts connections until the server is shut down or closed - with an accept loop on every worker, all of them on the one listening socket - and every connection is a task of its own, on the worker whose loop accepted it.

fn hello(request: Request): Task<Result<Response, HttpError>> {
  Ok Response.text("hello from {request.path()}")
}

var server = Server.listen(SocketAddress(IpAddress.loopback, 8080), hello)?
var serving = server.serve()
// ... and when the program is to stop:
server.shutdown().await()

A handler that fails is answered with the status its failure names where it names one - the body it read was cut short, not UTF-8 or not the JSON asked for - and 500 Internal Server Error otherwise; the failure itself is not sent to the client. A request the parser refuses is answered with the status its failure names (HttpError.answerStatus), and the connection is closed. The server writes no Server field: a product name on the wire tells an attacker what to try first, and a handler that wants one sets it.

fn listen

static fn listen(address: SocketAddress, handler: Handler, limits: ServerLimits = ServerLimits(), tls: ServerIdentity? = None): Result<Server, HttpError>

A server listening on address; port 0 asks the system for a free one, which Server.localAddress then says. With tls, every connection is HTTPS: the TLS handshake comes first, within the head time of the limits.

fn localAddress

fn localAddress(): SocketAddress

Where it listens.

fn serve

var fn serve(): Task<Result<Void, HttpError>>

Accepts connections and serves each in a task of its own, until the server is shut down or closed - then it answers Ok - or the listening socket fails.

It starts one accept loop per worker (Workers.count()), each accepting on the same listening socket, so the connections spread over every core: a loop holds nothing but the socket's handle, the handler and the limits, so an idle worker takes it before its first run, and a connection stays on the worker whose loop accepted it. A loop stays on this task's worker where the handler holds an object that cannot move, and a server that speaks TLS runs one loop, because its identity is such an object - correct either way, and parallel where it can be.

fn serveOne

var fn serveOne(): Task<Result<Void, HttpError>>

Accepts one connection and serves it to its end: what a test wants.

fn shutdown

var fn shutdown(grace: Duration = 10.seconds()): Task<Void>

Stops gracefully: no connection is accepted any more, a connection that waits for its next request is closed at once, a request in progress is finished and answered with Connection: close, and what still runs once grace has passed is cancelled. Finishes when every connection has ended.

fn close

var fn close()

Stops at once: the listener closes and every accept loop is cancelled, and with it every connection it serves. What the release of a server runs.