ioxidedocs

HTTP client Reference

API reference

Every public type of ioxide.httpclient, in namespace ioxide.httpclient. All of them belong to one reactor and are used from its thread.

HttpClientPool#

Keep-alive HTTP/1.1 connections to one origin, on one reactor's ring. sealed class HttpClientPool : IDisposable

MemberWhat it does
static HttpClientPool Start(Reactor reactor, HttpClientOptions options)Opens PoolSize connections, registers the pool as a reactor service - reactor.GetService<HttpClientPool>() - and its sweep as a reactor ticker. Call it from Reactor.OnStart. Services are keyed by type, so a second pool on the same reactor replaces the first there: with several backends, keep the pools it returns (several backends).
ValueTask<HttpClientResponse> GetAsync(ReadOnlyMemory<byte> path)A GET for path, which starts with /. The byte form costs no encoding: build the path once.
ValueTask<HttpClientResponse> GetAsync(string path)The same, encoding the path on each call.
ValueTask<HttpClientResponse> PostAsync(ReadOnlyMemory<byte> path, ReadOnlyMemory<byte> body)A POST with body; content-length is written for you.
ValueTask<HttpClientResponse> SendAsync(HttpClientRequest request)Any request. Nothing of request is kept once it returns, so one instance can be sent again and again.
int ConnectionCountConnections open or opening.
int IdleCountConnections idle in the pool right now.
void Dispose()Closes the idle connections and stops opening new ones. A request in flight finishes, and its connection is closed when it does; requests waiting for a connection fail.

RingHttpClient#

HTTP/1.1 to any origin by URL, with a pool per origin. sealed class RingHttpClient : IDisposable

MemberWhat it does
static RingHttpClient Start(Reactor reactor, RingHttpClientOptions? options = null)Creates the client and registers it as a reactor service - reactor.GetService<RingHttpClient>() - and its sweep as a ticker. Opens nothing until a request asks.
ValueTask<HttpClientResponse> GetAsync(string url)A GET for an absolute http or https URL, following redirects.
ValueTask<HttpClientResponse> PostAsync(string url, ReadOnlyMemory<byte> body)A POST with body, following redirects by .NET's rules.
ValueTask<HttpClientResponse> SendAsync(string url, HttpClientRequest request)Any request: its method, headers and body go to the URL, whose path and query replace the request's own path.
int OriginCountOrigins with a pool right now.
void Dispose()Disposes every origin's pool and the TLS context it made.

HttpClientOptions#

One origin, and how to keep connections to it. sealed record HttpClientOptions, set with init.

PropertyDefaultMeaning
string Host"127.0.0.1"A name, an IPv4 address or an IPv6 address. Sent as the request's host; over https, the name the certificate is checked against unless TlsClientOptions.ServerName is set.
ushort Port80The origin's port.
int PoolSize4Connections to keep, opened and opening together - the most requests in flight to the origin from one reactor.
TlsClientContext? TlsnullThe TLS context for an https origin; null is cleartext. Shared by the pool's connections; the caller disposes it.
int MaxResponseBytes8 MiBThe ceiling for one response's head and body. A larger one fails its request.
int SendBufferSize16 KiBEach connection's send buffer. A body larger than it is sent from your memory directly.
int ReceiveBufferSize16 KiBEach connection's receive buffer, grown up to MaxResponseBytes when a response needs it.
int AcquireTimeoutMs10 000How long a request may wait for a connection.
int ConnectTimeoutMs10 000How long one address may take to accept a connection before the next is tried. Enforced by the kernel; about a second at least.
int IdleTimeoutMs135 000An idle connection unused this long is closed, and in a warm pool replaced. 0: as long as the origin keeps it.
int MaxLifetimeMs0A connection older than this is closed after its request, or from the sweep while idle. 0: no limit.
int SweepIntervalMs250How often the sweep looks at idle connections and opens replacements, in whole reactor ticks of 250 ms.

RingHttpClientOptions#

sealed record RingHttpClientOptions

PropertyDefaultMeaning
HttpClientOptions Poolnew() { PoolSize = 64 }What every origin's pool is made from. Host, Port and Tls come from the URL; the rest applies as given.
TlsClientOptions? TlsnullTLS for every https origin, each connection checked against its URL's host; ServerName is ignored. Null: the system trust store, offering http/1.1.
int MaxRedirects50Redirects followed before the last is returned as the response; 0 follows none.

TlsClientOptions and TlsClientContext#

sealed record TlsClientOptions - see TLS for what each does.

PropertyDefault
string? ServerNamenull: the name the connection is opened to
string[] AlpnProtocolsnone offered
bool VerifyCertificatetrue
string? CaFilenull: the system trust store
string? CertificateFile, string? PrivateKeyFilenull: no client certificate
int MinimumVersionOpenSslVersions.Tls12
int HandshakeTimeoutMs10 000

sealed class TlsClientContext : IDisposable - the configured BoringSSL client context.

MemberWhat it does
static TlsClientContext Create(TlsClientOptions options)Builds the context: trust anchors, client certificate, protocol floor, ALPN. Throws IOException if BoringSSL refuses any of it, ArgumentException for a blank ServerName, a lone certificate or key, or an ALPN name that is not 1 to 255 ASCII bytes.
ValueTask<TlsClientStream> ConnectAsync(RingSocket socket)A TLS session over a connected ring socket, verified as ServerName, which must then be set. The stream owns the socket.
ValueTask<TlsClientStream> ConnectAsync(RingSocket socket, string serverName)The same, verified as serverName.
void Dispose()Frees the context. Connections opened from it keep working.

HttpClientRequest and HttpMethods#

sealed class HttpClientRequest - one request, as bytes. Reusable: nothing of it is kept once a send returns.

MemberMeaning
ReadOnlyMemory<byte> MethodThe method token; HttpMethods.Get by default.
ReadOnlyMemory<byte> PathThe request target, starting with /, e.g. "/api/things?id=7"u8. Replaced by the URL's with RingHttpClient.
KeyValueList HeadersExtra header lines: lowercase names, raw values. Not host, content-length or connection, which the client writes.
ReadOnlyMemory<byte> BodyThe body; content-length is written whenever it is not empty.
HttpClientRequest(), HttpClientRequest(ReadOnlyMemory<byte> method, ReadOnlyMemory<byte> path), HttpClientRequest(ReadOnlyMemory<byte> method, string path)Constructors; the string form encodes the path once.

static class HttpMethods - the method tokens as bytes: Get, Head, Post, Put, Patch, Delete.

HttpClientResponse#

sealed class HttpClientResponse : IDisposable - one response, as raw bytes in one pooled buffer it owns.

MemberMeaning
int StatusThe status code, e.g. 200.
KeyValueList HeadersThe header lines; names lowercased, values as sent.
bool TryGetHeader(ReadOnlySpan<byte> name, out ReadOnlyMemory<byte> value)The first value for a lowercase name: response.TryGetHeader("content-type"u8, out var type).
ReadOnlyMemory<byte> BodyThe body, de-chunked.
bool ConnectionCloseWhether this response ended its connection: the origin asked to close, or the body ran until the close.
void Dispose()Returns the buffer. Every memory above points at recycled storage afterwards.

KeyValueList#

sealed class KeyValueList - header lines as byte pairs, without a dictionary.

MemberMeaning
void Add(ReadOnlyMemory<byte> name, ReadOnlyMemory<byte> value)Appends a line.
int Count, this[int index]The lines, as KeyValuePair<ReadOnlyMemory<byte>, ReadOnlyMemory<byte>>.
ReadOnlySpan<KeyValuePair<ReadOnlyMemory<byte>, ReadOnlyMemory<byte>>> AsSpan()All lines, to iterate without copying.
void Clear()Removes every line.

HttpClientException#

sealed class HttpClientException : Exception - any failure to connect, send, receive or parse. The message says which, and for a response the parser refused, the inner exception names the rule broken. The messages are listed under Errors. A URL RingHttpClient cannot use throws ArgumentException instead, and so does a request whose method, target or headers hold a CR, LF or NUL.

Lower down: RingSocket#

The client's connections are core's RingSocket: a TCP socket whose connect, sends and receives run on a reactor's ring, which you can use directly - with TlsClientContext.ConnectAsync for TLS - to build a client of your own.

MemberWhat it does
static RingSocket CreateTcp(IRingHost host, AddressFamily family = AddressFamily.InterNetwork)A TCP socket with TCP_NODELAY, IPv4 or IPv6. A Reactor is an IRingHost.
ValueTask<int> ConnectAsync(IPAddress address, ushort port)Connects on the ring to an address of the socket's family; 0 or a negative errno.
ValueTask<int> ConnectAsync(string ipv4, ushort port)The same from an IPv4 literal.
ValueTask<int> SendAsync(nint buffer, int length), RecvAsync(nint buffer, int length)One send and one receive may be in flight at once; bytes, 0 at end of stream, or a negative errno.
int Fd, void Dispose()The descriptor; closing it, with nothing in flight.