class

HTTP2::Client

Inherits Reference < Object

A reusable, origin-bound HTTP/2 client.

Constants

DEFAULT_CONNECTION_CONFIGURATION = Connection::Configuration.new(keepalive_interval: 30.seconds)

Default connection configuration for clients: keepalive on, so an active connection detects a silent peer without a socket-level read timeout tearing down idle or quiet-but-healthy streams. Used only as the constructor default for connection_configuration — a caller-supplied configuration is used exactly as given, never merged with or overridden by this default.

MIN_GRACEFUL_CLOSE_TIMEOUT = 1.millisecond

The minimum per-connection slice #graceful_close ever passes to Connection#graceful_close, even for a connection reached after the shared deadline (see #graceful_close's doc comment) has already elapsed. Connection#graceful_close rejects a non-positive timeout outright (ArgumentError), so SOME positive floor is required; this is deliberately tiny — a guard against that ArgumentError, not a fairness guarantee that a connection reached this late gets a meaningful chance to drain. A connection reached with essentially no budget left either has nothing active to wait for (drains immediately) or does, and immediately raises Connection::DrainTimeoutError — both fast, bounded outcomes, rather than an exception from #graceful_close itself.

Constructors

new(origin : String | URI, *, timeouts : Timeouts = Timeouts.new, connection_configuration : Connection::Configuration = DEFAULT_CONNECTION_CONFIGURATION, tls_context : OpenSSL::SSL::Context::Client = Connection.default_tls_context, replay_policy : ReplayPolicy = ReplayPolicy::Never, max_replay_attempts : Int32 = 1, additional_never_indexed_fields : Enumerable(String) = [] of String, connection : Connection | Nil = nil, pool_configuration : PoolConfiguration | Nil = nil)

Creates a client bound to one http or https origin. Owned connections are dialed lazily and scale under pool_configuration. A supplied connection is used as-is, is owned by this client, and remains fixed: it is not reconnected or evicted for idleness. Supplying both connection and pool_configuration is invalid.

tls_context, whether supplied or defaulted, is used for every https dial this client makes (#dial's single Connection.connect_tls call reuses the SAME @tls_context for the client's whole lifetime) and is configured for ALPN "h2" in place, unconditionally, on every dial — see Connection.start_tls's doc comment for the full contract, including why this is self-healing against anything else that changes it between dials. The symmetric caveat: do not share one tls_context with a different consumer (another Client configured differently, or anything outside this library) that needs a different, stable ALPN protocol on it — every dial through this Client overwrites alpn_protocol back to "h2".

Source

Instance methods

additional_never_indexed_fields

Extra field names, beyond the library's own built-in four (authorization, proxy-authorization, cookie, set-cookie), that must never be promoted to HPACK's compressed, indexed representation or inserted into this client's connections' dynamic tables (see Task 9's HPACK incremental-indexing change). A caller with its own credential or secret header — x-api-key, x-csrf-token, a bearer token carried under a non-standard name — adds it here so it keeps the same literal-never-indexed treatment RFC 7541 §7.1.3 reserves for that wire marker precisely so intermediaries cannot promote it. Comparison is case-insensitive: names are downcased once, here, when this client is constructed. Empty by default: this is purely additive and cannot narrow the built-in four, which stay unconditional.

Treat the returned Set as fixed once this client starts sending requests. #request reads it directly on every call (no defensive copy, so a request-heavy connection is not paying a repeated allocation for a value that rarely changes) — mutating it concurrently with an in-flight request is a data race under -Dpreview_mt, and any entry added after construction must already be lowercase, since nothing downcases it a second time. Any mutation must happen before this client's first request.

Source
close

Closes every reusable origin connection and cancels unfinished requests.

Source
closed?

True once #close or #graceful_close has been called — both set the closed flag immediately when invoked, even while a #graceful_close drain is still in progress, not only once teardown finishes. A request made after this returns true fails immediately with ClosedError, without dialing or touching the network.

Source
connection_configuration
Source
delete(target : String, headers : Headers = Headers.new, *, cancellation : Cancellation | Nil = nil) : Response

Sends a DELETE request and waits for its final response fields.

Source
get(target : String, headers : Headers = Headers.new, *, cancellation : Cancellation | Nil = nil) : Response

Sends a GET request and waits for its final response fields.

Source
graceful_close(timeout : Time::Span = @connection_configuration.drain_timeout) : Nil

Gracefully drains all connections currently owned by this client.

timeout is a SHARED deadline across every connection, not a per-connection budget: it is applied once, up front, and each connection receives only whatever of it remains (floored at MIN_GRACEFUL_CLOSE_TIMEOUT), so N connections that each need a full drain still take roughly timeout in total rather than N times timeout — a client with several retired connections behind the current one closes in bounded time instead of one that grows with however many connections happen to be pending.

Source
head(target : String, headers : Headers = Headers.new, *, cancellation : Cancellation | Nil = nil) : Response

Sends a HEAD request and waits for its final response fields.

Source
max_replay_attempts
Source
options(target : String, headers : Headers = Headers.new, *, cancellation : Cancellation | Nil = nil) : Response

Sends a OPTIONS request and waits for its final response fields.

Source
patch(target : String, headers : Headers = Headers.new, body : Request::Body = nil, *, trailers : Headers = Headers.new, cancellation : Cancellation | Nil = nil) : Response

Sends a PATCH request. IO bodies stream from their current position. An IO body paired with an explicit content-length header must EOF exactly at that declared length — see Request#initialize for what happens, and the risk, if it does not.

Source
pool_configuration
Source
pool_state

Returns a value-only snapshot of the current pool.

Source
post(target : String, headers : Headers = Headers.new, body : Request::Body = nil, *, trailers : Headers = Headers.new, cancellation : Cancellation | Nil = nil) : Response

Sends a POST request. IO bodies stream from their current position. An IO body paired with an explicit content-length header must EOF exactly at that declared length — see Request#initialize for what happens, and the risk, if it does not.

Source
put(target : String, headers : Headers = Headers.new, body : Request::Body = nil, *, trailers : Headers = Headers.new, cancellation : Cancellation | Nil = nil) : Response

Sends a PUT request. IO bodies stream from their current position. An IO body paired with an explicit content-length header must EOF exactly at that declared length — see Request#initialize for what happens, and the risk, if it does not.

Source
replay_policy
Source
request(method : String, target : String, headers : Headers = Headers.new, body : Request::Body = nil, *, trailers : Headers = Headers.new, cancellation : Cancellation | Nil = nil) : Response

Builds and sends a request for any HTTP method. See Request#initialize for the EOF requirement on a sized IO body.

Source
request(request : Request, *, cancellation : Cancellation | Nil = nil) : Response

Sends a prepared request. The returned response body remains streaming.

Source
timeouts
Source

Nested types