Skip to main content

scion_http3/
lib.rs

1// Copyright 2026 Anapaya Systems
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//   http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! # SCION HTTP/3 client
16//!
17//! A high-level HTTP/3-over-SCION client: give it a URL, get a response.
18//!
19//! ## Features
20//!
21//! - SCION connectivity, discovered through an endhost API
22//! - TSAR DNS resolution (SCION addresses from DNS TXT records)
23//! - Per-origin connection pooling
24//! - Staggered multi-candidate connection establishment ([RFC 8305], Happy Eyeballs)
25//! - Connect, request, and idle-connection timeouts
26//! - Recovery from network changes
27//!
28//! The transport engine underneath is [`scion_quic::h3::client::Http3Client`]. It stays
29//! available for callers that need transport-level control.
30//!
31//! ## Usage
32//!
33//! Issue a GET request with the [`Client::get`] shorthand (see also [`Client::post`]):
34//!
35//! ```no_run
36//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
37//! use scion_http3::{Client, Config};
38//!
39//! /// Cap on a collected response body.
40//! const MAX_BODY_SIZE: usize = 1 << 20;
41//!
42//! let client = Client::new(
43//!     Config::new("https://endhost-api.example.org".parse()?).with_auth_token("token"),
44//! );
45//! let response = client.get("https://chat.example.org/rooms").await?;
46//! let (rooms, _trailers) = response.text(Some(MAX_BODY_SIZE)).await?;
47//! # Ok(())
48//! # }
49//! ```
50//!
51//! Or build a [`Request`] from scratch and issue it with [`Client::request`]:
52//!
53//! ```no_run
54//! # async fn example(client: scion_http3::Client) -> Result<(), Box<dyn std::error::Error>> {
55//! use scion_http3::Request;
56//!
57//! let request = Request::post("https://chat.example.org/messages")
58//!     .header("content-type", "application/json")
59//!     .body(r#"{"room":"general","text":"hi"}"#)
60//!     .build()?;
61//! let response = client.request(request).await?;
62//! # Ok(())
63//! # }
64//! ```
65//!
66//! ## Tunnels
67//!
68//! [`Client::connect`] opens an HTTP `CONNECT` tunnel to an [`Authority`] (`host:port`). The
69//! returned [`Tunnel`] is a byte stream that implements [`tokio::io::AsyncRead`] and
70//! [`tokio::io::AsyncWrite`]:
71//!
72//! ```no_run
73//! # async fn example(client: scion_http3::Client) -> Result<(), Box<dyn std::error::Error>> {
74//! use scion_http3::Authority;
75//! use tokio::io::{AsyncReadExt, AsyncWriteExt};
76//!
77//! let authority: Authority = "chat.example.org:443".parse()?;
78//! let mut tunnel = client.connect(&authority).await?;
79//! tunnel.write_all(b"hello").await?;
80//! let mut reply = vec![0u8; 5];
81//! tunnel.read_exact(&mut reply).await?;
82//! tunnel.shutdown().await?;
83//! # Ok(())
84//! # }
85//! ```
86//!
87//! The authority's host is resolved like a URL's host. [`Config::with_dns_override`] maps a
88//! host that has no TSAR records to fixed addresses.
89//!
90//! ## Connection pooling and lifecycle
91//!
92//! A [`Client`] holds a connection pool, so create one client per application and share it.
93//!
94//! Construction does no I/O: connections are established lazily on first use and then reused
95//! per origin. Use [`Client::warm_up`] to establish a connection before the first request.
96//!
97//! [`Client::close`] closes all pooled connections and frees their resources. Calling it
98//! before dropping the client is optional.
99//!
100//! ## Network changes
101//!
102//! Call [`Client::reset`] when the network below the client changes, for example when the
103//! platform reports a switch from Wi-Fi to mobile data. The call never blocks: it marks the
104//! current connections stale, and the next request tears them down and rebuilds connectivity.
105//!
106//! ## Error handling
107//!
108//! Every request-path failure surfaces as one error type, [`Error`]. To decide whether to
109//! retry a failed request, use [`Error::is_retryable`] instead of matching variants.
110//! A retryable error does not imply the request never reached the server, so retry
111//! non-idempotent requests with care.
112//!
113//! [RFC 8305]: https://datatracker.ietf.org/doc/html/rfc8305
114
115// Linter baseline for the published API surface.
116#![warn(clippy::pedantic)]
117#![allow(
118    // The module path deliberately echoes type names where a module has one main type.
119    clippy::module_name_repetitions,
120    // `#[must_use]` is applied deliberately where it matters rather than everywhere.
121    clippy::must_use_candidate,
122    // `# Errors` / `# Panics` sections are added where they aid the caller, not mechanically.
123    clippy::missing_errors_doc,
124    clippy::missing_panics_doc
125)]
126
127mod authority;
128mod client;
129mod config;
130mod epoch;
131mod error;
132mod establish;
133mod origin;
134mod pool;
135mod request;
136mod response;
137#[cfg(test)]
138mod test_support;
139mod tunnel;
140
141// Re-exported dependencies
142//
143// These crates appear in `scion-http3`'s public API by deliberate choice. They are re-exported
144// here so a client can name and construct the types our signatures require without adding
145// its own direct dependency.
146/// Body bytes ([`bytes::Bytes`]) used for request bodies and collected response bodies.
147pub use authority::{Authority, InvalidAuthority};
148pub use bytes;
149pub use client::Client;
150pub use config::{
151    Config, DEFAULT_CONNECT_TIMEOUT, DEFAULT_CONNECTION_ATTEMPT_DELAY,
152    DEFAULT_IDLE_CONNECTION_TIMEOUT, DEFAULT_MAX_ORIGINS, DEFAULT_REQUEST_TIMEOUT,
153};
154pub use error::{BuildRequestError, Error, TimeoutPhase};
155/// HTTP vocabulary types ([`http::Method`], [`http::HeaderMap`], [`http::StatusCode`]) used by
156/// requests and responses.
157pub use http;
158pub use request::{IntoUrl, Request, RequestBuilder};
159pub use response::Response;
160/// The transport engine. Its types appear in this API as the QUIC configuration
161/// ([`scion_quic::quic::config::QuicConfig`], see [`Config::with_quic_config`]).
162pub use scion_quic;
163/// The SCION stack. Its types appear in this API as the builder passthroughs
164/// ([`Config::with_preferred_underlay`], [`Config::with_stack_customizer`]) and the resolver
165/// escape hatch ([`Config::with_resolver`]).
166pub use scion_stack;
167/// Authentication token plumbing for [`Config::with_auth_token_source`]: implement
168/// [`TokenSource`] to supply tokens that expire; [`StaticTokenSource`] wraps a fixed one.
169pub use scion_stack::reqwest_connect_rpc::token_source::{
170    TokenSource, TokenSourceError, TokenSourceWatch, static_token::StaticTokenSource,
171};
172/// SCION address types ([`sciparse::address::ip_addr::ScionIpAddr`]) used by
173/// [`Config::with_dns_override`].
174pub use sciparse;
175/// Async I/O traits ([`tokio::io::AsyncRead`], [`tokio::io::AsyncWrite`]) implemented by
176/// [`Tunnel`].
177pub use tokio;
178pub use tunnel::Tunnel;
179/// URL type used for request URLs and the endhost API address.
180pub use url;