http_gun
Starts HTTP clients on Gun and sends requests through them.
import gleam/http/request
import http_gun
import http_gun/config
pub fn main() {
let assert Ok(client) = http_gun.start(config.default())
let assert Ok(req) = request.to("https://example.com/data")
let result = http_gun.send(client, request.set_body(req, <<>>))
http_gun.stop(client)
result
}
There is one function per operation: send collects a response into
Buffered, open and with_response stream an owned body.Body, and
batch runs many requests with bounded concurrency. Non-2xx statuses are
response data. The client performs no retries, redirects or
decompression.
Client views
Per-call settings live on the client handle. Each with_* view returns a
new handle over the same pool, and every operation honours it:
let stream =
client
|> http_gun.with_timeout(config.Infinity) // a long-lived SSE stream
|> http_gun.with_correlation(order)
with_timeout and with_deadline replace the client’s request timeout,
shorter or longer; the connect, pool and idle timeouts still apply.
with_destination can only narrow the destinations the client admits.
A client that serves several tenants admits the union of their
destinations, which any call holding the handle without a view reaches;
config.require_view_destination makes such a call fail closed with
ViewDestinationRequired. Only a view policy that narrows the client’s
destinations satisfies it; a policy that only tightens the plaintext rule
does not, so a library tightening the scheme keeps the requirement.
A library that receives a view reads the caller’s correlation with
correlation(client) and copies it into its own events, so one
with_correlation by the caller tags both packages’ events.
Lifecycle
start links a client to the caller. Under a supervisor, use
supervised(config, name); named(name) returns a handle that keeps
working across restarts. During a restart, calls fail with ClientClosed
and NotSent. stop refuses new work, lets open responses finish within
the shutdown timeout, then cancels them.
http_gun/testing and http_gun/cassette start offline and recording
clients of the same Client type.
Types
A collected response. Read fields by label; it may gain fields.
pub type Buffered {
Buffered(
response: response.Response(BitArray),
trailers: List(#(String, String)),
protocol: config.Negotiated,
truncated: Bool,
)
}
Constructors
-
Buffered( response: response.Response(BitArray), trailers: List(#(String, String)), protocol: config.Negotiated, truncated: Bool, )Arguments
- truncated
-
True only under
with_body_limit(_, _, Truncate)when the body exceeded its limit: the body holds the firstlimitbytes and the trailers are empty.
A shared client handle: the pool it sends through and the view settings of this handle. Copy it freely.
pub type Client =
@internal Client
pub type Failure =
error.Failure
The messages a supervised client’s process receives; name it with
process.new_name for supervised and named.
pub type Message =
@internal Message
What send does when a response body exceeds its limit.
pub type Overflow {
Fail
Truncate
}
Constructors
-
FailClose the body and fail with
LimitExceeded(ResponseBodyBytes, ..);error.statusanderror.headersreturn the response status and its headers, such asretry-after. -
TruncateClose the body and return the status, headers and the first
limitbytes, withBuffered.truncatedset and no trailers.
Why a client did not start.
pub type StartError {
InvalidConfig(config.ConfigError)
InvalidScript(position: Int)
ScriptTooLarge(limit: Int, observed: Int)
StartFailed
}
Constructors
-
InvalidConfig(config.ConfigError) -
InvalidScript(position: Int)The scripted exchange at this position has a status outside 200..599 or a body that is not whole bytes.
-
ScriptTooLarge(limit: Int, observed: Int)The script exceeds its 16 MiB budget.
-
StartFailedThe Gun application or the client process failed to start.
Values
pub fn batch(
client: Client,
requests: List(request.Request(BitArray)),
concurrency: Int,
) -> Result(List(Result(Buffered, error.Failure)), error.Failure)
Run at most concurrency requests at once, 1 to 1,024, over at most
10,000 requests. Results keep input order, and a failure occupies only its
own position. Bodies retained across results are bounded by
config.with_max_batch_bytes: once reached, the response being collected
fails and requests not yet sent fail with LimitExceeded(BatchBytes, ..)
and NotSent.
pub fn correlation(
client: Client,
) -> option.Option(correlation.Correlation)
The correlation this view tags its events with, set by
with_correlation, or None. A library that receives a caller’s view
reads the caller’s correlation here and copies it into its own
telemetry, instead of asking the caller to pass it twice:
let correlation = http_gun.correlation(client)
pub fn describe_start_error(problem: StartError) -> String
pub fn named(name: process.Name(Message)) -> Client
The handle of the client registered under name by supervised. Pure:
it does not check that the client is running.
pub fn open(
client: Client,
req: request.Request(BitArray),
) -> Result(response.Response(body.Body), error.Failure)
Open a response whose body the calling process reads. Close the body when
done, even after reading it to the end; prefer with_response when the
response fits inside one callback.
pub fn send(
client: Client,
req: request.Request(BitArray),
) -> Result(Buffered, error.Failure)
Send a request and collect its response, up to the body limit. An
oversized body fails with LimitExceeded(ResponseBodyBytes, ..), the
response status and its headers, unless the view chose Truncate. Every
failure after the head arrived keeps the status (error.status) and the
headers after the client’s redaction (error.headers). The body is closed
on every path.
pub fn start(
settings: config.Config,
) -> Result(Client, StartError)
Validate the configuration, then start a client with its own connection pool, linked to the caller.
pub fn stats(client: Client) -> Result(Stats, error.Failure)
Read the pool’s counters. Holds no request history or credentials.
pub fn stop(client: Client) -> Nil
Refuse new requests, fail queued ones with ClientClosed and NotSent,
let active HTTP exchanges finish within the shutdown timeout, then cancel
them and close every connection. Stopping ends access to retained response
bodies, including unread bytes after HTTP EOF. Callers needing old application
results must await and consume them before stopping the client.
Returns when the client has stopped; stopping a stopped client returns at
once. Meant for clients from start: a supervisor restarts a supervised
client that stops.
pub fn supervised(
settings: config.Config,
name: process.Name(Message),
) -> supervision.ChildSpecification(Client)
A child specification for a supervisor. The client registers under
name, so named(name) reaches it across restarts. An invalid
configuration fails the child’s start.
let name = process.new_name("http_client")
let assert Ok(_) =
static_supervisor.new(static_supervisor.OneForOne)
|> static_supervisor.add(http_gun.supervised(config.default(), name))
|> static_supervisor.start
let client = http_gun.named(name)
pub fn with_additional_cancellation(
client: Client,
token: cancellation.Token,
) -> Client
Add an independent cancellation token while retaining this view’s tokens. Cancellation by any retained token ends unfinished requests through this view. Adding the same token twice has no additional effect. This pure view operation starts no process, changes no other view and never cancels a token.
pub fn with_body_limit(
client: Client,
bytes: Int,
overflow: Overflow,
) -> Client
Collect at most bytes of each response body in send and batch
through this view, replacing config.with_max_response_body_bytes, and
choose what happens past the limit. A negative limit fails each request
with InvalidRequest(InvalidBodyLimit) before it is sent.
pub fn with_cancellation(
client: Client,
token: cancellation.Token,
) -> Client
Replace this view’s cancellation tokens with token. Unfinished requests
through the resulting view cancel when that token is cancelled. Other views
and tokens remain unchanged. Cancellation never enters cassette matching.
pub fn with_correlation(
client: Client,
correlation: correlation.Correlation,
) -> Client
Tag the lifecycle events of requests made through this view with the
caller’s correlation, under the correlation metadata key, so they join
the events of other packages working on the same unit. Every request
still gets its own request_id. A later call replaces the correlation.
pub fn with_deadline(
client: Client,
deadline: deadline.Deadline,
) -> Client
End every request made through this view by an absolute deadline shared
across calls. It replaces the client’s request timeout; combined with
with_timeout, the earlier of the two applies.
pub fn with_destination(
client: Client,
policy: destination.Policy,
) -> Client
Narrow the destinations of this view: a request must satisfy the client’s policy and every policy added this way, including the addresses of a pooled connection it would reuse. A view can never widen what the client admits; host name resolution stays the client’s.
A client configured with config.require_view_destination refuses
requests through a view unless one of its policies chooses a destination:
it sets destination.only_hosts, or it refuses an address class the
client admits. A policy that admits everything the client admits and only
tightens destination.with_plaintext chooses none, so a library that
tightens the scheme on a caller’s view never lifts the application’s
requirement:
// A library: tightens the scheme, chooses no destination.
let scheme =
destination.default()
|> destination.allow_loopback
|> destination.allow_private
|> destination.with_plaintext(destination.PlaintextToLoopbackOnly)
client |> http_gun.with_destination(scheme)
pub fn with_idle_timeout(
client: Client,
timeout: config.Timeout,
) -> Client
Replace the client’s idle timeout for this view: the longest gap without bytes while the response’s owner waits for its head or reads its body. Use it for a model that thinks before its first token, or a stream with sparse keepalives.
pub fn with_response(
client: Client,
req: request.Request(BitArray),
on_failure: fn(error.Failure) -> e,
run: fn(response.Response(body.Body)) -> Result(a, e),
) -> Result(a, e)
Open a response, run run with it and close the body when run returns
or raises. on_failure maps an opening failure into the callback’s error
type:
use response <- http_gun.with_response(client, req, AppHttpError)
read_events(response.body)
pub fn with_timeout(
client: Client,
timeout: config.Timeout,
) -> Client
Bound each request made through this view from admission to its last
byte, replacing the client’s request timeout, shorter or longer.
config.Infinity lifts the bound for a long-lived stream; the connect,
pool and idle timeouts still apply.