aioduct
Async-native Rust HTTP client built directly on hyper 1.x — no hyper-util, no legacy APIs.
Documentation | API Reference | Crates.io
Why aioduct?
- reqwest depends on hyper-util's
legacy::Client, wrapping hyper 0.x-style patterns over hyper 1.x with years of backwards-compatibility baggage. - hyper-util labels its own client as "legacy" — the hyper team acknowledges it's not the long-term answer.
- hyper 1.x provides clean connection-level primitives, but no production client uses them directly.
aioduct uses hyper 1.x the way it was intended — as a protocol engine you drive yourself, with your own connection pool, TLS, and runtime integration.
Features
- No hyper-util — custom IO adapters and executor directly against
hyper::rttraits - Multi-runtime — tokio, smol, and compio (io_uring) via feature flags; compatible WASM browser/worker and WASI Preview 2 support
- rustls TLS — async handshake with ALPN-based HTTP/1.1 and HTTP/2 negotiation
- Connection pooling — keyed by scheme, authority, protocol hint, proxy route, forced transport address, and effective HTTP/3 endpoint, with idle timeout and per-host limits plus
pool_stats()diagnostics (hit/miss/eviction counters and per-host idle/active inventory) - Redirect following — RFC-compliant handling of 301/302/303/307/308 with sensitive header stripping and content header removal
- Cookie jar — automatic cookie storage, domain/path/subdomain matching, Max-Age and Expires expiration, Secure flag enforcement, SameSite (Strict/Lax/None), cookie prefixes (__Host-, __Secure-)
- Timeouts — client-level and per-request total, connect, read, and write timeouts, plus per-request timeout bypass
- Retry — configurable exponential backoff with retry budgets, Retry-After header support, 429 Too Many Requests retry, and custom retry classification
- Decompression — automatic gzip, brotli, zstd, deflate response decompression
- Proxy — HTTP CONNECT tunneling, HTTPS proxy, SOCKS4/SOCKS4a, SOCKS5 (local DNS), SOCKS5h (remote DNS), proxy chaining up to 2 hops, URI-embedded credentials, credential resolver, system proxy detection (HTTP_PROXY/HTTPS_PROXY/NO_PROXY)
- Middleware — pluggable request/response interceptors via trait or closure
- Rate limiting — token-bucket rate limiter for outgoing requests
- Caching — in-memory HTTP cache with immutable responses, stale-while-revalidate, stale-if-error (fallback on 5xx/connection failure); pluggable
CacheStoretrait for custom backends - HSTS — automatic HTTP-to-HTTPS upgrade for Strict-Transport-Security domains
- SSE — Server-Sent Events stream parsing for LLM APIs
- Multipart —
multipart/form-datauploads with text fields and file parts - Streaming — chunked downloads and streaming uploads without buffering
- Chunk download — parallel HTTP Range requests for large files
- HTTP upgrade — WebSocket and other protocol upgrades via HTTP/1.1 101 and HTTP/2 extended CONNECT (RFC 8441)
- Request forwarding — proxy/gateway builder via
client.forward(req)that strips hop-by-hop headers, rewrites URIs, streams bodies, auto-detects WebSocket upgrades, supports H2 extended CONNECT tunneling, per-forward h2c for gRPC upstreams, and adaptive h2c/h1 fallback cached by effective route and endpoint - HTTP Message Signatures — RFC 9421 request/response signing and verification,
Accept-Signature,Content-Digest, trailer components, async signers, and forwarded message signing - Wasmtime host adapter — host-owned WASI HTTP forwarding via
aioduct::wasmtime, with Tokio, smol, and compio transports plus explicit header policy controls - Blocking client — synchronous wrapper for non-async contexts (
BlockingTokioClient,BlockingSmolClient,BlockingCompioClient) - Custom DNS — pluggable resolver via the
Resolvetrait; hickory-dns integration; DNS-over-HTTPS (dohfeature) and DNS-over-TLS (dotfeature) - HTTP/2 tuning — configurable window sizes, frame size, adaptive window, keepalive PINGs
- Per-request h2c —
RequestBuilderSendandRequestBuilderLocalexposeh2c_prior_knowledge(), while adaptive h2c probes and caches by effective route and endpoint so one client can mix h1 and h2c targets - Connection coalescing — reuses h2/h3 connections whose TLS certificate SANs cover the target domain (RFC 7540 §9.1.1), matching browser behavior
- TCP keepalive — configurable keepalive interval for long-lived connections
- TCP Fast Open — reduced connection latency on Linux via TCP_FASTOPEN_CONNECT
- Local address binding — bind outgoing connections to a specific local IP
- JSON — optional
jsonfeature for request/response serialization - Problem Details — RFC 9457
application/problem+jsonresponse parsing (requiresjsonfeature) - Happy Eyeballs — RFC 6555 connection racing, interleaves IPv6/IPv4 with 250ms stagger
- Digest auth — automatic HTTP Digest authentication with 401 retry (RFC 7616, MD5)
- Bandwidth limiter — token-bucket byte-rate throttle for download speed limiting
- Netrc —
.netrcfile parser and middleware for automatic credential injection - Auth helpers — bearer token, basic auth
- Form data — URL-encoded form bodies
- Query parameters — with percent-encoding
- Default headers — automatic User-Agent, configurable defaults
- Observability — optional tracing spans and OpenTelemetry middleware
- Tower integration — use aioduct as a tower
Service - Link headers — RFC 8288 Link header parsing for pagination and discovery
- Forwarded header — RFC 7239 Forwarded header builder and parser
- Request timings — per-phase timing via
RequestObserver(DNS, TCP, TLS, TTFB, total)
Quick Start
[]
= { = "0.2.5", = ["tokio"] }
use ;
async
HTTPS
Enable the rustls TLS backend plus exactly one rustls crypto provider:
= { = "0.2.5", = ["tokio", "rustls", "rustls-ring"] }
To use rustls with AWS-LC instead of ring, select the AWS-LC provider:
= { = "0.2.5", = ["tokio", "rustls", "rustls-aws-lc-rs"] }
To use the OS certificate store, add rustls-native-roots alongside either TLS provider:
= { = "0.2.5", = ["tokio", "rustls-native-roots", "rustls-aws-lc-rs"] }
use TokioClient;
let client = with_rustls;
let resp = client.get?.send.await?;
Feature Flags
| Feature | Description | Stability |
|---|---|---|
tokio |
Tokio async runtime | Stable |
smol |
Smol async runtime | Stable |
compio |
Compio runtime (io_uring / IOCP) | Experimental |
wasm |
Compatible browser/worker WASM via host Fetch API | Experimental |
wasi-p2 |
WASI Preview 2 guest HTTP client | Experimental |
wasmtime |
Host-side Wasmtime WASI HTTP adapter | Experimental |
rustls |
TLS via rustls; requires exactly one rustls provider | Stable |
rustls-ring |
ring crypto provider for rustls | Stable |
rustls-aws-lc-rs |
AWS-LC crypto provider for rustls | Stable |
rustls-native-roots |
Use OS certificate store with either rustls provider | Stable |
json |
JSON request/response with serde | Stable |
charset |
Charset decoding via encoding_rs | Stable |
gzip |
Gzip response decompression | Stable |
deflate |
Deflate response decompression | Stable |
brotli |
Brotli response decompression | Stable |
zstd |
Zstd response decompression | Stable |
blocking |
Synchronous wrapper for Tokio, smol, or compio clients | Stable |
hickory-dns |
DNS via hickory-resolver (requires tokio) | Stable |
doh |
DNS-over-HTTPS (implies hickory-dns) |
Stable |
dot |
DNS-over-TLS (implies hickory-dns) |
Stable |
tower |
Tower Service and Layer integration |
Stable |
tracing |
Tracing spans for requests | Stable |
otel |
OpenTelemetry middleware | Stable |
precise-timing |
Use std::time::Instant for sub-millisecond timing |
Stable |
http3 |
HTTP/3 via upstream h3 and quinn; requires Tokio, rustls, and one rustls provider |
Experimental |
At least one runtime feature must be enabled or compilation will fail. When rustls is enabled, choose exactly one of rustls-ring or rustls-aws-lc-rs. The native-tls backend name is reserved for possible future OpenSSL/native TLS support and is not implemented today.
HTTP/3 deliberately follows upstream h3 rather than carrying a protocol
fork. Deferred capabilities fail closed or use conservative replay behavior
until they can be implemented with complete validation and stream-lifecycle
guarantees; see the
HTTP/3 limitations.
Examples
Runnable examples are organized by runtime. The dispatch and proxy examples below exercise complete request paths rather than configuration alone.
| Scenario | Tokio | smol | compio |
|---|---|---|---|
| Forward a real incoming multipart upload | forward-multipart |
forward-multipart |
forward-multipart |
| Use one HTTP, HTTPS, or SOCKS proxy | proxy-connect |
proxy-connect |
proxy-connect |
| Route through a two-hop proxy chain | proxy-chain |
proxy-chain |
proxy-chain |
Select proxies by target scheme and NO_PROXY |
proxy-routing |
proxy-routing |
proxy-routing |
| Compare buffered and one-shot retry behavior | timeout-and-retry |
timeout-and-retry |
timeout-and-retry |
| Stream an HTTP/3 upload and fail closed | http3-streaming-upload |
Not supported | Not supported |
JSON
// Requires features = ["tokio", "json"]
let resp = client.post?
.json?
.send
.await?;
let user: User = resp.json.await?;
Form Data
let resp = client.post?
.form
.send
.await?;
Authentication
// Bearer token
let resp = client.get?
.bearer_auth
.send
.await?;
// Basic auth
let resp = client.get?
.basic_auth
.send
.await?;
Query Parameters
let resp = client.get?
.query
.send
.await?;
// GET /search?q=hello%20world&page=1
Client Configuration
use Duration;
use TokioClient;
let client = builder
.timeout
.max_redirects
.pool_idle_timeout
.pool_max_lifetime
.pool_max_idle_per_host
.pool_max_active_streams_per_connection
.tcp_keepalive
.local_address
.build?;
Proxy
use ;
// HTTP CONNECT proxy
let client = builder
.proxy
.build?;
// HTTPS proxy (TLS-wrapped CONNECT)
let client = builder
.proxy
.build?;
// SOCKS5 proxy (local DNS — client resolves hostnames)
let client = builder
.proxy
.build?;
// SOCKS5h proxy (remote DNS — proxy resolves hostnames)
let client = builder
.proxy
.build?;
// SOCKS4/SOCKS4a proxy
let client = builder
.proxy
.build?;
Proxy URLs can include credentials, which are automatically extracted:
// Credentials embedded in the URL are parsed automatically
let proxy = http.unwrap;
For credentials from environment variables, use EnvCredentialResolver:
use EnvCredentialResolver;
// Reads AIODUCT_PROXY_USER and AIODUCT_PROXY_PASS
let client = builder
.proxy_settings
.build?;
Proxy chaining routes requests through up to 2 proxies in sequence:
use ;
let chain = new;
let client = builder
.proxy_chain
.build?;
HTTP/2 Tuning
use Http2Config;
use TokioClient;
let client = builder
.tls
.http2
.build?;
Smol Runtime
use SmolClient;
block_on;
Request Forwarding (Reverse Proxy)
use ;
use Bytes;
use Full;
# async
CLI Tools
The workspace includes two CLI tools built on aioduct:
aioduct-aria
An aria2-inspired parallel download tool. Splits large files into segments and downloads them concurrently using HTTP Range requests.
# Download with 8 segments
# Resume an interrupted download
aioduct-curl
A curl-inspired HTTP tool with familiar flags.
# GET request
# POST with JSON body
# Follow redirects, basic auth, save to file
Both tools are workspace members (publish = false) and serve as real-world integration examples.
Architecture
HttpEngineSend<R: RuntimePoll, C: ConnectorSend> ← tokio, smol (Send futures)
HttpEngineLocal<R: RuntimeLocal, C: ConnectorLocal> ← compio (completion-based, !Send)
├── HttpEngineCore<B> ← shared config (pool, timeouts, middleware, etc.)
├── RequestBuilderSend / RequestBuilderLocal
│ ← fluent APIs (headers, body, auth, query, timeout)
├── ConnectionPool ← keyed by origin, protocol, route, address, H3 endpoint
├── TLS (rustls) ← async handshake, ALPN → h1/h2; Quinn for h3
├── ConnectorSend / ConnectorLocal ← pre-resolved socket connection and config
└── Runtime traits
├── RuntimeCompletion ← base: sleep and block_on
├── RuntimePoll ← Send spawn (tokio, smol)
└── RuntimeLocal ← !Send spawn (compio)
Type aliases:
TokioClient = HttpEngineSend<TokioRuntime, tokio_rt::TcpConnector>
SmolClient = HttpEngineSend<SmolRuntime, smol_rt::TcpConnector>
CompioClient = HttpEngineLocal<CompioRuntime, compio_rt::TcpConnector>
The runtime and connector responsibilities are split into separate traits.
Their core signatures are shown below; connector socket-adoption helpers and
the resolver's resolve_all default method are omitted for brevity.
/// Base runtime: timing and synchronous entry.
/// Send-capable runtime: spawn Send futures (Tokio and smol).
/// Local runtime: spawn futures that need not be Send (compio).
/// Send connector for a pre-resolved socket address.
/// Local connector for completion-based runtimes.
/// Pluggable DNS resolution.
Comparison
| reqwest | aioduct | |
|---|---|---|
| hyper | 1.x via hyper-util legacy | 1.x direct |
| hyper-util | Required | Not used |
| Runtime | tokio only | tokio / smol / compio / wasm / wasi |
| TLS | rustls or native-tls | rustls (native-tls reserved for future support) |
| HTTP/3 | Experimental | Experimental |
| io_uring | No | Via compio |
| Connection pool | hyper-util legacy | Custom h1/h2/h3 |
| Cookie jar | Yes | Yes |
| SSE streaming | No (manual) | Built-in |
| Rate limiting | No | Built-in |
| HTTP caching | No | Built-in |
| HSTS | No | Built-in |
| Link headers | No | Built-in |
| Problem Details | No | Built-in |
| Middleware | Via tower | Built-in + tower |
| Happy Eyeballs | No | RFC 6555 |
| Digest auth | No | Built-in |
| Bandwidth limiter | No | Built-in |
| Netrc | No | Built-in |
| Request timings | No | Observer |
| Connection coalescing | No | Built-in (RFC 7540) |
| DNS-over-HTTPS/TLS | No | Built-in |
| HTTP/3 0-RTT | No | Unsupported |
| Request forwarding | No | Built-in |
MSRV
The minimum supported Rust version is 1.95.0 (edition 2024).
License
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT License (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.