Peer-to-peer DHTTP/3 transport over QUIC, implemented in Rust.
- Peer-to-Peer: Extends HTTP3(RFC9114) to DHTTP/3, allowing both sides of the connection to initiate and handle HTTP3 requests (achieved by disabling server push).
- Asynchronous I/O: Built on the Rust asynchronous ecosystem, providing high-performance I/O processing capabilities.
- Zero-Copy: Achieves full-link zero-copy from the QUIC layer to the application layer.
- Multipath QUIC: Integrates the
dquicimplementation, featuring efficient transmission, robust authentication capabilities, and high extensibility. - Hyper / Tower Compatibility (feature
hyper, enabled by default): Provides a single-fileh3x::hyperfacade for hyper-facing integrations. The facade exposesTowerService,HyperService, request execution errors, upgrade/takeover helpers, Extended CONNECT helpers, and protocol extension helpers. Lower-level hyper adapters also remain available from their semantic owners:dhttp::message::hyper,endpoint::hyper,qpack::field::hyper, andextended_connect::hyper. - RPC / IPC (features
rpcandipc, experimental): Optionalremocintegration for remote trait calls (RTC) over QUIC connections, with optional IPC transport support. Consumers must select exactly oneremoc/default-codec-*feature; h3x tests use the bincode codec through a dev-dependency. - Extended CONNECT: Supports Extended CONNECT (RFC9220) for protocol tunneling over HTTP/3.
- Future Extensions: Plans to support extensions such as WebTransport over HTTP/3 (Draft).
⚠️ Currently, h3x is in the early stages of development, and the API may undergo significant changes.
h3x includes dquic as its built-in QUIC backend (feature dquic, enabled by default). Wrap a QuicEndpoint in an H3Endpoint to get HTTP/3 client and server semantics on top of QUIC.
use bytes::Bytes;
use h3x::{dquic::QuicEndpoint, endpoint::H3Endpoint};
use http_body_util::{BodyExt, Empty};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let endpoint = H3Endpoint::new(QuicEndpoint::new().await);
let connection = endpoint.connect("example.com:4433".parse()?).await?;
let request = http::Request::get("https://example.com:4433/hello")
.body(Empty::<Bytes>::new())?;
let response = connection.execute_hyper_request(request).await?;
assert_eq!(response.status(), http::StatusCode::OK);
let body = response.into_body().collect().await?.to_bytes();
println!("{}", String::from_utf8_lossy(&body));
Ok(())
}use std::sync::Arc;
use axum::{Router as AxumRouter, routing::get};
use h3x::{
dquic::{Identity, Name, QuicEndpoint},
endpoint::H3Endpoint,
hyper::TowerService,
};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let identity = Arc::new(Identity {
name: "localhost".parse()?,
certs: todo!("load your certificate chain"),
key: todo!("load your private key"),
ocsp: Arc::new(None),
});
let endpoint = H3Endpoint::new(
QuicEndpoint::builder()
.identity(identity)
.bind(Arc::new(vec!["127.0.0.1:4433".parse()?]))
.build().await,
);
let router = AxumRouter::new().route("/hello", get(|| async { "Hello, World!" }));
let service = TowerService(router.into_service());
endpoint.listen(service).await?;
Ok(())
}h3x provides adapters to bridge the Tower / hyper service ecosystem into DHTTP/3, available under the hyper feature (enabled by default).
TowerService(S)— wraps atower::Service(e.g. an axumRouter)HyperService(S)— wraps ahyper::service::Service
API note: h3x cannot construct hyper's internal types directly, so the
h3x::hypermodule provides its own alternatives:
h3x::hyper::upgrade— stream takeover for Extended CONNECT tunnels (instead ofhyper::upgrade)h3x::hyper::ext::Protocol— protocol indication in CONNECT requests (instead ofhyper::ext::Protocol)
use std::sync::Arc;
use axum::{Router as AxumRouter, routing::get};
use h3x::{
dquic::{Identity, Name, QuicEndpoint},
endpoint::H3Endpoint,
hyper::TowerService,
};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let router = AxumRouter::new()
.route("/hello", get(|| async { "Hello from DHTTP/3!" }));
let service = TowerService(router.into_service());
let identity = Arc::new(Identity {
name: "localhost".parse()?,
certs: todo!("load your certificate chain"),
key: todo!("load your private key"),
ocsp: Arc::new(None),
});
H3Endpoint::new(
QuicEndpoint::builder()
.identity(identity)
.bind(Arc::new(vec!["127.0.0.1:4433".parse()?]))
.build().await,
)
.listen(service)
.await?;
Ok(())
}use bytes::Bytes;
use h3x::{dquic::QuicEndpoint, endpoint::H3Endpoint};
use http_body_util::{BodyExt, Empty};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let endpoint = H3Endpoint::new(QuicEndpoint::new().await);
let connection = endpoint.connect("example.com:4433".parse()?).await?;
let request = http::Request::get("https://example.com:4433/hello")
.body(Empty::<Bytes>::new())?;
let response = connection.execute_hyper_request(request).await?;
assert_eq!(response.status(), http::StatusCode::OK);
let body = response.into_body().collect().await?.to_bytes();
println!("{}", String::from_utf8_lossy(&body));
Ok(())
}h3x uses one canonical test entrypoint for local development and CI:
cargo test --all-features --all-targetsAll feature-gated paths, including rpc, ipc, webtransport, dquic, and hyper, must compile and test through that command. The dev-dependency on remoc selects the bincode default codec for tests so rpc/ipc can compile under --all-features.
Documentation is checked with warnings denied for all non-rpc/ipc features:
RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --no-default-features --features dquic,hyper,serde,testing,webtransportCoverage uses cargo-llvm-cov directly:
cargo llvm-cov clean
mkdir -p target/llvm-cov
cargo llvm-cov \
--all-features \
--all-targets \
--summary-only \
--json \
--output-path target/llvm-cov/coverage.json \
--fail-under-lines 62 \
--fail-under-functions 56 \
--fail-under-regions 64
cargo llvm-cov report --lcov --output-path target/llvm-cov/lcov.info
cargo llvm-cov report --html --output-dir target/llvm-covThe HTML report entrypoint is:
target/llvm-cov/html/index.html
The initial coverage gate preserves the current baseline. Raise the gate only in commits that add meaningful tests or remove dead code.