kex docs Standard Library 0.4.0-alpha kex.run ↗

Net.HTTP

module Net.HTTP

Buffered HTTP clients, responses, and a small declaration-ordered server router. Requests never follow redirects or perform generic retries implicitly.

using Net.HTTP

let response = HTTP.get("https://example.test/").try
response.status.success?   # => true

For connection reuse and statistics, own a client explicitly:

let client = Client.open.try
let response = client.get("https://example.test/").try
client.close.try

record Headers

An insertion-ordered HTTP field collection. Names compare case-insensitively and duplicate fields are preserved.

Fields

entries
[(String, String)]

record Status

A validated HTTP status code in 100..599.

Fields

code
Integer

record Response<B>

A typed HTTP response envelope whose body representation is explicit.

Fields

status
Status
headers
Headers
body
B

record Request<B>

A typed HTTP request envelope whose body representation is explicit.

Fields

method
String
target
URI
headers
Headers
body
B

record RouteContext

Route captures decoded after path segmentation.

Fields

parameters
Map<String, String> optional

record Context

Per-request server context.

Fields

route
RouteContext

type Handler

A buffered HTTP route handler.

Variants

  • abstract

record Route

One declared route; routers preserve declaration order.

Fields

method
String
path
String
handler
Handler

record Router

An immutable, declaration-ordered HTTP router.

Fields

routes
[Route] optional

record ShutdownReport

Counts and elapsed time from graceful server shutdown.

Fields

completed
Integer
failed
Integer
forced
Integer
elapsedMilliseconds
Integer

record ServerOptions

Bounded HTTP server resources and default graceful-shutdown duration.

Fields

maximumHandlers
Integer optional
backlog
Integer optional
gracefulShutdown
Duration optional

record PoolOptions

HTTP connection-pool bounds and idle lifetime.

Fields

perOrigin
Integer optional
total
Integer optional
queuedRequests
Integer optional
idleExpiryMilliseconds
Integer optional

record ClientOptions

Options owned by an explicit HTTP client.

Fields

pool
PoolOptions optional

record ClientStatistics

Lifetime request/reuse counters plus current pooled connections.

Fields

openConnections
Integer
requests
Integer
reusedConnections
Integer

record ClientCloseReport

Resources released by Client.close.

Fields

closedConnections
Integer

type Client

The pooled HTTP client. An opaque handle over the connection pool that owns it; Client.open makes one and client.close releases it.

module Net.HTTP.Headers

Construction and parsing of validated HTTP header collections.

constant empty Headers

Returns a field collection with no entries.

function from

Validates header names and values without folding duplicates.

Duplicate fields stay in their original order. Invalid names and values containing line breaks are rejected instead of creating a malformed or injectable HTTP message.

from(entries) : [(String, String)] -> Result<Headers, NetError>

Returns: Result<Headers, NetError> — validated fields, or Parse

Examples

Forwarding an explicitly selected set of request headers

Headers.from([
  ("Accept", "application/json"),
  ("X-Request-ID", requestId)
]).try

function parse

Parses CRLF- or LF-separated header fields.

Use this at a protocol boundary when headers arrive as text. Application code normally builds them with from, add, and set.

parse(text) : String -> Result<Headers, NetError>

Returns: Result<Headers, NetError> — fields in source order, or Parse

Examples

Parsing headers captured from a diagnostic fixture

Headers.parse("Content-Type: text/plain\r\nX-Trace: abc\r\n").try

module Net.HTTP.Status

Validation for numeric HTTP status codes.

function from

Validates an HTTP status code.

from(code) : Integer -> Result<Status, NetError>
Parameters
code Integer
a status in 100..599

Returns: Result<Status, NetError> — the status, or Parse

Examples

Validating a configurable health-check success code

let code = ENV.get("HEALTH_STATUS").flatMap { |text| text.to(Integer) }.or(204)
let expected = Status.from(code).try

module Net.HTTP.Response

Buffered response constructors for route handlers.

function binary

Builds a buffered binary response with validated headers.

binary(status, body, headers)
Examples

Returning a downloaded file without decoding it as text

Response.binary(200, archive, Headers.empty.set("Content-Type", "application/zip"))

function text

Builds a UTF-8 text response with an explicit text/plain content type.

text(status, body)
Examples

A small health endpoint

Response.text(200, "ok")

function empty

Builds a response with an empty body.

empty(status)
Examples

A successful DELETE endpoint

Response.empty(204)

module Net.HTTP.Router

The empty starting point for an immutable route declaration chain.

constant build ?

make Router

route

Appends a route; earlier matching declarations win.

Use this for a method without a convenience function. Paths may include named or wildcard captures, which the handler reads from RouteContext.

route(method, path, handler)
Examples

Adding a custom method

router.route("PURGE", "/cache/:key", ~purge)

get

Appends a GET route. GET also supplies automatic HEAD fallback.

get(path, handler)

head

Appends an explicit HEAD route, overriding automatic GET fallback.

head(path, handler)

options

Appends an explicit OPTIONS route, overriding generated OPTIONS.

options(path, handler)

post

Appends a POST route.

post(path, handler)

put

Appends a PUT route.

put(path, handler)

patch

Appends a PATCH route.

patch(path, handler)

delete

Appends a DELETE route.

delete(path, handler)

module Net.HTTP.Server

Starting, observing, and gracefully stopping HTTP servers.

type Running

An opaque asynchronous HTTP server handle.

function start

Starts a server with conservative defaults and returns immediately.

The returned handle owns the listener and active handlers. Use start when the process has other work to do; use serve for a foreground server whose main job is handling HTTP.

start(endpoint, router)
Examples
let router = Router.build.get("/health", do |request, context|
  Response.text(200, "ok")
end)
let endpoint = Net.Socket.TCP.Endpoint.loopback(Net.Port.from(0).try)
let server = Server.start(endpoint, router).try
Server.stop(server).try

function serve

Starts with defaults and blocks until the server stops.

serve(endpoint, router)

function stop

Gracefully stops using the duration captured at start.

New requests stop being accepted while in-flight handlers get their grace period to finish. The report says how much work completed or was forced down during shutdown.

stop(server)
Examples

Shutting down from an application lifecycle hook

let report = Server.stop(server).try
IO.printLine("closed after ${report.completed} requests")

function join

Waits until the server owner exits.

join(server)

function running?

running?(server)

Returns: Bool — whether the server owner is alive

function localAddress

Returns the bound address, including an operating-system-assigned port.

localAddress(server)

Returns: Net.Socket.TCP.Endpoint — the bound address

Examples

Publishing the actual address of a test server

let endpoint = Server.localAddress(server)
IO.printLine("test server: http://${endpoint.host}:${endpoint.port.string}")

module Net.HTTP.Client

Constructors for explicitly owned, connection-pooling HTTP clients.

function open

Opens an explicit pooled client with conservative defaults.

Reuse one client for related requests so keep-alive connections and DNS work can be reused. Close it when the owning service shuts down.

open() : Result<Client, NetError>
open(options) : ClientOptions -> Result<Client, NetError>
Examples

Fetching several pages through one connection pool

let client = Client.open.try
let first = client.get("https://api.example.com/items?page=1").try
let second = client.get("https://api.example.com/items?page=2").try
client.close.try

make Client

request

Sends a buffered request. Redirects and generic retries are not implicit.

This keeps policy with the caller: inspect a redirect before following it, and retry only methods and failures your application knows are safe.

request(method, url, headers, body)
Examples

Sending JSON with an idempotency key

let headers = Headers.empty
  .set("Content-Type", "application/json")
  .set("Idempotency-Key", requestId)
client.request("POST", url, headers, JSON.stringify(order).to(Binary).try)

get

Sends a buffered GET request.

get(url)

post

Sends a buffered binary POST request.

post(url, body)

put

Sends a buffered binary PUT request.

put(url, body)

patch

Sends a buffered binary PATCH request.

patch(url, body)

delete

Sends a DELETE request with an empty body.

delete(url)

head

Sends a HEAD request; the returned body is empty.

head(url)

options

Sends an OPTIONS request.

options(url)

statistics

Reports current pool occupancy and lifetime request counters.

statistics()

Returns: ClientStatistics — current pool and lifetime request counters

Examples

Emitting client-pool diagnostics

let stats = client.statistics
IO.printLine("HTTP reuse: ${stats.reusedConnections}/${stats.requests}")

close

Idempotently closes the client and every idle pooled connection.

Further requests fail with Closed; a second close is harmless.

close()

make Headers implements Showable, Inspectable

add

Appends a field without replacing existing fields of the same name.

add(name, value)
Examples

Headers.empty.add("Accept", "text/plain")

set

Replaces all fields of name with one value.

set(name, value)
Examples

headers.set("Content-Type", "application/json")

remove

Removes every field matching name case-insensitively.

remove(name)
Examples

Stripping hop-by-hop state before forwarding

let forwarded = incoming.remove("Connection")

get

Returns the first matching field value.

get(name)
Examples

Selecting a response decoder

let contentType = response.headers.get("Content-Type").or("application/octet-stream")

getAll

Returns every matching value in insertion order.

getAll(name)
Examples

Preserving every Set-Cookie field

let cookies = response.headers.getAll("Set-Cookie")

inspectValue

Structural inspection uses the same credential-safe rendering.

inspectValue(colors)

make Status

make RouteContext

parameter

Returns one decoded named or wildcard route capture.

parameter(name)
Parameters
name String
the capture name declared in the route

Returns: Result<String, NetError> — the capture, or Parse when absent

Examples

Reading :id from a /users/:id route

let id = context.parameter("id").try

module Net.HTTP.HTTP

Stateless HTTP convenience calls for scripts and occasional requests.

function request

Sends one stateless buffered request with no redirect or hidden retry.

Each call owns a short-lived client. This is convenient for scripts and occasional requests; use Client for a service making repeated calls.

request : String -> String -> Headers -> Binary -> Result<Response<Binary>, NetError>
Examples

A one-off authenticated request in a command-line tool

let headers = Headers.empty.set("Authorization", "Bearer ${token}")
HTTP.request("GET", url, headers).try

function get

Sends one stateless buffered GET.

get(url) : String -> Result<Response<Binary>, NetError>
Examples

HTTP.get("https://example.test/").try

function delete

Sends one stateless DELETE with an empty body.

delete(url) : String -> Result<Response<Binary>, NetError>

function head

Sends one stateless HEAD and returns an empty response body.

head(url) : String -> Result<Response<Binary>, NetError>

function options

Sends one stateless OPTIONS request.

options(url) : String -> Result<Response<Binary>, NetError>

function post

Sends one stateless buffered binary POST.

post(url, body) : String -> Binary -> Result<Response<Binary>, NetError>

function put

Sends one stateless buffered binary PUT.

put(url, body) : String -> Binary -> Result<Response<Binary>, NetError>

function patch

Sends one stateless buffered binary PATCH.

patch(url, body) : String -> Binary -> Result<Response<Binary>, NetError>

Submodules

  • Net.HTTP.WebSocket High-level RFC 6455 client messages. The runtime handles fragmentation and ping/pong frames; reconnect and heartbeat policies remain application-owned.