NURLNURL registrynurl-lang.org →

← http-client

http-client 0.2.0 API

http_client.nu

http-client — the unified HTTP client interface library for NURL.

One include gives a consumer:

hybrid key exchange, ML-DSA and classical certificate chains), the HTTP/1.1 client, the HTTP/2 client, the RFC 6265 cookie jar, gzip / deflate — and

object which speaks whatever the server offers.

Usage from a dependent package:

$ deps/http-client/src/http_client.nu

@ main → i { : *HttpClient c ( http_client_new ) ?? ( http_client_get c https://example.org/ ) { T r → { ( nurl_print_int . r status ) ( http_response_free r ) } F e → { ( nurl_eprintln ( http_client_err_name e ) ) } } ( http_client_free c ) ^ 0 }

The intent mirrors the http package on the server side: this is THE dependency anything needing an HTTP client reaches for.


client.nu

http-client/client.nu — the ergonomic HttpClient facade over the stdlib client stack.

The stdlib ships a complete client toolkit — pure-NURL TLS 1.3 (with X25519MLKEM768 hybrid key exchange and ML-DSA certificate chains), an HTTP/1.1 keep-alive client (http_pure.nu), a multiplexed HTTP/2 client (http2_client.nu), an RFC 6265 cookie jar (cookies.nu), and gzip / deflate (compress.nu). What every program re-invents is the glue that wires them into one thing: pick the protocol the server actually offers, pool the connection to reuse it, carry cookies, follow redirects, decode the body.

HttpClient collapses that into one object:

: *HttpClient c ( http_client_new ) ?? ( http_client_get c https://example.org/ ) { T r → { ( nurl_print_int . r status ) ( http_response_free r ) } F e → { ( nurl_eprintln ( http_client_err_name e ) ) } } ( http_client_free c )

Protocol selection is automatic and needs nothing configured: an https origin is dialled with ALPN "h2 http/1.1", and whichever the server picks is what the client speaks — HTTP/2 multiplexed over one connection, or HTTP/1.1 with keep-alive. A server that advertises HTTP/3 (Alt-Svc: h3=":443") gets the next request over QUIC (ext/http3_client.nu), and a QUIC attempt that fails falls back to TCP for that origin (http_client_set_h3: 0 follow Alt-Svc — the default, 1 try QUIC first, 2 never). The negotiated protocol (1, 2 or 3) and whether the key exchange was post-quantum are readable after each request (http_client_last_proto / http_client_last_pq).

Memory model: http_client_new returns a heap *HttpClient; free it with http_client_free, which closes every pooled connection. A returned HttpResponse is owned by the caller (http_response_free).

API

: | HttpClientErr

: | HttpClientErr {
    HcConnect  // TCP / TLS connect failed
    HcTimeout  // a read or write passed the deadline
    HcTls  // TLS handshake or certificate verification failed
    HcDns  // hostname did not resolve
    HcInvalidUrl  // malformed URL or a scheme other than http/https
    HcProtocol  // the peer broke framing (bad h2/h1 response)
    HcTooManyRedirects  // the redirect chain passed max_redirects
    HcTooLarge  // the response body passed the size cap
    HcDecode  // a Content-Encoding body would not decompress
    HcOther
}

── Errors ────────────────────────────────────────────────────────────

One error type for the whole client — the h1 transport, the h2 transport and TLS all fold into these.

@ _hc_eq_ci s a s b → b

Case-insensitive ASCII equality of a header name against a lowercase literal — the client's own copy so it needs nothing file-private from http_request.nu.

@ http_client_err_name HttpClientErr e → s

: HcOrigin

: HcOrigin {
    String key  // "scheme://host:port"
    String host
    i port
    i is_https
    i proto
    i has_h2 H2Client h2
    i has_h1 HttpConn h1
    i pq  // 1 = the TLS key exchange for this origin was post-quantum
    i has_h3 * H3Client h3
    i alt_h3_port  // the port an Alt-Svc named for h3 (0 none seen)
    i alt_h3_until  // ... valid until this wall-clock second
    i h3_failed  // 1 = a QUIC attempt failed here; TCP from now on
    i pq_tcp  // the TCP connection's post-quantum evidence, kept beside h3's
}

── Pooled origin ─────────────────────────────────────────────────────

One live connection per (scheme, host, port). proto records what the origin negotiated: 0 nothing yet, 1 HTTP/1.1, 2 HTTP/2, 3 HTTP/3. For h2 the pooled connection is the multiplexed H2Client; for h1 it is one idle keep-alive HttpConn, present only while has_h1 is 1; for h3 it is the QUIC connection in H3Client, beside whatever TCP connection the origin still holds.

: HttpClient

: HttpClient {
    CookieJar jar
    ( Vec i ) origins  // *HcOrigin boxed, so the structs have stable identity
    i verify  // verify TLS chains (1) or not (0)
    i follow  // follow redirects (1) or return the 3xx (0)
    i max_redirects
    i timeout_ms  // per read/write deadline; 0 = none
    i decompress  // request + decode gzip/deflate (1) or leave bodies raw (0)
    i body_max  // response body cap in bytes; 0 = unlimited
    String ua
    i h3_mode  // 0 follow Alt-Svc (default) · 1 try QUIC first · 2 never
    // Evidence from the most recent request.
    i last_proto  // 1 h1, 2 h2, 3 h3, 0 none yet
    i last_pq  // 1 = post-quantum key exchange
}

── Client ────────────────────────────────────────────────────────────

@ http_client_new → *HttpClient

@ http_client_set_verify * HttpClient c b on → v

Skip TLS certificate / hostname verification. For pinned, self-signed or test servers only — an unverified connection authenticates nothing.

@ http_client_set_follow * HttpClient c b on → v

Follow 3xx redirects (default) or hand the 3xx response back.

@ http_client_set_max_redirects * HttpClient c i n → v

@ http_client_set_timeout * HttpClient c i ms → v

Per read/write deadline in milliseconds (0 = none). A stalled server answers HcTimeout instead of hanging.

@ http_client_set_decompress * HttpClient c b on → v

Offer and decode gzip/deflate bodies (default) or leave the body as the wire carried it (the caller then owns Content-Encoding).

@ http_client_set_body_max * HttpClient c i n → v

Cap the (decoded) response body; a larger body answers HcTooLarge.

@ http_client_set_user_agent * HttpClient c s ua → v

@ http_client_set_h3 * HttpClient c i mode → v

The protocol / key-exchange facts of the most recent request. HTTP/3: 0 (default) use QUIC once an origin's response has advertised it with Alt-Svc: h3=...; 1 try QUIC first on every https origin (falling back to TCP when the attempt fails); 2 never.

@ http_client_last_proto * HttpClient c → i

@ http_client_last_pq * HttpClient c → b

@ http_client_jar * HttpClient c → CookieJar

Direct access to the cookie jar (seed a session cookie, inspect, …).

@ http_client_request * HttpClient c s method s url ( Vec Header ) headers ( Vec u ) body → !HttpResponse HttpClientErr

The one entry point: send method to url with body and the caller's headers, following redirects and carrying cookies. headers is consumed (freed); body is borrowed.

@ http_client_get * HttpClient c s url → !HttpResponse HttpClientErr

@ http_client_head * HttpClient c s url → !HttpResponse HttpClientErr

@ http_client_delete * HttpClient c s url → !HttpResponse HttpClientErr

@ http_client_post * HttpClient c s url ( Vec u ) body s content_type → !HttpResponse HttpClientErr

POST/PUT/PATCH with a body and a Content-Type. body is borrowed.

@ http_client_put * HttpClient c s url ( Vec u ) body s content_type → !HttpResponse HttpClientErr

@ http_client_patch * HttpClient c s url ( Vec u ) body s content_type → !HttpResponse HttpClientErr

@ http_client_post_str * HttpClient c s url s body s content_type → !HttpResponse HttpClientErr

Post a string body (text / JSON).

@ http_client_status HttpResponse r → i

@ http_client_body_str HttpResponse r → String

The response body as a borrowed String view (valid until free).

@ http_client_header HttpResponse r s name → String

@ http_client_free * HttpClient c → v