The one place the outbound adapters issue HTTP requests: the object stores
(Ankusa.BlobStore.S3, GCS, Azure, OCI), Ankusa.Sink.Http,
Ankusa.Sink.SQS, and Ankusa.Sink.GooglePubSub.
Those adapters share more than a client. Every request they send is a function
of its own bytes — a signed S3 URL, a signed SendMessage, a forwarded hook
body — which makes four options part of each adapter's contract rather than a
preference:
retry: false— retries belong to the framework's own loops (Ankusa.RetryPolicyfor the sinks,Ankusa.BlobStore.Retryaround one object-store request, re-signed per attempt). Req's default retry would add hidden latency inside those, and resend a signature as it ages.redirect: false— a followed redirect re-issues a POST as a GET, so a 302 from a sink endpoint would be reported as a successful delivery. A redirect on a signed request is never more useful: the signature covers the host and path it was made for.http_errors: :return— a non-2xx status is a value each adapter maps ({:error, :not_found}), not an exception.decode_body: false— these bodies are segments, claims and hook payloads, not JSON; the one JSON reply (SQS's) is checked against the bytes sent, so its adapter decodes it itself.
:req_options is an allowlist
Callers reach this module through each adapter's :req_options, and the
accepted keys are [:finch, :connect_options, :pool_timeout, :plug]:
transport tuning that cannot change the request. Anything else raises, because
the keys that can change it — :params, :base_url, :auth, :headers —
would alter a URL that has already been signed, or replace the headers carrying
a credential. Silently ignoring them would be a worse failure than refusing
them: the caller would believe a proxy or a pool was in use.
TLS is verified, always
An https request verifies the peer's certificate chain and hostname
(verify: :verify_peer) and negotiates TLS 1.2 or 1.3 only. The trust store is
the operating system's, with castore's bundle behind it where OTP cannot
load that; :cacertfile in opts names a PEM bundle instead (a private CA).
Turning verification off is not a :req_options setting: a transport_opts: [verify: :verify_none] raises ArgumentError from validate!/2, and the one
way past it is allow_insecure_tls: true in opts, which Ankusa.Sink.Http
exposes per sink for a receiver that cannot present a certificate.
A named pool owns its transport
finch: [name: MyFinch] sends the request through a pool the caller started,
and that pool's conn_opts are the connection options (timeout, TLS, proxy):
Req refuses :finch beside :connect_options, so the request carries none.
Asking for TLS settings per request on top of a named pool (:cacertfile,
allow_insecure_tls: true, :connect_options) raises ArgumentError from
validate!/3, rather than being ignored. Ankusa.BlobStore.Pool is such a
pool.
Every other request goes through an owned, bounded pool
A request with no :finch and no :plug in its options is sent through
Ankusa.HttpClient.Pools: a pool per origin and connection profile, sized and
idle-swept from the instance's egress.pools config, never Req's default of an
unbounded number of pools that are never closed. The connection options the
request would carry (connect timeout, TLS, the :connect_options in
req_options) become the pool's, so every connection it opens uses them.
opts takes the pool's context (taken out before the request is built):
:instance (whose egress.pools apply; none uses the defaults), :tenant
(whose ceiling the pool counts against), :source (what close_source/3
closes pools for) and :pool (a per-sink override, see
Ankusa.HttpClient.Pools.validate_override!/1). A pool ceiling that cannot be
made room for is {:error, {:unavailable, :pool_limit}}.
Summary
Functions
The host, or host:port when the port is not the scheme's default, of
url — a host header, a description's :host.
Issue one request, returning the status and raw body.
request/6, plus the response headers (%{lowercase_name => [value]}), for
callers that read one such as retry-after.
Raise ArgumentError unless req_options holds only transport keys
(:finch, :connect_options, :pool_timeout, :plug) and does not turn off
TLS verification (connect_options: [transport_opts: [verify: :verify_none]])
without allow_insecure_tls?, nor asks a named :finch pool (see "A named
pool owns its transport") for per-request connection options: :connect_options,
a cacertfile or allow_insecure_tls?: true.
Functions
The host, or host:port when the port is not the scheme's default, of
url — a host header, a description's :host.
@spec request( atom(), String.t(), [{String.t(), String.t()}], iodata() | nil, timeout(), keyword() ) :: {:ok, non_neg_integer(), term()} | {:error, term()}
Issue one request, returning the status and raw body.
timeout_ms is applied to both the connection and the response. A
:connect_options in req_options is merged over it, so supplying a proxy
never drops the timeout.
:max_response_bytes in opts (not a transport option; it is taken out
before the request is built) stops reading the response once its body passes
that many bytes: the body is then {:truncated, bytes_read} and the
connection is closed rather than drained. Without it the whole body is read,
which the blob stores need.
:cacertfile and :allow_insecure_tls in opts are likewise taken out
before the request is built; see "TLS is verified, always".
@spec request_with_headers( atom(), String.t(), [{String.t(), String.t()}], iodata() | nil, timeout(), keyword() ) :: {:ok, non_neg_integer(), %{required(String.t()) => [String.t()]}, term()} | {:error, term()}
request/6, plus the response headers (%{lowercase_name => [value]}), for
callers that read one such as retry-after.
Raise ArgumentError unless req_options holds only transport keys
(:finch, :connect_options, :pool_timeout, :plug) and does not turn off
TLS verification (connect_options: [transport_opts: [verify: :verify_none]])
without allow_insecure_tls?, nor asks a named :finch pool (see "A named
pool owns its transport") for per-request connection options: :connect_options,
a cacertfile or allow_insecure_tls?: true.
request/6 calls it on every request, and Ankusa.Sink.Http.validate_opts!/1
calls it when a config is validated, so a bad :req_options on an HTTP sink
fails at boot, not at its first delivery.