Reference

std/http/message

std/http/src/message.trb

The parts of an HTTP message: Method, Status, Headers, the Body that is a stream, and the two messages, Request and Response (docs/design/NETWORK.md section 6).

type Method

type Method with Show, Equals, Hash

An HTTP method: a case per method RFC 9110 registers, Query (RFC 10008) and WebDAV's Search (RFC 5323), and Other for the rest - the set is open (a WebDAV server meets PROPFIND), so a router matches on the cases and still sees every method.

match request.method {
  .Get => ...
  .Post => ...
  _ => ...
}

Method.of is the one way in from text: it answers the case for a registered name and Other only for the rest, so GET is never Other("GET"). A name is case-sensitive, as RFC 9110 section 9.1 says: get is Other("get").

case Get

case Get

Not documented.

case Head

case Head

Not documented.

case Post

case Post

Not documented.

case Put

case Put

Not documented.

case Delete

case Delete

Not documented.

case Patch

case Patch

Not documented.

case Options

case Options

Not documented.

case Trace

case Trace

Not documented.

case Connect

case Connect

Not documented.

case Query

case Query

QUERY (RFC 10008, June 2026): a query in the request's content rather than in the URI - safe and idempotent like GET, with a body like POST, and its response cacheable with the content as part of the key.

case Search

case Search

SEARCH of WebDAV (RFC 5323): a search whose request is the body, safe and idempotent.

case Other

case Other(name: String)

A method none of the cases names. Made by Method.of, which never makes one for a name a case has.

fn of

static fn of(name: String): Result<Method, HttpError>

The method of that name. A name that is not a token of RFC 9110 - empty, with a space, a control character or a separator - is no method.

fn name

fn name(): String

"GET".

fn isSafe

fn isSafe(): Bool

GET, HEAD, OPTIONS, TRACE, QUERY and SEARCH: a request of these asks and changes nothing (RFC 9110 section 9.2.1, RFC 10008 section 2, RFC 5323 section 2). Other is not: nothing is known of it.

fn isIdempotent

fn isIdempotent(): Bool

The safe methods, PUT and DELETE: sending a request of these twice has the effect of sending it once (RFC 9110 section 9.2.2), so a client may send it again where the connection failed before an answer arrived.

fn carriesContent

fn carriesContent(): Bool

POST, PUT, PATCH, QUERY and SEARCH: a request of these carries content, and announces even an empty one.

fn show

fn show(): String

Not documented.

type Status

type Status with Show, Equals, Hash, Compare

An HTTP status: a capsule over its code, from 100 to 599, with the common ones as constants.

Response.text("no such user", status: Status.notFound)

const ok

static ok = Status 200

Not documented.

const created

static created = Status 201

Not documented.

const accepted

static accepted = Status 202

Not documented.

const noContent

static noContent = Status 204

Not documented.

const movedPermanently

static movedPermanently = Status 301

Not documented.

const found

static found = Status 302

Not documented.

const seeOther

static seeOther = Status 303

Not documented.

const notModified

static notModified = Status 304

Not documented.

const temporaryRedirect

static temporaryRedirect = Status 307

Not documented.

const permanentRedirect

static permanentRedirect = Status 308

Not documented.

const badRequest

static badRequest = Status 400

Not documented.

const unauthorized

static unauthorized = Status 401

Not documented.

const forbidden

static forbidden = Status 403

Not documented.

const notFound

static notFound = Status 404

Not documented.

const methodNotAllowed

static methodNotAllowed = Status 405

Not documented.

const notAcceptable

static notAcceptable = Status 406

Not documented.

const requestTimeout

static requestTimeout = Status 408

Not documented.

const conflict

static conflict = Status 409

Not documented.

const gone

static gone = Status 410

Not documented.

const lengthRequired

static lengthRequired = Status 411

Not documented.

const contentTooLarge

static contentTooLarge = Status 413

Not documented.

const uriTooLong

static uriTooLong = Status 414

Not documented.

const unsupportedMediaType

static unsupportedMediaType = Status 415

Not documented.

const unprocessableContent

static unprocessableContent = Status 422

Not documented.

const tooManyRequests

static tooManyRequests = Status 429

Not documented.

const requestHeaderFieldsTooLarge

static requestHeaderFieldsTooLarge = Status 431

Not documented.

const internalServerError

static internalServerError = Status 500

Not documented.

const notImplemented

static notImplemented = Status 501

Not documented.

const badGateway

static badGateway = Status 502

Not documented.

const serviceUnavailable

static serviceUnavailable = Status 503

Not documented.

const gatewayTimeout

static gatewayTimeout = Status 504

Not documented.

const httpVersionNotSupported

static httpVersionNotSupported = Status 505

Not documented.

fn of

static fn of(code: Int): Result<Status, HttpError>

The status of that code: 100 to 599, which is every code HTTP can carry.

fn code

fn code(): Int

404.

fn reason

fn reason(): String

The phrase RFC 9110 registers for the code, or "" for a code it does not.

fn isInformational

fn isInformational(): Bool

1xx.

fn isSuccess

fn isSuccess(): Bool

2xx.

fn isRedirect

fn isRedirect(): Bool

3xx.

fn isClientError

fn isClientError(): Bool

4xx.

fn isServerError

fn isServerError(): Bool

5xx.

fn show

fn show(): String

404 Not Found, or the code alone where no phrase is registered.

fn compare

fn compare(other: Status): Ordering

Not documented.

type Headers

type Headers with Show, Equals

The fields of a head, in the order they arrived. A name is compared without regard to case and kept as it was written; a name may repeat (Set-Cookie), which is why this is not a Map.

var headers = Headers()
headers.set "Content-Type", "text/plain"
print headers.get("content-type")

A value that holds a line break or a NUL is written with spaces in their place, and a field whose name is not a token is not written at all: a header built from what a client sent cannot smuggle a second header in.

fn get

fn get(name: String): String?

The first value of name, or None.

fn all

fn all(name: String): List<String>

Every value of name, in order.

fn contains

fn contains(name: String): Bool

Whether a field of that name is there.

fn length

fn length(): Int

How many fields there are, repeated names counted each time.

fn set

var fn set(name: String, value: String)

Replaces every field of name by one with value, where the first of them was; at the end where there was none.

fn add

var fn add(name: String, value: String)

Adds a field, also where one of that name is there already.

fn remove

var fn remove(name: String)

Removes every field of name.

fn entries

fn entries(): List<(name: String, value: String)>

Every field, in order: for (name, value) in headers.entries() { ... }.

fn show

fn show(): String

Content-Type: text/plain, one field per line.

extend Headers with From<Map<String, String>>

extend Headers with From<Map<String, String>>

Headers.from(["Accept": "application/json"]): the entries of a map, in its order.

fn from

static fn from(value: Map<String, String>): Headers

type Body

shared type Body with Source<Bytes, HttpError>

The bytes of a request or a response, as a stream that is read once.

const all = response.body.through(Json.items<User>()).checked().toList().await()?   // one at a time
const text = response.body.text().await()?                                          // or all of it
File.write(path, response.body.mapFailure(IoError.from)).await()?  // or straight to disk

The convenience methods come first on purpose - that is the lesson of fetch and Bun, where await response.json() is what almost every program wants - and every one of them takes a limit, because a peer that decides how much memory a program allocates is a denial of service.

const defaultLimit

static defaultLimit: Int = 16777216

16 MiB: large enough for any document a program means to hold in one piece, small enough that a body nobody expected is refused instead of filling the machine. Streaming is the way past it, not a bigger number.

fn next

var fn next(): Task<Result<Bytes?, HttpError>>

The next chunk, or None at the end of the body. Most callers reach for Body.bytes or Body.text instead.

fn close

var fn close()

Nothing of its own: reading is a field, and the release of the body releases it - and with it the underlying connection - right after this, without reading the rest.

fn length

fn length(): Int?

How many bytes the body has, where that is known before it is read.

fn bytes

var fn bytes(limit: Int = Body.defaultLimit): Task<Result<Bytes, HttpError>>

The whole body. More than limit bytes is an HttpError and the rest is not read.

fn text

var fn text(limit: Int = Body.defaultLimit): Task<Result<String, HttpError>>

The whole body as text. Bytes that are not UTF-8 are an HttpError, never a replacement character.

fn json

var fn json<Value: Decode>(limit: Int = Body.defaultLimit): Task<Result<Value, HttpError>>

The whole body, decoded as JSON: response.body.json<User>().await()?

fn lines

var fn lines(): Source<String, HttpError>

Lines of text, as a stream: a log, an event stream, a CSV of any size.

fn of

static fn of(var source: Source<Bytes, HttpError>, length: Int? = None): Body

A body of any size - what an upload or a proxy hands on - with its length where the source knows it. Not Body.from(source): From.from takes its argument by value, and handing a stream over needs the permission to read it (var), because the Body reads it from now on.

fn empty

static fn empty(): Body

A body with nothing in it, e.g. for a GET request.

fn jsonOf

static fn jsonOf<Value: Encode>(value: Value): Body

An Encode value as a JSON body. Not Body.json(value): a type has one namespace for its members, and json is taken by the reading side, where it matters more (body.json<User>()).

extend Body with From<String>

extend Body with From<String>

Body.from("hello") - the text as UTF-8.

fn from

static fn from(value: String): Body

extend Body with From<Bytes>

extend Body with From<Bytes>

Body.from(bytes) - one chunk, already in memory.

fn from

static fn from(value: Bytes): Body

type Request

shared type Request

A request: what a client sends and what a server's handler is handed. A shared type and not a value, because it owns its body, a stream that is read once.

fn handle(request: Request): Task<Result<Response, HttpError>> {
  if request.path() == "/hello" {
    return Ok Response.text("hello")
  }
  Ok Response.of(Status.notFound)
}

field method

method: Method

Not documented.

field target

target: String

The request target as it was sent: /users/7?details=true, or *, or host:443 for CONNECT.

field uri

uri: Uri

The target URI RFC 9112 section 3.3 rebuilds from the target, the Host field and whether the connection is TLS: https://example.test/users/7?details=true. Normalized, so a router sees a path whose dot segments are gone.

field headers

headers: Headers = Headers()

Not documented.

field body

var body: Body

var, because reading a body changes it: whoever reads one needs a var path to it.

field remote

remote: SocketAddress? = None

Where the request came from, on a server; None on a client.

field version

version: String = "HTTP/1.1"

HTTP/1.1 or HTTP/1.0.

fn path

fn path(): String

The path of the target URI: /users/7.

fn segments

fn segments(): List<String>

The path of the target URI split at its /, still percent-encoded: ["users", "7"].

fn query

fn query(): String?

The query of the target URI, or None.

type Response

shared type Response

What came back from a server, or what a handler answers. A shared type and not a value: it owns one end of a stream that is read once, and a copy would promise a second read of a body that is already gone. So a response that is read from sits in a var binding, for the same reason a File does.

field status

status: Status

Not documented.

field headers

var headers: Headers = Headers()

var, so that a handler can add a field to a response it made.

field body

var body: Body

var, because reading a body changes it: whoever reads one needs a var path to it.

fn of

static fn of(status: Status): Response

A response of status with no body: Response.of(Status.notFound).

fn text

static fn text(content: String, status: Status = Status.ok): Response

A text as UTF-8, with Content-Type: text/plain; charset=utf-8.

fn jsonOf

static fn jsonOf<Value: Encode>(value: Value, status: Status = Status.ok): Response

An Encode value as JSON, with Content-Type: application/json.

fn json

var fn json<Value: Decode>(limit: Int = Body.defaultLimit): Task<Result<Value, HttpError>>

Decodes the body as JSON. A Task, because the body arrives over the network and decoding it means reading it.