HTTP transport for Rust API SDKs. reqx provides consistent retry, timeout,
idempotency, proxy, TLS, streaming, and observability behavior across async and
blocking clients.
- Idempotency-aware retries with
Retry-After, retry budgets, and circuit breakers - Per-attempt, response-body, connect, and total timeouts
- Async and blocking clients with buffered and streaming I/O
- Rate limiting, concurrency limits, and multi-endpoint routing
- Rustls or native-tls, including custom CAs and mTLS
- Fixed DNS addresses preserving Host and TLS identity
- Proxies with CIDR exclusions, redirects, size limits, and structured errors
- Metrics, observers, interceptors, and optional OpenTelemetry integration
The default build uses the async client with rustls (ring) and enables gzip
response decoding:
cargo add reqxTo replace the default transport or TLS backend:
cargo add reqx --no-default-features --features async-tls-native,compression-gzip| Feature | Transport |
|---|---|
async-tls-rustls-ring |
Async, rustls with ring (default) |
async-tls-rustls-aws-lc-rs |
Async, rustls with AWS-LC |
async-tls-rustls-graviola |
Async, rustls with Graviola |
async-tls-rustls-no-provider |
Async, rustls with a caller-installed provider |
async-tls-native |
Async, platform-native TLS |
blocking-tls-rustls-ring |
Blocking (ureq), rustls with ring |
blocking-tls-rustls-aws-lc-rs |
Blocking (ureq), rustls with AWS-LC |
blocking-tls-rustls-graviola |
Blocking, rustls with Graviola |
blocking-tls-rustls-no-provider |
Blocking, rustls with a caller-installed provider |
blocking-tls-native |
Blocking (ureq), platform-native TLS |
TLS backend features are additive. When several are enabled, select one with
Client::builder(...).tls_backend(...); rustls with ring is preferred by
default. Optional capabilities include all-codec compression, individual
compression-* codecs, resumable-upload, and otel; enable otel before
calling .otel_enabled(true).
Enable Graviola for either or both transports:
[dependencies]
reqx = { version = "0.2", default-features = false, features = ["async-tls-rustls-graviola", "blocking-tls-rustls-graviola", "compression-gzip"] }The Graviola backends configure their own provider for each client. They do not require or modify the process-wide rustls provider:
fn main() -> reqx::Result<()> {
let _async_client = reqx::Client::builder("https://example.com")
.tls_backend(reqx::TlsBackend::RustlsGraviola)
.build()?;
let _blocking_client = reqx::blocking::Client::builder("https://example.com")
.tls_backend(reqx::TlsBackend::RustlsGraviola)
.build()?;
Ok(())
}When Graviola is the only TLS backend for a transport, the explicit
.tls_backend(...) call is optional. With multiple backends enabled, the default
priority is ring, AWS-LC, native TLS, Graviola, then the caller-installed provider.
Graviola is an optional dependency; the default build continues to use ring.
Graviola builds without a C toolchain. Its supported architectures are x86_64
and aarch64, with specific CPU instruction requirements; check the
upstream platform requirements
for your deployment. Upload checksums and retry jitter remain independent of the
TLS provider.
Both clients can use the application's process-wide rustls provider, for example
Graviola. Disable default features to avoid enabling ring through reqx:
[dependencies]
reqx = { version = "0.2", default-features = false, features = ["async-tls-rustls-no-provider", "blocking-tls-rustls-no-provider", "compression-gzip"] }
rustls-graviola = "0.4"Install the provider once, during application startup, before building clients:
fn main() -> reqx::Result<()> {
rustls_graviola::default_provider()
.install_default()
.expect("install the application's rustls provider");
let _async_client = reqx::Client::builder("https://example.com")
.tls_backend(reqx::TlsBackend::RustlsNoProvider)
.build()?;
let _blocking_client = reqx::blocking::Client::builder("https://example.com")
.tls_backend(reqx::TlsBackend::RustlsNoProvider)
.build()?;
Ok(())
}Enable only the transport features you need. Each no-provider backend is the
default when it is the only TLS backend enabled for that transport. If multiple
backends are enabled, explicitly select TlsBackend::RustlsNoProvider as above;
installing a provider does not change the priority of ring, AWS-LC, native TLS, or Graviola.
Cargo features are additive: another dependency can still enable ring or AWS-LC,
so inspect the final application's dependency graph when excluding them matters.
When using this backend, both builders return Error::TlsBackendInit if no
provider is installed.
Selecting this backend without its corresponding transport feature returns
Error::TlsBackendUnavailable. The provider is process-wide, not configurable
per client. The blocking backend retains its existing TLS-version-override
limitations.
use std::time::Duration;
use reqx::prelude::Client;
async fn fetch_item() -> reqx::Result<()> {
let client = Client::builder("https://api.example.com")
.client_name("example-sdk")
.request_timeout(Duration::from_secs(3))
.total_timeout(Duration::from_secs(8))
.build()?;
let response = client.get("/v1/items/42").send().await?;
println!("status={} bytes={}", response.status(), response.body().len());
Ok(())
}POST requests are retried only when they can be sent safely; provide an
idempotency key when the API supports one.
Async and blocking clients support resolve and resolve_to_addrs, preserving
Host and TLS identity without falling back to DNS for configured hostnames:
let client = Client::builder("https://example.com")
.resolve_to_addrs("example.com", &validated_addresses)
.build()?;Accepts 1–16 addresses per hostname; incompatible with proxies. Callers validate addresses and each redirect destination. See the API reference for details.
Run the async example with cargo run --example basic_json. For the blocking
client, run:
cargo run --example blocking_basic --no-default-features --features blocking-tls-rustls-ring