essenceDocumentation
essenceDocs
on this pageAt a glance
Coming from JavaScript?

Http

namespace

Requests to a host, as work that has not run: what to send, what comes back, and why sometimes nothing does.

Requests to a host, as work that has not run.

Taught on: Asynchrony.

At a glance

Declared
namespace Http
Views

A view is a namespace for a narrower type. A value of that type has the view's methods on top of its own.

Modes
  • HttpMethodWhich method of HTTP a request is made with.
  • RedirectsWhat a request does with a redirect a host answers with.
  • HttpFailureWhy a request answered with no Response.

Either side of a request

Request

type Request = {
	url: String,
	method: HttpMethod,
	headers: Dictionary<String, String>,
	body: Optional<String>,
	redirects: Redirects,
}

A request to send.

Only url has to be written. The default on Http.send fills in a #Get with no headers, no body and #Follow.

The body is a String or nothing. A request carrying one names its own content-type in the headers, because this library adds none.

Response

type Response = {
	status: Integer,
	headers: Dictionary<String, String>,
	body: String,
}

What a host answered with.

The header keys are lowercase, which is what makes a Dictionary the right shape for them. HTTP header names do not differ by case, and header(named:) reads one under any spelling.

A name a host sent twice is one entry, with its values joined by , . That is what a host does to every other header name, and set-cookie is joined the same way here. Two cookies joined like that can not be read apart again, because a cookie's Expires holds a comma.

The body is text, decoded as UTF-8. Bytes that are not text are read as replacement characters, so an answer that is not text is a Response with a lossy body.

Sending a request

send

on Http

static send(
	_ request: Request = {
		method = #Get,
		headers = [=],
		body = #Empty,
		redirects = #Follow,
	},
) -> Future<Result<Response, HttpFailure>>

Answers work that sends the given request and answers with what the host said.

Nothing is sent while the answer is being built. Each start sends the request again, so a request that is started twice is made twice.

A request that reaches a host answers #Value with its Response, whatever the status is. A request that reaches none answers #Failure with the reason.

A run that is stopped never answers. The runs that within and race leave behind are stopped, and what one would have answered is never read.

NameTypeDescription
_Request = {the request to send; every member but url has a default

work answering the Response, or the reason there is none.

The verbs

get

on Http

static get(
	_ url: String,
	headers: Dictionary<String, String> = [=],
) -> Future<Result<Response, HttpFailure>>

Answers work that asks the given address for what it holds.

NameTypeDescription
_Stringthe address to ask
headersDictionary<String, String> = [=]the headers to send; none when it is left out

work answering the Response, or the reason there is none.

post

on Http

static post(
	_ url: String,
	body: String,
	headers: Dictionary<String, String> = [=],
) -> Future<Result<Response, HttpFailure>>

Answers work that sends the given body to the address.

NameTypeDescription
_Stringthe address to send to
bodyStringthe body to send
headersDictionary<String, String> = [=]the headers to send; none when it is left out

work answering the Response, or the reason there is none.

put

on Http

static put(
	_ url: String,
	body: String,
	headers: Dictionary<String, String> = [=],
) -> Future<Result<Response, HttpFailure>>

Answers work that puts the given body at the address.

NameTypeDescription
_Stringthe address to put to
bodyStringthe body to send
headersDictionary<String, String> = [=]the headers to send; none when it is left out

work answering the Response, or the reason there is none.

patch

on Http

static patch(
	_ url: String,
	body: String,
	headers: Dictionary<String, String> = [=],
) -> Future<Result<Response, HttpFailure>>

Answers work that sends the given body as a change to what the address holds.

NameTypeDescription
_Stringthe address to change
bodyStringthe body to send
headersDictionary<String, String> = [=]the headers to send; none when it is left out

work answering the Response, or the reason there is none.

delete

on Http

static delete(
	_ url: String,
	headers: Dictionary<String, String> = [=],
) -> Future<Result<Response, HttpFailure>>

Answers work that asks the address to remove what it holds.

NameTypeDescription
_Stringthe address to ask
headersDictionary<String, String> = [=]the headers to send; none when it is left out

work answering the Response, or the reason there is none.

on Http

static head(
	_ url: String,
	headers: Dictionary<String, String> = [=],
) -> Future<Result<Response, HttpFailure>>

Answers work that asks the address for the headers alone.

The answer carries the headers a get would answer with, and an empty body.

NameTypeDescription
_Stringthe address to ask
headersDictionary<String, String> = [=]the headers to send; none when it is left out

work answering the Response, or the reason there is none.

Reading the answer

isSuccessful

on Response

isSuccessful() -> Boolean

Answers whether the status is one of the successful ones.

The successful statuses are 200 through 299. A redirect, a refusal and a host error are each a Response that answers false here.

true when the status is between 200 and 299.

Example
constant answered: Response = {
  status = 204,
  headers = [=],
  body = "",
}

expect answered::isSuccessful()

on Response

header(named name: String) -> Optional<String>

Answers the header of the given name, whatever case the name is written in.

The keys of a Response a host answered with are lowercase, so the name is lowercased before it is looked up. A header the answer does not carry answers nothing.

A Response a Program wrote down itself is found only where it wrote the key in lowercase. The lookup key is always the lowercased name, and a key written Content-Type is reached by no spelling at all.

NameTypeDescription
namedStringthe name of the header, in any case

the header's value, or nothing where the answer carries none.

Example
constant answered: Response = {
  status = 200,
  headers = ["content-type" = "text/plain"],
  body = "hi",
}

expect answered::header(named "Content-Type")::is("text/plain")
expect answered::header(named "accept")::isEmpty()

Reading a failure

toString

on HttpFailure

toString() -> String

Answers the failure as a String, written InvalidUrl(…), Unreachable(…) or InvalidBody(…).

The reason is quoted, as a String payload is quoted inside any structure. The # sigil is left out, as Result prints Failure and Ordering prints Less.

the rendering of the Case and its reason.

Example
constant failure = HttpFailure#Unreachable("refused")

expect failure::toString()::is("Unreachable(\"refused\")")

Choices

HttpMethod

choice HttpMethod {
	Get,
	Post,
	Put,
	Patch,
	Delete,
	Head,
	Options,
}

Which method of HTTP a request is made with.

#Get is what a request that names none is made with. The Cases are the seven methods this library sends, spelled as the Choice a method member takes.

Cases #Get, #Post, #Put, #Patch, #Delete, #Head, #Options

Redirects

choice Redirects {
	Follow,
	Manual,
	Refuse,
}

What a request does with a redirect a host answers with.

Under #Follow the request is made again at the new address, which is what a request that names no Case does. Under #Manual the redirect is the answer where the host hands it over. On Node and Bun it carries the status and the location header that says where it points. A browser answers an opaque redirect instead, with no status and no headers. Under #Refuse a redirect is a #Failure, and its reason is #Unreachable.

Cases #Follow, #Manual, #Refuse

HttpFailure

choice HttpFailure {
	InvalidUrl { url: String },
	Unreachable { reason: String },
	InvalidBody { reason: String },
}

Why a request answered with no Response.

The Case #InvalidUrl is an address this library does not send to. It covers an address that can not be read at all, and one whose scheme is neither http nor https.

The Case #Unreachable is a host that was not reached. It covers a refused connection, a name that does not resolve and a certificate that was not accepted. It also covers a host with no way to send a request. A redirect a request refused to follow is one. So is a request this library refused to send. A #Get or a #Head carrying a body is one, and so is a header name HTTP does not allow.

The Case #InvalidBody is an answer whose body could not be read. The answer had been delivered, and reading its bytes failed part way.

A status is none of these. A host that answers 404 answered, and 404 is a Response.

Cases #InvalidUrl { url: String }, #Unreachable { reason: String }, #InvalidBody { reason: String }

esc