TLS & HTTPS
aioduct supports HTTPS via rustls. No TLS library is included by default — plain HTTP works without any TLS dependency.
Enabling HTTPS
Use the rustls TLS backend with the ring crypto provider:
[dependencies]
aioduct = { version = "0.2.0-alpha.1", features = ["tokio", "rustls", "rustls-ring"] }
Use the same rustls backend with the AWS-LC crypto provider:
[dependencies]
aioduct = { version = "0.2.0-alpha.1", features = ["tokio", "rustls", "rustls-aws-lc-rs"] }
Add rustls-native-roots alongside either provider to use the OS certificate store:
[dependencies]
aioduct = { version = "0.2.0-alpha.1", features = ["tokio", "rustls-native-roots", "rustls-aws-lc-rs"] }
Quick Start
use aioduct::TokioClient;
#[tokio::main]
async fn main() -> Result<(), aioduct::Error> {
// with_rustls() configures WebPKI root certificates automatically
let client = TokioClient::with_rustls();
let resp = client
.get("https://httpbin.org/get")?
.send()
.await?;
println!("status: {}", resp.status());
Ok(())
}
How It Works
Handshake
The TLS handshake is fully async, implemented as a manual state machine:
RustlsConnectorwraps arustls::ClientConfig(with ALPN protocolsh2andhttp/1.1)- On connect, a
TlsStream<S>is created with the underlying TCP stream and arustls::ClientConnection - The handshake drives
read_tls/write_tlshelper functions that wrap the async stream as synchronousstd::io::Read/Write, usingWouldBlockfor flow control - Once complete, the negotiated ALPN protocol determines whether to use HTTP/1.1 or HTTP/2
ALPN Negotiation
After the TLS handshake, the negotiated protocol is inspected:
h2→ useshyper::client::conn::http2::handshakehttp/1.1(or no ALPN) → useshyper::client::conn::http1::handshake
This happens transparently — the client automatically selects the best protocol for each connection.
Root Certificates
TokioClient::with_rustls() uses webpki-roots, which bundles Mozilla’s root certificate store directly in the binary. No system certificate store access is needed.
Enable rustls-native-roots to build the connector from the operating system certificate store instead. This feature enables the rustls backend but does not select a crypto provider by itself; combine it with either rustls-ring or rustls-aws-lc-rs.
Crypto Providers
The rustls feature enables the rustls TLS backend, while rustls-ring and rustls-aws-lc-rs select the crypto provider. Enable exactly one provider whenever rustls is enabled; enabling neither or both is a compile error.
The backend/provider split keeps room for future TLS backends. A native-tls backend name is reserved for possible OpenSSL/native TLS support, but it is not implemented today.
Custom TLS Configuration
For advanced use cases, configure the RustlsConnector directly:
#![allow(unused)]
fn main() {
use aioduct::TokioClient;
use aioduct::tls::RustlsConnector;
let client = TokioClient::builder()
.tls(RustlsConnector::with_webpki_roots())
.build()?;
}
Accepting Invalid Certificates
For development and testing, you can disable certificate verification:
#![allow(unused)]
fn main() {
use aioduct::TokioClient;
let client = TokioClient::builder()
.danger_accept_invalid_certs()
.build()?;
}
Warning: Never use this in production. It disables all certificate verification, making the connection vulnerable to MITM attacks.
HTTPS-Only Mode
To enforce that all requests use HTTPS:
#![allow(unused)]
fn main() {
use aioduct::TokioClient;
use aioduct::tls::RustlsConnector;
let client = TokioClient::builder()
.tls(RustlsConnector::with_webpki_roots())
.https_only(true)
.build()?;
// This will return an error:
// client.get("http://example.com")?.send().await?;
}
Error Handling
TLS errors surface as Error::Tls(Box<dyn std::error::Error + Send + Sync>). Common failure modes:
- Certificate verification failure (expired, wrong hostname, untrusted CA)
- No TLS connector configured (HTTPS URL without the
rustlsbackend and a rustls provider, or without a.tls()builder call for a custom connector) - Handshake timeout (use
.timeout()on the request or client)