trillium_client/lib.rs
1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![forbid(unsafe_code)]
3#![deny(
4 clippy::dbg_macro,
5 missing_copy_implementations,
6 rustdoc::missing_crate_level_docs,
7 missing_debug_implementations,
8 missing_docs,
9 nonstandard_style,
10 unused_qualifications
11)]
12
13//! trillium client is an HTTP client that uses the same `conn` approach as
14//! [`trillium`](https://trillium.rs) but which can be used
15//! independently for any HTTP client application.
16//!
17//! ## Connector
18//!
19//! [`trillium_client::Client`](Client) is built with a [`Connector`]. Each runtime crate
20//! ([`trillium_smol`](https://docs.trillium.rs/trillium_smol),
21//! [`trillium_tokio`](https://docs.trillium.rs/trillium_tokio),
22//! [`trillium_async_std`](https://docs.trillium.rs/trillium_async_std)) offers
23//! a Connector implementation, which can optionally be combined with a
24//! tls crate such as
25//! [`trillium_rustls`](https://docs.trillium.rs/trillium_rustls),
26//! [`trillium_native_tls`](https://docs.trillium.rs/trillium_native_tls), or
27//! [`trillium_openssl`](https://docs.trillium.rs/trillium_openssl).
28//!
29//! See the documentation for [`Client`] and [`Conn`] for further usage
30//! examples.
31//!
32//! ## Protocol selection
33//!
34//! By default, trillium-client auto-discovers the best HTTP version for each request:
35//!
36//! - Over `https://` with a TLS connector that advertises `h2` in ALPN *and* exposes the server's selection
37//! back to trillium (the default for [`trillium_rustls::RustlsConfig`](https://docs.trillium.rs/trillium_rustls/struct.RustlsConfig.html)
38//! and [`trillium_openssl::OpenSslConfig`](https://docs.trillium.rs/trillium_openssl/struct.OpenSslConfig.html)):
39//! the server picks h2 or h1.1 during the TLS handshake. Whatever ALPN selects is what the client
40//! uses.
41//! - Over `https://` with `h2` removed from the ALPN list (e.g. `RustlsConfig::without_http2()`):
42//! h1 only.
43//! - Over `https://` with a TLS connector that doesn't surface ALPN selection
44//! (`trillium_native_tls`): h1 only by default, since trillium can't tell whether the server
45//! picked h2. Use the `Version::Http2` hint described below to force h2 over TLS in that case.
46//! - Over `https://` when the [`Client`] was built with
47//! [`Client::new_with_quic`](Client::new_with_quic): the client may use h3 for origins that have
48//! advertised it via [`Alt-Svc`][altsvc] or that the user has hinted (see below).
49//! - Over `http://`: h1 only. There is no h2c probing without explicit prior knowledge.
50//!
51//! [altsvc]: https://datatracker.ietf.org/doc/html/rfc7838
52//!
53//! ### Prior-knowledge hints
54//!
55//! Setting [`Conn::http_version`](Conn::with_http_version) before sending the request
56//! signals **prior knowledge** of what the server speaks. The default value is
57//! [`Version::Http1_1`], which means "no hint — use auto-discovery."
58//!
59//! | hint | URL scheme | behavior | curl equivalent |
60//! |---|---|---|---|
61//! | `Version::Http3` | `https` | Skip the [`Alt-Svc`][altsvc] cache and dial QUIC directly. Falls back to h2 / h1 if QUIC connect fails. Requires [`Client::new_with_quic`](Client::new_with_quic). | `--http3` |
62//! | `Version::Http2` | `https` | TLS handshake (with whatever ALPN the connector advertises), then start the h2 driver immediately without checking the negotiated ALPN. **No fallback** — a non-h2-speaking server surfaces as an IO error. Useful with TLS connectors that don't surface ALPN selection. | (curl bundles this with `--http2-prior-knowledge`'s cleartext mode) |
63//! | `Version::Http2` | `http` | h2c immediate preface (cleartext h2 prior knowledge). **No fallback**. | `--http2-prior-knowledge` |
64//! | `Version::Http1_1` (default) | any | Auto-discovery as described above. | (default) |
65//! | `Version::Http1_0` | any | h1.0 wire format (no `Host`, no chunked encoding, etc.). | `--http1.0` |
66//!
67//! Hints are per-[`Conn`]; mix them freely on requests sharing one [`Client`].
68//!
69//! ### Forcing h1.1 (no h2 ALPN)
70//!
71//! There is no per-request knob equivalent to curl's `--http1.1`. Opting out of h2 ALPN
72//! advertisement is a TLS configuration concern, not a per-request concern: use
73//! [`RustlsConfig::without_http2()`](https://docs.trillium.rs/trillium_rustls/struct.RustlsConfig.html#method.without_http2)
74//! (or the equivalent on whichever TLS crate you're using) when constructing the
75//! [`Client`].
76//!
77//! ## WebSockets and WebTransport
78//!
79//! With the `websockets` cargo feature, `Conn::into_websocket` transforms a built conn into
80//! a `WebSocketConn` (RFC 6455 over h1, RFC 8441 extended CONNECT over h2). With the
81//! `webtransport` cargo feature, `Client::webtransport(url)` + `Conn::into_webtransport()`
82//! open a multiplexed WebTransport-over-h3 session (RFC 9220 +
83//! draft-ietf-webtrans-http3). Multiple WebTransport sessions to the same origin coalesce
84//! onto a single underlying QUIC connection — see the `webtransport` module for details.
85
86#[cfg(test)]
87#[doc = include_str!("../README.md")]
88mod readme {}
89mod client;
90mod client_handler;
91mod conn;
92mod conn_handler_ext;
93mod h3;
94mod into_url;
95mod pool;
96mod response_body;
97mod util;
98#[cfg(feature = "websockets")]
99pub mod websocket;
100#[cfg(feature = "webtransport")]
101pub mod webtransport;
102
103pub use client::Client;
104pub use client_handler::ClientHandler;
105#[cfg(any(feature = "serde_json", feature = "sonic-rs"))]
106pub use conn::ClientSerdeError;
107pub use conn::{Conn, USER_AGENT, UnexpectedStatusError};
108pub use conn_handler_ext::ConnExt;
109pub use into_url::IntoUrl;
110// open an issue if you have a reason for pool to be public
111pub(crate) use pool::Pool;
112pub use response_body::ResponseBody;
113pub use trillium_http::{
114 Body, BodySource, Error, HeaderName, HeaderValue, HeaderValues, Headers, KnownHeaderName,
115 Method, Result, Status, Version,
116};
117pub use trillium_server_common::{
118 ArcedConnector, ArcedQuicClientConfig, Connector, QuicClientConfig, Url, url,
119};
120#[cfg(feature = "websockets")]
121pub use trillium_websockets::{WebSocketConfig, WebSocketConn, async_tungstenite, tungstenite};
122#[cfg(feature = "websockets")]
123pub use websocket::WebSocketUpgradeError;
124
125#[cfg(all(feature = "serde_json", feature = "sonic-rs"))]
126compile_error!("cargo features \"serde_json\" and \"sonic-rs\" are mutually exclusive");
127
128#[cfg(feature = "serde_json")]
129#[cfg_attr(docsrs, doc(cfg(feature = "serde_json")))]
130pub use serde_json::{Value, json};
131#[cfg(feature = "sonic-rs")]
132#[cfg_attr(docsrs, doc(cfg(feature = "sonic-rs")))]
133pub use sonic_rs::{Value, json};
134
135/// constructs a new [`Client`] -- alias for [`Client::new`]
136pub fn client(connector: impl Connector) -> Client {
137 Client::new(connector)
138}