From 6c8970a85db00194589325453c3b7502aa0b5fa6 Mon Sep 17 00:00:00 2001 From: Erick Guan <297343+erickguan@users.noreply.github.com> Date: Thu, 18 Jun 2026 00:03:32 +0800 Subject: [PATCH 01/22] feat(http-transport-reqwest): allow tls options --- bindings/dotnet/Cargo.toml | 2 +- bindings/java/Cargo.toml | 2 +- core/Cargo.lock | 22 ++ core/Cargo.toml | 32 ++- core/http-transports/reqwest/Cargo.toml | 23 +- core/http-transports/reqwest/README.md | 132 +++++++++ core/http-transports/reqwest/src/lib.rs | 344 +++++++++++++++++++++++- core/services/hf/Cargo.toml | 4 +- core/src/lib.rs | 5 + 9 files changed, 541 insertions(+), 25 deletions(-) create mode 100644 core/http-transports/reqwest/README.md diff --git a/bindings/dotnet/Cargo.toml b/bindings/dotnet/Cargo.toml index 9e7aa575d5cd..9a46c5f08b92 100644 --- a/bindings/dotnet/Cargo.toml +++ b/bindings/dotnet/Cargo.toml @@ -34,7 +34,7 @@ doc = false # this crate won't be published, we always use the local version opendal = { version = ">=0", path = "../../core", features = [ "blocking", - "reqwest-rustls-tls", + "http-transport-reqwest-rustls", "executors-tokio", # enabled layers diff --git a/bindings/java/Cargo.toml b/bindings/java/Cargo.toml index e88f5a348b71..b5b8070afcb9 100644 --- a/bindings/java/Cargo.toml +++ b/bindings/java/Cargo.toml @@ -35,7 +35,7 @@ jni = { version = "0.22.4" } # opendal-java won't be published to crates.io, we always use the local version opendal = { version = ">=0", path = "../../core", default-features = false, features = [ "blocking", - "reqwest-rustls-tls", + "http-transport-reqwest-rustls", "executors-tokio", # enabled layers diff --git a/core/Cargo.lock b/core/Cargo.lock index b5b4ed5b6551..54e96140bfae 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -4654,6 +4654,22 @@ dependencies = [ "tower-service", ] +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper 1.9.0", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.20" @@ -6508,6 +6524,9 @@ dependencies = [ "http-body 1.0.1", "opendal-core", "reqwest", + "rustls 0.23.40", + "rustls-platform-verifier", + "webpki-roots 1.0.7", ] [[package]] @@ -9390,11 +9409,13 @@ dependencies = [ "http-body-util", "hyper 1.9.0", "hyper-rustls 0.27.9", + "hyper-tls", "hyper-util", "js-sys", "log", "mime", "mime_guess", + "native-tls", "percent-encoding", "pin-project-lite", "quinn", @@ -9405,6 +9426,7 @@ dependencies = [ "serde_json", "sync_wrapper 1.0.2", "tokio", + "tokio-native-tls", "tokio-rustls 0.26.4", "tokio-util", "tower 0.5.3", diff --git a/core/Cargo.toml b/core/Cargo.toml index abd30f49de1f..4029e314b875 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -86,8 +86,7 @@ auto-register-services = ["dep:ctor"] blocking = ["opendal-core/blocking"] default = [ "auto-register-services", - "http-transport-reqwest", - "reqwest-rustls-tls", + "http-transport-reqwest-native-tls", "executors-tokio", "layers-concurrent-limit", "layers-logging", @@ -95,7 +94,28 @@ default = [ "layers-timeout", ] executors-tokio = ["opendal-core/executors-tokio"] +# Enable the reqwest HTTP transport without selecting a TLS backend. http-transport-reqwest = ["dep:opendal-http-transport-reqwest"] +# Use reqwest with platform TLS via native-tls. +http-transport-reqwest-native-tls = [ + "http-transport-reqwest", + "opendal-http-transport-reqwest/native-tls", +] +# Use reqwest with Rustls, the aws-lc-rs crypto provider, and platform certificate verification. +http-transport-reqwest-rustls = [ + "http-transport-reqwest", + "opendal-http-transport-reqwest/rustls", +] +# Use reqwest with Rustls and no built-in crypto provider. +http-transport-reqwest-rustls-no-provider = [ + "http-transport-reqwest", + "opendal-http-transport-reqwest/rustls-no-provider", +] +# Use reqwest with Rustls, the aws-lc-rs crypto provider, and bundled Mozilla root certificates. +http-transport-reqwest-webpki-roots = [ + "http-transport-reqwest", + "opendal-http-transport-reqwest/webpki-roots", +] internal-tokio-rt = ["opendal-core/internal-tokio-rt"] layers-async-backtrace = ["dep:opendal-layer-async-backtrace"] layers-await-tree = ["dep:opendal-layer-await-tree"] @@ -121,14 +141,6 @@ layers-tail-cut = ["dep:opendal-layer-tail-cut"] layers-throttle = ["dep:opendal-layer-throttle"] layers-timeout = ["dep:opendal-layer-timeout"] layers-tracing = ["dep:opendal-layer-tracing"] -reqwest-rustls-no-provider-tls = [ - "http-transport-reqwest", - "opendal-http-transport-reqwest/rustls-no-provider", -] -reqwest-rustls-tls = [ - "http-transport-reqwest", - "opendal-http-transport-reqwest/rustls", -] services-aliyun-drive = ["dep:opendal-service-aliyun-drive"] services-alluxio = ["dep:opendal-service-alluxio"] services-azblob = ["dep:opendal-service-azblob"] diff --git a/core/http-transports/reqwest/Cargo.toml b/core/http-transports/reqwest/Cargo.toml index 1b72144e2f24..4db5abe5f001 100644 --- a/core/http-transports/reqwest/Cargo.toml +++ b/core/http-transports/reqwest/Cargo.toml @@ -31,9 +31,25 @@ version = { workspace = true } all-features = true [features] -default = [] -rustls = ["reqwest/rustls"] +default = ["native-tls"] +# Use platform TLS via reqwest's native-tls feature. +native-tls = ["reqwest/native-tls"] +# Use Rustls with the aws-lc-rs crypto provider and platform certificate verification. +rustls = [ + "reqwest/rustls-no-provider", + "dep:rustls", + "rustls/aws-lc-rs", + "dep:rustls-platform-verifier", +] +# Use Rustls without a built-in crypto provider. rustls-no-provider = ["reqwest/rustls-no-provider"] +# Use Rustls with the aws-lc-rs crypto provider and bundled Mozilla root certificates. +webpki-roots = [ + "reqwest/rustls-no-provider", + "dep:rustls", + "rustls/aws-lc-rs", + "dep:webpki-roots", +] [dependencies] bytes = { workspace = true } @@ -44,3 +60,6 @@ opendal-core = { path = "../../core", version = "0.57.0", default-features = fal reqwest = { version = "0.13.4", features = [ "stream", ], default-features = false } +rustls = { version = "0.23", optional = true, default-features = false } +rustls-platform-verifier = { version = "0.7", optional = true, default-features = false } +webpki-roots = { version = "1", optional = true } diff --git a/core/http-transports/reqwest/README.md b/core/http-transports/reqwest/README.md new file mode 100644 index 000000000000..7498b3c46661 --- /dev/null +++ b/core/http-transports/reqwest/README.md @@ -0,0 +1,132 @@ +# opendal-http-transport-reqwest + +Reqwest-based HTTP transport for [Apache OpenDAL](https://opendal.apache.org). + +This crate provides `ReqwestTransport`, an implementation of OpenDAL's +`HttpTransport` trait backed by [reqwest](https://crates.io/crates/reqwest). + +## TLS configuration + +When using Rustls, TLS configuration has two independent axes: + +| Axis | What it decides | Options | +|------|----------------|---------| +| **Crypto provider** | Who performs the cryptographic operations (key exchange, symmetric ciphers, hashing) | `aws-lc-rs` (default in `rustls`/`webpki-roots`), `ring`, or any custom `CryptoProvider` | +| **Certificate verification** | How the server's TLS certificate chain is validated | Platform verifier (default in `rustls`), bundled Mozilla roots (`webpki-roots`), or custom | + +The `native-tls` feature sidesteps both axes by delegating everything to +the OS TLS library (SChannel / Secure Transport / OpenSSL). + +### Feature matrix + +| Feature | Crypto provider | Certificate roots | Use when | +|---------|----------------|-------------------|----------| +| `native-tls` (default) | OS library | OS trust store | You want zero Rust-side TLS config | +| `rustls` | aws-lc-rs | Platform verifier | Pure-Rust TLS with OS trust store | +| `webpki-roots` | aws-lc-rs | Bundled Mozilla roots | Fully self-contained, no OS dependency | +| `rustls-no-provider` | **you provide** | **you provide** | BYO crypto (ring, FIPS module, etc.) | + +### Usage via the `opendal` facade crate + +Most users depend on `opendal` rather than this crate directly. The facade +installs this transport when any `http-transport-reqwest-*` feature is enabled. + +```toml +# Default — reqwest transport with native-tls +opendal = { version = "0.57" } +``` + +To select a different TLS backend, disable default features and enable the +one you need: + +```toml +opendal = { version = "0.57", default-features = false, features = ["http-transport-reqwest-rustls"] } +``` + +### Feature usage with `rustls` + +```toml +[dependencies] +opendal-http-transport-reqwest = { version = "0.57", default-features = false, features = ["rustls"] } +``` + +```rust +use std::time::Duration; + +use opendal_http_transport_reqwest::ReqwestTlsBackend; +use opendal_http_transport_reqwest::ReqwestTransport; +use opendal::HttpTransporter; + +// You can configure reqwest dynamically and select a compiled TLS backend. +let tls_backend = "rustls".parse::().unwrap(); +let transport = ReqwestTransport::builder() + .tls_backend(tls_backend) + .configure(|builder| builder.connect_timeout(Duration::from_secs(10))) + .build() + .unwrap(); +``` + +### Bringing your own reqwest client + +When you need full control over the TLS stack — custom `ClientConfig`, +client certificates, proxy settings, or connection pool tuning — build a +`reqwest::Client` yourself and wrap it: + +```toml +[dependencies] +opendal = { version = "0.57", default-features = false, features = [ + "services-s3", + "http-transport-reqwest-rustls-no-provider", +] } +opendal-http-transport-reqwest = { version = "0.57", default-features = false, features = ["rustls-no-provider"] } +rustls = { version = "0.23", features = ["ring"], default-features = false } +webpki-roots = "1" +``` + +```rust +use std::time::Duration; + +use opendal::HttpTransporter; +use opendal::OperationContext; +use opendal_http_transport_reqwest::ReqwestTransport; + +fn main() { + // 1. Configure your crypto provider and certificate roots. + let root_store = + rustls::RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); + + let tls_config = rustls::ClientConfig::builder_with_provider( + rustls::crypto::ring::default_provider().into(), + ) + .with_safe_default_protocol_versions() + .unwrap() + .with_root_certificates(root_store) + .with_no_client_auth(); + + // 2. Build a reqwest client with your TLS config. + let client = reqwest::Client::builder() + .tls_backend_preconfigured(tls_config) + .connect_timeout(Duration::from_secs(10)) + .pool_max_idle_per_host(20) + .build() + .unwrap(); + + // 3. Wrap it as a ReqwestTransport and attach to an operator. + let transport = HttpTransporter::new(ReqwestTransport::new(client)); + + let op = opendal::Operator::via_iter("s3", [ + ("bucket".to_string(), "my-bucket".to_string()), + ("region".to_string(), "us-east-1".to_string()), + ]) + .expect("failed to build operator") + .with_context(OperationContext::new().with_http_transport(transport)); +} +``` + +This approach gives you complete ownership over TLS and client. + +## License and Trademarks + +Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + +Apache OpenDAL, OpenDAL, and Apache are either registered trademarks or trademarks of the Apache Software Foundation. diff --git a/core/http-transports/reqwest/src/lib.rs b/core/http-transports/reqwest/src/lib.rs index 6f7c49b3a4e1..d9ca7a0126b2 100644 --- a/core/http-transports/reqwest/src/lib.rs +++ b/core/http-transports/reqwest/src/lib.rs @@ -16,11 +16,63 @@ // under the License. //! Reqwest based HTTP transport for Apache OpenDAL. +//! +//! # TLS backends +//! +//! Enable one of the following Cargo features to select a TLS backend. +//! The `default` feature enables `native-tls`. +//! +//! - **`native-tls`** — Platform TLS links against the OS TLS library: +//! - Windows: SChannel +//! - macOS: Secure Transport +//! - Linux: OpenSSL, requires system development packages +//! +//! - **`rustls`** — [Rustls](https://crates.io/crates/rustls) with the +//! aws-lc-rs crypto provider and platform certificate verification. +//! Pure-Rust TLS stack, no system TLS dependency. +//! +//! - **`rustls-no-provider`** — Rustls without a built-in crypto provider. +//! You must install a [`rustls::crypto::CryptoProvider`] before building a +//! client. Use this when you want to bring your own provider (e.g. `ring` +//! or a FIPS-certified module). +//! +//! - **`webpki-roots`** — Rustls with bundled +//! [Mozilla root certificates](https://crates.io/crates/webpki-roots). +//! Fully self-contained: no platform certificate store dependency. +//! When opendal compiles, cargo will download webpki and bundle certificates. +//! Good for reproducible builds and environments with a stripped-down root store. +//! But you can't update root certificates without rebuilding opendal. +//! +//! In application builds, prefer selecting a single backend. In workspace or +//! `--all-features` builds, Cargo may enable multiple backend features via +//! feature unification; the transport picks native TLS when available. +//! +//! # Builder example +//! +//! Use [`ReqwestTransport::builder`] when applications need to select a TLS +//! backend at runtime while still configuring reqwest-specific options: +//! +//! ```no_run +//! use std::time::Duration; +//! +//! use opendal_core::HttpTransporter; +//! use opendal_http_transport_reqwest::ReqwestTlsBackend; +//! use opendal_http_transport_reqwest::ReqwestTransport; +//! +//! # fn build() -> opendal_core::Result<()> { +//! let transport = ReqwestTransport::builder() +//! .tls_backend("rustls".parse::()?) +//! .configure(|builder| builder.connect_timeout(Duration::from_secs(10))) +//! .build()?; +//! let _transport = HttpTransporter::new(transport); +//! # Ok(()) +//! # } +//! ``` +//! +//! Building the transport returns [`ErrorKind::ConfigInvalid`] when the chosen +//! TLS backend feature was not compiled into this crate. -#![deny(missing_docs)] - -use std::fmt::Debug; -use std::fmt::Formatter; +use std::fmt::{Debug, Display, Formatter}; use std::future; use std::mem; use std::str::FromStr; @@ -38,13 +90,165 @@ use opendal_core::Result; use opendal_core::raw::parse_content_encoding; use opendal_core::raw::parse_content_length; -static DEFAULT_REQWEST_CLIENT: LazyLock = LazyLock::new(reqwest::Client::new); +static DEFAULT_REQWEST_CLIENT: LazyLock = LazyLock::new(build_default_client); -/// A [`reqwest::Client`] backed HTTP transport. +fn build_default_client() -> reqwest::Client { + ReqwestTransportBuilder::new() + .build_client() + .expect("failed to build default reqwest client") +} +#[cfg(not(any( + feature = "native-tls", + feature = "rustls", + feature = "rustls-no-provider", + feature = "webpki-roots", +)))] +compile_error!( + "At least one reqwest TLS backend feature must be enabled: native-tls, rustls, rustls-no-provider, webpki-roots" +); + +/// TLS backend options. /// -/// # Notes +/// Each variant corresponds to one of the Cargo features exposed by this +/// crate. Building a transport with a variant whose feature is not compiled +/// returns [`ErrorKind::ConfigInvalid`]. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum ReqwestTlsBackend { + /// Platform TLS through reqwest's `native-tls` feature. + #[cfg(feature = "native-tls")] + NativeTls, + /// Rustls with the aws-lc-rs crypto provider and platform certificate verification. + #[cfg(feature = "rustls")] + Rustls, + /// Rustls without a built-in crypto provider through reqwest's `rustls-no-provider` feature. + #[cfg(feature = "rustls-no-provider")] + RustlsNoProvider, + /// Rustls with bundled Mozilla root certificates. + #[cfg(feature = "webpki-roots")] + WebpkiRoots, +} + +impl ReqwestTlsBackend { + /// Return the stable feature-style name for this TLS backend. + pub fn as_str(self) -> &'static str { + match self { + #[cfg(feature = "native-tls")] + ReqwestTlsBackend::NativeTls => "native-tls", + #[cfg(feature = "rustls")] + ReqwestTlsBackend::Rustls => "rustls", + #[cfg(feature = "rustls-no-provider")] + ReqwestTlsBackend::RustlsNoProvider => "rustls-no-provider", + #[cfg(feature = "webpki-roots")] + ReqwestTlsBackend::WebpkiRoots => "webpki-roots", + } + } +} + +impl Display for ReqwestTlsBackend { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(self.as_str()) + } +} + +impl FromStr for ReqwestTlsBackend { + type Err = Error; + + fn from_str(value: &str) -> Result { + let value = value.trim(); + + match value { + #[cfg(feature = "native-tls")] + "native-tls" => Ok(Self::NativeTls), + + #[cfg(feature = "rustls")] + "rustls" => Ok(Self::Rustls), + + #[cfg(feature = "rustls-no-provider")] + "rustls-no-provider" => Ok(Self::RustlsNoProvider), + + #[cfg(feature = "webpki-roots")] + "webpki-roots" => Ok(Self::WebpkiRoots), + + _ => Err( + Error::new(ErrorKind::ConfigInvalid, "unknown reqwest TLS backend") + .with_operation("ReqwestTlsBackend::from_str") + .with_context("tls_backend", value), + ), + } + } +} + +/// Builder for [`ReqwestTransport`]. /// -/// Reqwest must be configured with a TLS feature before sending HTTPS requests. +/// This builder enables applications choose an TLS backend at runtime +/// while preserving access to reqwest's own [`reqwest::ClientBuilder`] options. +pub struct ReqwestTransportBuilder { + client_builder: reqwest::ClientBuilder, + tls_backend: ReqwestTlsBackend, +} + +impl Default for ReqwestTransportBuilder { + fn default() -> Self { + Self::new() + } +} + +impl ReqwestTransportBuilder { + /// Create a new builder from [`reqwest::Client::builder`]. + pub fn new() -> Self { + Self { + client_builder: reqwest::Client::builder(), + tls_backend: ReqwestTlsBackend::NativeTls, + } + } + + /// Create a new builder from an existing [`reqwest::ClientBuilder`]. + pub fn from_client_builder(client_builder: reqwest::ClientBuilder) -> Self { + Self { + client_builder, + tls_backend: ReqwestTlsBackend::NativeTls, + } + } + + /// Select the TLS backend to use while building the reqwest client. + /// + /// [`Self::build`] and [`Self::build_client`] return + /// [`ErrorKind::ConfigInvalid`] if the matching Cargo feature is not + /// compiled into this crate. + pub fn tls_backend(mut self, tls_backend: ReqwestTlsBackend) -> Self { + self.tls_backend = tls_backend; + self + } + + /// Configure the underlying [`reqwest::ClientBuilder`]. + pub fn configure( + mut self, + configure: impl FnOnce(reqwest::ClientBuilder) -> reqwest::ClientBuilder, + ) -> Self { + self.client_builder = configure(self.client_builder); + self + } + + /// Build a [`reqwest::Client`]. + pub fn build_client(self) -> Result { + let tls_backend = self.tls_backend; + let client_builder = apply_tls_backend(self.client_builder, tls_backend); + + client_builder.build().map_err(|err| { + Error::new(ErrorKind::ConfigInvalid, "reqwest client config is invalid") + .with_operation("ReqwestTransportBuilder::build") + .with_context("tls_backend", tls_backend.as_str()) + .set_source(err) + }) + } + + /// Build a [`ReqwestTransport`]. + pub fn build(self) -> Result { + self.build_client().map(ReqwestTransport::new) + } +} + +/// A [`reqwest::Client`] backed HTTP transport. #[derive(Clone)] pub struct ReqwestTransport { client: reqwest::Client, @@ -69,10 +273,71 @@ impl From for ReqwestTransport { } impl ReqwestTransport { + /// Create a builder for [`ReqwestTransport`]. + pub fn builder() -> ReqwestTransportBuilder { + ReqwestTransportBuilder::new() + } + /// Create a new transport from a [`reqwest::Client`]. pub fn new(client: reqwest::Client) -> Self { Self { client } } + + /// Create a new transport from a [`reqwest::ClientBuilder`] and TLS backend. + pub fn from_client_builder( + client_builder: reqwest::ClientBuilder, + tls_backend: ReqwestTlsBackend, + ) -> Result { + ReqwestTransportBuilder::from_client_builder(client_builder) + .tls_backend(tls_backend) + .build() + } +} + +fn apply_tls_backend( + client_builder: reqwest::ClientBuilder, + tls_backend: ReqwestTlsBackend, +) -> reqwest::ClientBuilder { + match tls_backend { + #[cfg(feature = "native-tls")] + ReqwestTlsBackend::NativeTls => client_builder.tls_backend_native(), + #[cfg(feature = "rustls")] + ReqwestTlsBackend::Rustls => client_builder.tls_backend_preconfigured(rustls_tls_config()), + #[cfg(feature = "rustls-no-provider")] + ReqwestTlsBackend::RustlsNoProvider => client_builder.tls_backend_rustls(), + #[cfg(feature = "webpki-roots")] + ReqwestTlsBackend::WebpkiRoots => { + client_builder.tls_backend_preconfigured(webpki_roots_tls_config()) + } + } +} + +#[cfg(feature = "rustls")] +fn rustls_tls_config() -> rustls::ClientConfig { + use rustls_platform_verifier::BuilderVerifierExt; + + rustls::ClientConfig::builder_with_provider( + rustls::crypto::aws_lc_rs::default_provider().into(), + ) + .with_safe_default_protocol_versions() + .expect("aws-lc-rs provider must support the default rustls protocol versions") + .with_platform_verifier() + .expect("platform verifier must be available") + .with_no_client_auth() +} + +#[cfg(feature = "webpki-roots")] +fn webpki_roots_tls_config() -> rustls::ClientConfig { + let root_store = + rustls::RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); + + rustls::ClientConfig::builder_with_provider( + rustls::crypto::aws_lc_rs::default_provider().into(), + ) + .with_safe_default_protocol_versions() + .expect("aws-lc-rs provider must support the default rustls protocol versions") + .with_root_certificates(root_store) + .with_no_client_auth() } impl HttpTransport for ReqwestTransport { @@ -202,3 +467,66 @@ impl http_body::Body for HttpBufferBody { http_body::SizeHint::with_exact(self.0.len() as u64) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_tls_backend_from_str_native_tls() { + // native-tls is the default feature, always compiled in tests. + assert_eq!( + "native-tls".parse::().unwrap(), + ReqwestTlsBackend::NativeTls + ); + } + + #[test] + fn test_tls_backend_from_str_trims_whitespace() { + assert_eq!( + " native-tls ".parse::().unwrap(), + ReqwestTlsBackend::NativeTls + ); + } + + #[test] + fn test_tls_backend_from_str_unknown() { + let err = "bogus".parse::().unwrap_err(); + assert_eq!(err.kind(), ErrorKind::ConfigInvalid); + } + + #[test] + fn test_tls_backend_display_roundtrip() { + let backend = ReqwestTlsBackend::NativeTls; + let s = backend.to_string(); + let parsed: ReqwestTlsBackend = s.parse().unwrap(); + assert_eq!(parsed, backend); + } + + #[test] + fn test_builder_defaults_to_native_tls() { + let builder = ReqwestTransportBuilder::new(); + assert_eq!(builder.tls_backend, ReqwestTlsBackend::NativeTls); + } + + #[test] + fn test_builder_configure() { + let transport = ReqwestTransportBuilder::new() + .configure(|b| b.connect_timeout(std::time::Duration::from_secs(5))) + .build(); + assert!(transport.is_ok()); + } + + #[test] + fn test_default_transport_succeeds() { + let transport = ReqwestTransport::default(); + assert_eq!(format!("{:?}", transport), "ReqwestTransport"); + } + + #[test] + fn test_from_reqwest_client() { + let client = reqwest::Client::new(); + let transport = ReqwestTransport::from(client); + assert_eq!(format!("{:?}", transport), "ReqwestTransport"); + } +} diff --git a/core/services/hf/Cargo.toml b/core/services/hf/Cargo.toml index c159da107843..205d0cd7f295 100644 --- a/core/services/hf/Cargo.toml +++ b/core/services/hf/Cargo.toml @@ -43,8 +43,6 @@ serde_json = { workspace = true } [dev-dependencies] base64 = { workspace = true } futures = { workspace = true } -opendal-http-transport-reqwest = { path = "../../http-transports/reqwest", version = "0.57.0", features = [ - "rustls", -] } +opendal-http-transport-reqwest = { path = "../../http-transports/reqwest", version = "0.57.0" } serde_json = { workspace = true } tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } diff --git a/core/src/lib.rs b/core/src/lib.rs index 2457fd2e1f4c..1e4c1b0f40c7 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -24,6 +24,11 @@ pub use opendal_core::*; +#[cfg(feature = "http-transport-reqwest")] +pub use opendal_http_transport_reqwest::{ + ReqwestTlsBackend, ReqwestTransport, ReqwestTransportBuilder, +}; + #[cfg(feature = "tests")] pub extern crate opendal_testkit as tests; From dede66d66bce14a2ff3b74e155ea2e6029cbb7b3 Mon Sep 17 00:00:00 2001 From: Erick Guan <297343+erickguan@users.noreply.github.com> Date: Tue, 30 Jun 2026 23:34:42 +0800 Subject: [PATCH 02/22] Update reqwest feature --- core/http-transports/reqwest/Cargo.toml | 2 +- core/http-transports/reqwest/README.md | 6 +++--- core/http-transports/reqwest/src/lib.rs | 22 +++++++++++----------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/core/http-transports/reqwest/Cargo.toml b/core/http-transports/reqwest/Cargo.toml index 4db5abe5f001..2588a75f22c9 100644 --- a/core/http-transports/reqwest/Cargo.toml +++ b/core/http-transports/reqwest/Cargo.toml @@ -44,7 +44,7 @@ rustls = [ # Use Rustls without a built-in crypto provider. rustls-no-provider = ["reqwest/rustls-no-provider"] # Use Rustls with the aws-lc-rs crypto provider and bundled Mozilla root certificates. -webpki-roots = [ +rustls-webpki-roots = [ "reqwest/rustls-no-provider", "dep:rustls", "rustls/aws-lc-rs", diff --git a/core/http-transports/reqwest/README.md b/core/http-transports/reqwest/README.md index 7498b3c46661..6ea6236969b2 100644 --- a/core/http-transports/reqwest/README.md +++ b/core/http-transports/reqwest/README.md @@ -11,8 +11,8 @@ When using Rustls, TLS configuration has two independent axes: | Axis | What it decides | Options | |------|----------------|---------| -| **Crypto provider** | Who performs the cryptographic operations (key exchange, symmetric ciphers, hashing) | `aws-lc-rs` (default in `rustls`/`webpki-roots`), `ring`, or any custom `CryptoProvider` | -| **Certificate verification** | How the server's TLS certificate chain is validated | Platform verifier (default in `rustls`), bundled Mozilla roots (`webpki-roots`), or custom | +| **Crypto provider** | Who performs the cryptographic operations (key exchange, symmetric ciphers, hashing) | `aws-lc-rs`, `ring`, or any custom `CryptoProvider` | +| **Certificate verification** | How the server's TLS certificate chain is validated | Platform verifier (default in `rustls`), bundled Mozilla roots (`rustls-webpki-roots`), or custom | The `native-tls` feature sidesteps both axes by delegating everything to the OS TLS library (SChannel / Secure Transport / OpenSSL). @@ -23,7 +23,7 @@ the OS TLS library (SChannel / Secure Transport / OpenSSL). |---------|----------------|-------------------|----------| | `native-tls` (default) | OS library | OS trust store | You want zero Rust-side TLS config | | `rustls` | aws-lc-rs | Platform verifier | Pure-Rust TLS with OS trust store | -| `webpki-roots` | aws-lc-rs | Bundled Mozilla roots | Fully self-contained, no OS dependency | +| `rustls-webpki-roots` | aws-lc-rs | Bundled Mozilla roots | Fully self-contained, no OS dependency | | `rustls-no-provider` | **you provide** | **you provide** | BYO crypto (ring, FIPS module, etc.) | ### Usage via the `opendal` facade crate diff --git a/core/http-transports/reqwest/src/lib.rs b/core/http-transports/reqwest/src/lib.rs index d9ca7a0126b2..44efee416f7f 100644 --- a/core/http-transports/reqwest/src/lib.rs +++ b/core/http-transports/reqwest/src/lib.rs @@ -36,7 +36,7 @@ //! client. Use this when you want to bring your own provider (e.g. `ring` //! or a FIPS-certified module). //! -//! - **`webpki-roots`** — Rustls with bundled +//! - **`rustls-webpki-roots`** — Rustls with bundled //! [Mozilla root certificates](https://crates.io/crates/webpki-roots). //! Fully self-contained: no platform certificate store dependency. //! When opendal compiles, cargo will download webpki and bundle certificates. @@ -101,10 +101,10 @@ fn build_default_client() -> reqwest::Client { feature = "native-tls", feature = "rustls", feature = "rustls-no-provider", - feature = "webpki-roots", + feature = "rustls-webpki-roots", )))] compile_error!( - "At least one reqwest TLS backend feature must be enabled: native-tls, rustls, rustls-no-provider, webpki-roots" + "At least one reqwest TLS backend feature must be enabled: native-tls, rustls, rustls-no-provider, rustls-webpki-roots" ); /// TLS backend options. @@ -124,8 +124,8 @@ pub enum ReqwestTlsBackend { #[cfg(feature = "rustls-no-provider")] RustlsNoProvider, /// Rustls with bundled Mozilla root certificates. - #[cfg(feature = "webpki-roots")] - WebpkiRoots, + #[cfg(feature = "rustls-webpki-roots")] + RustlsWebpkiRoots, } impl ReqwestTlsBackend { @@ -138,8 +138,8 @@ impl ReqwestTlsBackend { ReqwestTlsBackend::Rustls => "rustls", #[cfg(feature = "rustls-no-provider")] ReqwestTlsBackend::RustlsNoProvider => "rustls-no-provider", - #[cfg(feature = "webpki-roots")] - ReqwestTlsBackend::WebpkiRoots => "webpki-roots", + #[cfg(feature = "rustls-webpki-roots")] + ReqwestTlsBackend::RustlsWebpkiRoots => "rustls-webpki-roots", } } } @@ -166,8 +166,8 @@ impl FromStr for ReqwestTlsBackend { #[cfg(feature = "rustls-no-provider")] "rustls-no-provider" => Ok(Self::RustlsNoProvider), - #[cfg(feature = "webpki-roots")] - "webpki-roots" => Ok(Self::WebpkiRoots), + #[cfg(feature = "rustls-webpki-roots")] + "rustls-webpki-roots" => Ok(Self::RustlsWebpkiRoots), _ => Err( Error::new(ErrorKind::ConfigInvalid, "unknown reqwest TLS backend") @@ -305,8 +305,8 @@ fn apply_tls_backend( ReqwestTlsBackend::Rustls => client_builder.tls_backend_preconfigured(rustls_tls_config()), #[cfg(feature = "rustls-no-provider")] ReqwestTlsBackend::RustlsNoProvider => client_builder.tls_backend_rustls(), - #[cfg(feature = "webpki-roots")] - ReqwestTlsBackend::WebpkiRoots => { + #[cfg(feature = "rustls-webpki-roots")] + ReqwestTlsBackend::RustlsWebpkiRoots => { client_builder.tls_backend_preconfigured(webpki_roots_tls_config()) } } From 2676e107db7a15fe4850a6852437bfc0238352c6 Mon Sep 17 00:00:00 2001 From: Erick Guan <297343+erickguan@users.noreply.github.com> Date: Tue, 30 Jun 2026 23:46:15 +0800 Subject: [PATCH 03/22] fix reqwest tls backend configuration --- core/Cargo.lock | 1 - core/http-transports/reqwest/Cargo.toml | 12 +--- core/http-transports/reqwest/README.md | 16 +++-- core/http-transports/reqwest/src/lib.rs | 77 +++++++++++++++---------- 4 files changed, 63 insertions(+), 43 deletions(-) diff --git a/core/Cargo.lock b/core/Cargo.lock index 54e96140bfae..8d9a9ec656c6 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -6525,7 +6525,6 @@ dependencies = [ "opendal-core", "reqwest", "rustls 0.23.40", - "rustls-platform-verifier", "webpki-roots 1.0.7", ] diff --git a/core/http-transports/reqwest/Cargo.toml b/core/http-transports/reqwest/Cargo.toml index 2588a75f22c9..467a4c70f796 100644 --- a/core/http-transports/reqwest/Cargo.toml +++ b/core/http-transports/reqwest/Cargo.toml @@ -34,17 +34,12 @@ all-features = true default = ["native-tls"] # Use platform TLS via reqwest's native-tls feature. native-tls = ["reqwest/native-tls"] -# Use Rustls with the aws-lc-rs crypto provider and platform certificate verification. -rustls = [ - "reqwest/rustls-no-provider", - "dep:rustls", - "rustls/aws-lc-rs", - "dep:rustls-platform-verifier", -] +# Use Rustls with reqwest's default crypto provider and platform certificate verification. +rustls = ["reqwest/rustls"] # Use Rustls without a built-in crypto provider. rustls-no-provider = ["reqwest/rustls-no-provider"] # Use Rustls with the aws-lc-rs crypto provider and bundled Mozilla root certificates. -rustls-webpki-roots = [ +webpki-roots = [ "reqwest/rustls-no-provider", "dep:rustls", "rustls/aws-lc-rs", @@ -61,5 +56,4 @@ reqwest = { version = "0.13.4", features = [ "stream", ], default-features = false } rustls = { version = "0.23", optional = true, default-features = false } -rustls-platform-verifier = { version = "0.7", optional = true, default-features = false } webpki-roots = { version = "1", optional = true } diff --git a/core/http-transports/reqwest/README.md b/core/http-transports/reqwest/README.md index 6ea6236969b2..a984ffc28509 100644 --- a/core/http-transports/reqwest/README.md +++ b/core/http-transports/reqwest/README.md @@ -11,8 +11,8 @@ When using Rustls, TLS configuration has two independent axes: | Axis | What it decides | Options | |------|----------------|---------| -| **Crypto provider** | Who performs the cryptographic operations (key exchange, symmetric ciphers, hashing) | `aws-lc-rs`, `ring`, or any custom `CryptoProvider` | -| **Certificate verification** | How the server's TLS certificate chain is validated | Platform verifier (default in `rustls`), bundled Mozilla roots (`rustls-webpki-roots`), or custom | +| **Crypto provider** | Who performs the cryptographic operations (key exchange, symmetric ciphers, hashing) | reqwest's default provider, or any custom `CryptoProvider` | +| **Certificate verification** | How the server's TLS certificate chain is validated | Platform verifier (default in `rustls`), bundled Mozilla roots (`webpki-roots`), or custom | The `native-tls` feature sidesteps both axes by delegating everything to the OS TLS library (SChannel / Secure Transport / OpenSSL). @@ -22,8 +22,8 @@ the OS TLS library (SChannel / Secure Transport / OpenSSL). | Feature | Crypto provider | Certificate roots | Use when | |---------|----------------|-------------------|----------| | `native-tls` (default) | OS library | OS trust store | You want zero Rust-side TLS config | -| `rustls` | aws-lc-rs | Platform verifier | Pure-Rust TLS with OS trust store | -| `rustls-webpki-roots` | aws-lc-rs | Bundled Mozilla roots | Fully self-contained, no OS dependency | +| `rustls` | reqwest default | Platform verifier | Pure-Rust TLS with OS trust store | +| `webpki-roots` | aws-lc-rs | Bundled Mozilla roots | Fully self-contained, no OS dependency | | `rustls-no-provider` | **you provide** | **you provide** | BYO crypto (ring, FIPS module, etc.) | ### Usage via the `opendal` facade crate @@ -45,6 +45,14 @@ opendal = { version = "0.57", default-features = false, features = ["http-transp ### Feature usage with `rustls` +The `rustls` feature uses reqwest's own Rustls configuration through +`ClientBuilder::tls_backend_rustls()`, so settings such as custom root +certificates, client identity, SNI, TLS info, and dangerous certificate +verification flags should be configured with reqwest's builder methods. +The `webpki-roots` feature still builds a `rustls::ClientConfig` directly +because `webpki-roots` exposes Rustls trust anchors rather than +`reqwest::Certificate` values. + ```toml [dependencies] opendal-http-transport-reqwest = { version = "0.57", default-features = false, features = ["rustls"] } diff --git a/core/http-transports/reqwest/src/lib.rs b/core/http-transports/reqwest/src/lib.rs index 44efee416f7f..3a3f7dc5b6fb 100644 --- a/core/http-transports/reqwest/src/lib.rs +++ b/core/http-transports/reqwest/src/lib.rs @@ -27,16 +27,16 @@ //! - macOS: Secure Transport //! - Linux: OpenSSL, requires system development packages //! -//! - **`rustls`** — [Rustls](https://crates.io/crates/rustls) with the -//! aws-lc-rs crypto provider and platform certificate verification. -//! Pure-Rust TLS stack, no system TLS dependency. +//! - **`rustls`** — [Rustls](https://crates.io/crates/rustls) configured by +//! reqwest with its default crypto provider and platform certificate +//! verification. Pure-Rust TLS stack, no system TLS dependency. //! //! - **`rustls-no-provider`** — Rustls without a built-in crypto provider. //! You must install a [`rustls::crypto::CryptoProvider`] before building a //! client. Use this when you want to bring your own provider (e.g. `ring` //! or a FIPS-certified module). //! -//! - **`rustls-webpki-roots`** — Rustls with bundled +//! - **`webpki-roots`** — Rustls with bundled //! [Mozilla root certificates](https://crates.io/crates/webpki-roots). //! Fully self-contained: no platform certificate store dependency. //! When opendal compiles, cargo will download webpki and bundle certificates. @@ -101,10 +101,10 @@ fn build_default_client() -> reqwest::Client { feature = "native-tls", feature = "rustls", feature = "rustls-no-provider", - feature = "rustls-webpki-roots", + feature = "webpki-roots", )))] compile_error!( - "At least one reqwest TLS backend feature must be enabled: native-tls, rustls, rustls-no-provider, rustls-webpki-roots" + "At least one reqwest TLS backend feature must be enabled: native-tls, rustls, rustls-no-provider, webpki-roots" ); /// TLS backend options. @@ -117,15 +117,15 @@ pub enum ReqwestTlsBackend { /// Platform TLS through reqwest's `native-tls` feature. #[cfg(feature = "native-tls")] NativeTls, - /// Rustls with the aws-lc-rs crypto provider and platform certificate verification. + /// Rustls configured by reqwest with its default crypto provider and platform certificate verification. #[cfg(feature = "rustls")] Rustls, /// Rustls without a built-in crypto provider through reqwest's `rustls-no-provider` feature. #[cfg(feature = "rustls-no-provider")] RustlsNoProvider, /// Rustls with bundled Mozilla root certificates. - #[cfg(feature = "rustls-webpki-roots")] - RustlsWebpkiRoots, + #[cfg(feature = "webpki-roots")] + WebpkiRoots, } impl ReqwestTlsBackend { @@ -138,8 +138,8 @@ impl ReqwestTlsBackend { ReqwestTlsBackend::Rustls => "rustls", #[cfg(feature = "rustls-no-provider")] ReqwestTlsBackend::RustlsNoProvider => "rustls-no-provider", - #[cfg(feature = "rustls-webpki-roots")] - ReqwestTlsBackend::RustlsWebpkiRoots => "rustls-webpki-roots", + #[cfg(feature = "webpki-roots")] + ReqwestTlsBackend::WebpkiRoots => "webpki-roots", } } } @@ -166,8 +166,8 @@ impl FromStr for ReqwestTlsBackend { #[cfg(feature = "rustls-no-provider")] "rustls-no-provider" => Ok(Self::RustlsNoProvider), - #[cfg(feature = "rustls-webpki-roots")] - "rustls-webpki-roots" => Ok(Self::RustlsWebpkiRoots), + #[cfg(feature = "webpki-roots")] + "webpki-roots" => Ok(Self::WebpkiRoots), _ => Err( Error::new(ErrorKind::ConfigInvalid, "unknown reqwest TLS backend") @@ -198,7 +198,7 @@ impl ReqwestTransportBuilder { pub fn new() -> Self { Self { client_builder: reqwest::Client::builder(), - tls_backend: ReqwestTlsBackend::NativeTls, + tls_backend: default_tls_backend(), } } @@ -206,7 +206,7 @@ impl ReqwestTransportBuilder { pub fn from_client_builder(client_builder: reqwest::ClientBuilder) -> Self { Self { client_builder, - tls_backend: ReqwestTlsBackend::NativeTls, + tls_backend: default_tls_backend(), } } @@ -302,28 +302,47 @@ fn apply_tls_backend( #[cfg(feature = "native-tls")] ReqwestTlsBackend::NativeTls => client_builder.tls_backend_native(), #[cfg(feature = "rustls")] - ReqwestTlsBackend::Rustls => client_builder.tls_backend_preconfigured(rustls_tls_config()), + ReqwestTlsBackend::Rustls => client_builder.tls_backend_rustls(), #[cfg(feature = "rustls-no-provider")] ReqwestTlsBackend::RustlsNoProvider => client_builder.tls_backend_rustls(), - #[cfg(feature = "rustls-webpki-roots")] - ReqwestTlsBackend::RustlsWebpkiRoots => { + #[cfg(feature = "webpki-roots")] + ReqwestTlsBackend::WebpkiRoots => { client_builder.tls_backend_preconfigured(webpki_roots_tls_config()) } } } -#[cfg(feature = "rustls")] -fn rustls_tls_config() -> rustls::ClientConfig { - use rustls_platform_verifier::BuilderVerifierExt; +fn default_tls_backend() -> ReqwestTlsBackend { + default_tls_backend_impl() +} - rustls::ClientConfig::builder_with_provider( - rustls::crypto::aws_lc_rs::default_provider().into(), - ) - .with_safe_default_protocol_versions() - .expect("aws-lc-rs provider must support the default rustls protocol versions") - .with_platform_verifier() - .expect("platform verifier must be available") - .with_no_client_auth() +#[cfg(feature = "native-tls")] +fn default_tls_backend_impl() -> ReqwestTlsBackend { + ReqwestTlsBackend::NativeTls +} + +#[cfg(all(not(feature = "native-tls"), feature = "rustls"))] +fn default_tls_backend_impl() -> ReqwestTlsBackend { + ReqwestTlsBackend::Rustls +} + +#[cfg(all( + not(feature = "native-tls"), + not(feature = "rustls"), + feature = "rustls-no-provider" +))] +fn default_tls_backend_impl() -> ReqwestTlsBackend { + ReqwestTlsBackend::RustlsNoProvider +} + +#[cfg(all( + not(feature = "native-tls"), + not(feature = "rustls"), + not(feature = "rustls-no-provider"), + feature = "webpki-roots" +))] +fn default_tls_backend_impl() -> ReqwestTlsBackend { + ReqwestTlsBackend::WebpkiRoots } #[cfg(feature = "webpki-roots")] From 0a3522a353ecd0acab8fe35ef54e3c2d379d69ec Mon Sep 17 00:00:00 2001 From: Erick Guan <297343+erickguan@users.noreply.github.com> Date: Wed, 1 Jul 2026 23:56:46 +0800 Subject: [PATCH 04/22] fix reqwest rustls webpki feature name --- core/Cargo.toml | 2 +- core/http-transports/reqwest/Cargo.toml | 2 +- core/http-transports/reqwest/README.md | 6 +++--- core/http-transports/reqwest/src/lib.rs | 28 ++++++++++++------------- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/core/Cargo.toml b/core/Cargo.toml index 4029e314b875..f7b525ddea18 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -114,7 +114,7 @@ http-transport-reqwest-rustls-no-provider = [ # Use reqwest with Rustls, the aws-lc-rs crypto provider, and bundled Mozilla root certificates. http-transport-reqwest-webpki-roots = [ "http-transport-reqwest", - "opendal-http-transport-reqwest/webpki-roots", + "opendal-http-transport-reqwest/rustls-webpki-roots", ] internal-tokio-rt = ["opendal-core/internal-tokio-rt"] layers-async-backtrace = ["dep:opendal-layer-async-backtrace"] diff --git a/core/http-transports/reqwest/Cargo.toml b/core/http-transports/reqwest/Cargo.toml index 467a4c70f796..0f759af6004a 100644 --- a/core/http-transports/reqwest/Cargo.toml +++ b/core/http-transports/reqwest/Cargo.toml @@ -39,7 +39,7 @@ rustls = ["reqwest/rustls"] # Use Rustls without a built-in crypto provider. rustls-no-provider = ["reqwest/rustls-no-provider"] # Use Rustls with the aws-lc-rs crypto provider and bundled Mozilla root certificates. -webpki-roots = [ +rustls-webpki-roots = [ "reqwest/rustls-no-provider", "dep:rustls", "rustls/aws-lc-rs", diff --git a/core/http-transports/reqwest/README.md b/core/http-transports/reqwest/README.md index a984ffc28509..39adb3696499 100644 --- a/core/http-transports/reqwest/README.md +++ b/core/http-transports/reqwest/README.md @@ -12,7 +12,7 @@ When using Rustls, TLS configuration has two independent axes: | Axis | What it decides | Options | |------|----------------|---------| | **Crypto provider** | Who performs the cryptographic operations (key exchange, symmetric ciphers, hashing) | reqwest's default provider, or any custom `CryptoProvider` | -| **Certificate verification** | How the server's TLS certificate chain is validated | Platform verifier (default in `rustls`), bundled Mozilla roots (`webpki-roots`), or custom | +| **Certificate verification** | How the server's TLS certificate chain is validated | Platform verifier (default in `rustls`), bundled Mozilla roots (`rustls-webpki-roots`), or custom | The `native-tls` feature sidesteps both axes by delegating everything to the OS TLS library (SChannel / Secure Transport / OpenSSL). @@ -23,7 +23,7 @@ the OS TLS library (SChannel / Secure Transport / OpenSSL). |---------|----------------|-------------------|----------| | `native-tls` (default) | OS library | OS trust store | You want zero Rust-side TLS config | | `rustls` | reqwest default | Platform verifier | Pure-Rust TLS with OS trust store | -| `webpki-roots` | aws-lc-rs | Bundled Mozilla roots | Fully self-contained, no OS dependency | +| `rustls-webpki-roots` | aws-lc-rs | Bundled Mozilla roots | Fully self-contained, no OS dependency | | `rustls-no-provider` | **you provide** | **you provide** | BYO crypto (ring, FIPS module, etc.) | ### Usage via the `opendal` facade crate @@ -49,7 +49,7 @@ The `rustls` feature uses reqwest's own Rustls configuration through `ClientBuilder::tls_backend_rustls()`, so settings such as custom root certificates, client identity, SNI, TLS info, and dangerous certificate verification flags should be configured with reqwest's builder methods. -The `webpki-roots` feature still builds a `rustls::ClientConfig` directly +The `rustls-webpki-roots` feature still builds a `rustls::ClientConfig` directly because `webpki-roots` exposes Rustls trust anchors rather than `reqwest::Certificate` values. diff --git a/core/http-transports/reqwest/src/lib.rs b/core/http-transports/reqwest/src/lib.rs index 3a3f7dc5b6fb..f518d91134fb 100644 --- a/core/http-transports/reqwest/src/lib.rs +++ b/core/http-transports/reqwest/src/lib.rs @@ -36,7 +36,7 @@ //! client. Use this when you want to bring your own provider (e.g. `ring` //! or a FIPS-certified module). //! -//! - **`webpki-roots`** — Rustls with bundled +//! - **`rustls-webpki-roots`** — Rustls with bundled //! [Mozilla root certificates](https://crates.io/crates/webpki-roots). //! Fully self-contained: no platform certificate store dependency. //! When opendal compiles, cargo will download webpki and bundle certificates. @@ -101,10 +101,10 @@ fn build_default_client() -> reqwest::Client { feature = "native-tls", feature = "rustls", feature = "rustls-no-provider", - feature = "webpki-roots", + feature = "rustls-webpki-roots", )))] compile_error!( - "At least one reqwest TLS backend feature must be enabled: native-tls, rustls, rustls-no-provider, webpki-roots" + "At least one reqwest TLS backend feature must be enabled: native-tls, rustls, rustls-no-provider, rustls-webpki-roots" ); /// TLS backend options. @@ -124,8 +124,8 @@ pub enum ReqwestTlsBackend { #[cfg(feature = "rustls-no-provider")] RustlsNoProvider, /// Rustls with bundled Mozilla root certificates. - #[cfg(feature = "webpki-roots")] - WebpkiRoots, + #[cfg(feature = "rustls-webpki-roots")] + RustlsWebpkiRoots, } impl ReqwestTlsBackend { @@ -138,8 +138,8 @@ impl ReqwestTlsBackend { ReqwestTlsBackend::Rustls => "rustls", #[cfg(feature = "rustls-no-provider")] ReqwestTlsBackend::RustlsNoProvider => "rustls-no-provider", - #[cfg(feature = "webpki-roots")] - ReqwestTlsBackend::WebpkiRoots => "webpki-roots", + #[cfg(feature = "rustls-webpki-roots")] + ReqwestTlsBackend::RustlsWebpkiRoots => "rustls-webpki-roots", } } } @@ -166,8 +166,8 @@ impl FromStr for ReqwestTlsBackend { #[cfg(feature = "rustls-no-provider")] "rustls-no-provider" => Ok(Self::RustlsNoProvider), - #[cfg(feature = "webpki-roots")] - "webpki-roots" => Ok(Self::WebpkiRoots), + #[cfg(feature = "rustls-webpki-roots")] + "rustls-webpki-roots" => Ok(Self::RustlsWebpkiRoots), _ => Err( Error::new(ErrorKind::ConfigInvalid, "unknown reqwest TLS backend") @@ -305,8 +305,8 @@ fn apply_tls_backend( ReqwestTlsBackend::Rustls => client_builder.tls_backend_rustls(), #[cfg(feature = "rustls-no-provider")] ReqwestTlsBackend::RustlsNoProvider => client_builder.tls_backend_rustls(), - #[cfg(feature = "webpki-roots")] - ReqwestTlsBackend::WebpkiRoots => { + #[cfg(feature = "rustls-webpki-roots")] + ReqwestTlsBackend::RustlsWebpkiRoots => { client_builder.tls_backend_preconfigured(webpki_roots_tls_config()) } } @@ -339,13 +339,13 @@ fn default_tls_backend_impl() -> ReqwestTlsBackend { not(feature = "native-tls"), not(feature = "rustls"), not(feature = "rustls-no-provider"), - feature = "webpki-roots" + feature = "rustls-webpki-roots" ))] fn default_tls_backend_impl() -> ReqwestTlsBackend { - ReqwestTlsBackend::WebpkiRoots + ReqwestTlsBackend::RustlsWebpkiRoots } -#[cfg(feature = "webpki-roots")] +#[cfg(feature = "rustls-webpki-roots")] fn webpki_roots_tls_config() -> rustls::ClientConfig { let root_store = rustls::RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); From 2de79e807d49c648371b67ab92b5c0656182168e Mon Sep 17 00:00:00 2001 From: Erick Guan <297343+erickguan@users.noreply.github.com> Date: Thu, 2 Jul 2026 22:50:52 +0800 Subject: [PATCH 05/22] add reqwest rustls ring backend --- core/Cargo.lock | 1 + core/Cargo.toml | 5 ++ core/http-transports/reqwest/Cargo.toml | 10 +++ core/http-transports/reqwest/README.md | 5 +- core/http-transports/reqwest/src/lib.rs | 83 +++++++++++++++---------- 5 files changed, 69 insertions(+), 35 deletions(-) diff --git a/core/Cargo.lock b/core/Cargo.lock index 8d9a9ec656c6..54e96140bfae 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -6525,6 +6525,7 @@ dependencies = [ "opendal-core", "reqwest", "rustls 0.23.40", + "rustls-platform-verifier", "webpki-roots 1.0.7", ] diff --git a/core/Cargo.toml b/core/Cargo.toml index f7b525ddea18..5f3f73728ab6 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -106,6 +106,11 @@ http-transport-reqwest-rustls = [ "http-transport-reqwest", "opendal-http-transport-reqwest/rustls", ] +# Use reqwest with Rustls, the ring crypto provider, and platform certificate verification. +http-transport-reqwest-rustls-ring = [ + "http-transport-reqwest", + "opendal-http-transport-reqwest/rustls-ring", +] # Use reqwest with Rustls and no built-in crypto provider. http-transport-reqwest-rustls-no-provider = [ "http-transport-reqwest", diff --git a/core/http-transports/reqwest/Cargo.toml b/core/http-transports/reqwest/Cargo.toml index 0f759af6004a..1af7e888ad1e 100644 --- a/core/http-transports/reqwest/Cargo.toml +++ b/core/http-transports/reqwest/Cargo.toml @@ -36,6 +36,15 @@ default = ["native-tls"] native-tls = ["reqwest/native-tls"] # Use Rustls with reqwest's default crypto provider and platform certificate verification. rustls = ["reqwest/rustls"] +# Use Rustls with the ring crypto provider and platform certificate verification. +rustls-ring = [ + "reqwest/rustls-no-provider", + "dep:rustls", + "rustls/ring", + # rustls-ring builds its own ClientConfig, so it must pull in the + # platform verifier directly instead of relying on reqwest's rustls setup. + "dep:rustls-platform-verifier", +] # Use Rustls without a built-in crypto provider. rustls-no-provider = ["reqwest/rustls-no-provider"] # Use Rustls with the aws-lc-rs crypto provider and bundled Mozilla root certificates. @@ -56,4 +65,5 @@ reqwest = { version = "0.13.4", features = [ "stream", ], default-features = false } rustls = { version = "0.23", optional = true, default-features = false } +rustls-platform-verifier = { version = "0.7", optional = true, default-features = false } webpki-roots = { version = "1", optional = true } diff --git a/core/http-transports/reqwest/README.md b/core/http-transports/reqwest/README.md index 39adb3696499..12b6a220e7c4 100644 --- a/core/http-transports/reqwest/README.md +++ b/core/http-transports/reqwest/README.md @@ -11,7 +11,7 @@ When using Rustls, TLS configuration has two independent axes: | Axis | What it decides | Options | |------|----------------|---------| -| **Crypto provider** | Who performs the cryptographic operations (key exchange, symmetric ciphers, hashing) | reqwest's default provider, or any custom `CryptoProvider` | +| **Crypto provider** | Who performs the cryptographic operations (key exchange, symmetric ciphers, hashing) | reqwest's default provider, `ring`, or any custom `CryptoProvider` | | **Certificate verification** | How the server's TLS certificate chain is validated | Platform verifier (default in `rustls`), bundled Mozilla roots (`rustls-webpki-roots`), or custom | The `native-tls` feature sidesteps both axes by delegating everything to @@ -23,6 +23,7 @@ the OS TLS library (SChannel / Secure Transport / OpenSSL). |---------|----------------|-------------------|----------| | `native-tls` (default) | OS library | OS trust store | You want zero Rust-side TLS config | | `rustls` | reqwest default | Platform verifier | Pure-Rust TLS with OS trust store | +| `rustls-ring` | ring | Platform verifier | Rustls with ring and OS trust store | | `rustls-webpki-roots` | aws-lc-rs | Bundled Mozilla roots | Fully self-contained, no OS dependency | | `rustls-no-provider` | **you provide** | **you provide** | BYO crypto (ring, FIPS module, etc.) | @@ -49,6 +50,8 @@ The `rustls` feature uses reqwest's own Rustls configuration through `ClientBuilder::tls_backend_rustls()`, so settings such as custom root certificates, client identity, SNI, TLS info, and dangerous certificate verification flags should be configured with reqwest's builder methods. +The `rustls-ring` feature builds a `rustls::ClientConfig` directly so it can +select ring without installing a process-global Rustls crypto provider. The `rustls-webpki-roots` feature still builds a `rustls::ClientConfig` directly because `webpki-roots` exposes Rustls trust anchors rather than `reqwest::Certificate` values. diff --git a/core/http-transports/reqwest/src/lib.rs b/core/http-transports/reqwest/src/lib.rs index f518d91134fb..8b46692a2382 100644 --- a/core/http-transports/reqwest/src/lib.rs +++ b/core/http-transports/reqwest/src/lib.rs @@ -31,6 +31,9 @@ //! reqwest with its default crypto provider and platform certificate //! verification. Pure-Rust TLS stack, no system TLS dependency. //! +//! - **`rustls-ring`** — Rustls with the ring crypto provider and platform +//! certificate verification. +//! //! - **`rustls-no-provider`** — Rustls without a built-in crypto provider. //! You must install a [`rustls::crypto::CryptoProvider`] before building a //! client. Use this when you want to bring your own provider (e.g. `ring` @@ -100,11 +103,12 @@ fn build_default_client() -> reqwest::Client { #[cfg(not(any( feature = "native-tls", feature = "rustls", + feature = "rustls-ring", feature = "rustls-no-provider", feature = "rustls-webpki-roots", )))] compile_error!( - "At least one reqwest TLS backend feature must be enabled: native-tls, rustls, rustls-no-provider, rustls-webpki-roots" + "At least one reqwest TLS backend feature must be enabled: native-tls, rustls, rustls-ring, rustls-no-provider, rustls-webpki-roots" ); /// TLS backend options. @@ -112,19 +116,42 @@ compile_error!( /// Each variant corresponds to one of the Cargo features exposed by this /// crate. Building a transport with a variant whose feature is not compiled /// returns [`ErrorKind::ConfigInvalid`]. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub enum ReqwestTlsBackend { /// Platform TLS through reqwest's `native-tls` feature. #[cfg(feature = "native-tls")] + #[default] NativeTls, /// Rustls configured by reqwest with its default crypto provider and platform certificate verification. #[cfg(feature = "rustls")] + #[cfg_attr(not(feature = "native-tls"), default)] Rustls, + /// Rustls with the ring crypto provider and platform certificate verification. + #[cfg(feature = "rustls-ring")] + #[cfg_attr(all(not(feature = "native-tls"), not(feature = "rustls")), default)] + RustlsRing, /// Rustls without a built-in crypto provider through reqwest's `rustls-no-provider` feature. #[cfg(feature = "rustls-no-provider")] + #[cfg_attr( + all( + not(feature = "native-tls"), + not(feature = "rustls"), + not(feature = "rustls-ring") + ), + default + )] RustlsNoProvider, /// Rustls with bundled Mozilla root certificates. #[cfg(feature = "rustls-webpki-roots")] + #[cfg_attr( + all( + not(feature = "native-tls"), + not(feature = "rustls"), + not(feature = "rustls-ring"), + not(feature = "rustls-no-provider") + ), + default + )] RustlsWebpkiRoots, } @@ -136,6 +163,8 @@ impl ReqwestTlsBackend { ReqwestTlsBackend::NativeTls => "native-tls", #[cfg(feature = "rustls")] ReqwestTlsBackend::Rustls => "rustls", + #[cfg(feature = "rustls-ring")] + ReqwestTlsBackend::RustlsRing => "rustls-ring", #[cfg(feature = "rustls-no-provider")] ReqwestTlsBackend::RustlsNoProvider => "rustls-no-provider", #[cfg(feature = "rustls-webpki-roots")] @@ -163,6 +192,9 @@ impl FromStr for ReqwestTlsBackend { #[cfg(feature = "rustls")] "rustls" => Ok(Self::Rustls), + #[cfg(feature = "rustls-ring")] + "rustls-ring" => Ok(Self::RustlsRing), + #[cfg(feature = "rustls-no-provider")] "rustls-no-provider" => Ok(Self::RustlsNoProvider), @@ -198,7 +230,7 @@ impl ReqwestTransportBuilder { pub fn new() -> Self { Self { client_builder: reqwest::Client::builder(), - tls_backend: default_tls_backend(), + tls_backend: ReqwestTlsBackend::default(), } } @@ -206,7 +238,7 @@ impl ReqwestTransportBuilder { pub fn from_client_builder(client_builder: reqwest::ClientBuilder) -> Self { Self { client_builder, - tls_backend: default_tls_backend(), + tls_backend: ReqwestTlsBackend::default(), } } @@ -303,6 +335,10 @@ fn apply_tls_backend( ReqwestTlsBackend::NativeTls => client_builder.tls_backend_native(), #[cfg(feature = "rustls")] ReqwestTlsBackend::Rustls => client_builder.tls_backend_rustls(), + #[cfg(feature = "rustls-ring")] + ReqwestTlsBackend::RustlsRing => { + client_builder.tls_backend_preconfigured(rustls_ring_tls_config()) + } #[cfg(feature = "rustls-no-provider")] ReqwestTlsBackend::RustlsNoProvider => client_builder.tls_backend_rustls(), #[cfg(feature = "rustls-webpki-roots")] @@ -312,37 +348,16 @@ fn apply_tls_backend( } } -fn default_tls_backend() -> ReqwestTlsBackend { - default_tls_backend_impl() -} - -#[cfg(feature = "native-tls")] -fn default_tls_backend_impl() -> ReqwestTlsBackend { - ReqwestTlsBackend::NativeTls -} - -#[cfg(all(not(feature = "native-tls"), feature = "rustls"))] -fn default_tls_backend_impl() -> ReqwestTlsBackend { - ReqwestTlsBackend::Rustls -} - -#[cfg(all( - not(feature = "native-tls"), - not(feature = "rustls"), - feature = "rustls-no-provider" -))] -fn default_tls_backend_impl() -> ReqwestTlsBackend { - ReqwestTlsBackend::RustlsNoProvider -} +#[cfg(feature = "rustls-ring")] +fn rustls_ring_tls_config() -> rustls::ClientConfig { + use rustls_platform_verifier::BuilderVerifierExt; -#[cfg(all( - not(feature = "native-tls"), - not(feature = "rustls"), - not(feature = "rustls-no-provider"), - feature = "rustls-webpki-roots" -))] -fn default_tls_backend_impl() -> ReqwestTlsBackend { - ReqwestTlsBackend::RustlsWebpkiRoots + rustls::ClientConfig::builder_with_provider(rustls::crypto::ring::default_provider().into()) + .with_safe_default_protocol_versions() + .expect("ring provider must support the default rustls protocol versions") + .with_platform_verifier() + .expect("platform verifier must be available") + .with_no_client_auth() } #[cfg(feature = "rustls-webpki-roots")] From 57f4c676dd26b363e0283df575487686956641c7 Mon Sep 17 00:00:00 2001 From: Erick Guan <297343+erickguan@users.noreply.github.com> Date: Thu, 2 Jul 2026 23:27:47 +0800 Subject: [PATCH 06/22] clarify reqwest tls backend default --- core/http-transports/reqwest/src/lib.rs | 67 +++++++++++++++++-------- 1 file changed, 46 insertions(+), 21 deletions(-) diff --git a/core/http-transports/reqwest/src/lib.rs b/core/http-transports/reqwest/src/lib.rs index 8b46692a2382..c10d537ce30c 100644 --- a/core/http-transports/reqwest/src/lib.rs +++ b/core/http-transports/reqwest/src/lib.rs @@ -116,42 +116,22 @@ compile_error!( /// Each variant corresponds to one of the Cargo features exposed by this /// crate. Building a transport with a variant whose feature is not compiled /// returns [`ErrorKind::ConfigInvalid`]. -#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum ReqwestTlsBackend { /// Platform TLS through reqwest's `native-tls` feature. #[cfg(feature = "native-tls")] - #[default] NativeTls, /// Rustls configured by reqwest with its default crypto provider and platform certificate verification. #[cfg(feature = "rustls")] - #[cfg_attr(not(feature = "native-tls"), default)] Rustls, /// Rustls with the ring crypto provider and platform certificate verification. #[cfg(feature = "rustls-ring")] - #[cfg_attr(all(not(feature = "native-tls"), not(feature = "rustls")), default)] RustlsRing, /// Rustls without a built-in crypto provider through reqwest's `rustls-no-provider` feature. #[cfg(feature = "rustls-no-provider")] - #[cfg_attr( - all( - not(feature = "native-tls"), - not(feature = "rustls"), - not(feature = "rustls-ring") - ), - default - )] RustlsNoProvider, /// Rustls with bundled Mozilla root certificates. #[cfg(feature = "rustls-webpki-roots")] - #[cfg_attr( - all( - not(feature = "native-tls"), - not(feature = "rustls"), - not(feature = "rustls-ring"), - not(feature = "rustls-no-provider") - ), - default - )] RustlsWebpkiRoots, } @@ -173,6 +153,51 @@ impl ReqwestTlsBackend { } } +#[allow(clippy::derivable_impls, clippy::needless_return)] +impl Default for ReqwestTlsBackend { + fn default() -> Self { + #[cfg(feature = "native-tls")] + { + return Self::NativeTls; + } + + #[cfg(all(not(feature = "native-tls"), feature = "rustls"))] + { + return Self::Rustls; + } + + #[cfg(all( + not(feature = "native-tls"), + not(feature = "rustls"), + feature = "rustls-ring" + ))] + { + return Self::RustlsRing; + } + + #[cfg(all( + not(feature = "native-tls"), + not(feature = "rustls"), + not(feature = "rustls-ring"), + feature = "rustls-no-provider" + ))] + { + return Self::RustlsNoProvider; + } + + #[cfg(all( + not(feature = "native-tls"), + not(feature = "rustls"), + not(feature = "rustls-ring"), + not(feature = "rustls-no-provider"), + feature = "rustls-webpki-roots" + ))] + { + Self::RustlsWebpkiRoots + } + } +} + impl Display for ReqwestTlsBackend { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.write_str(self.as_str()) From 76cc17deeec2346b31023638c549d34300fc1f23 Mon Sep 17 00:00:00 2001 From: Erick Guan <297343+erickguan@users.noreply.github.com> Date: Thu, 2 Jul 2026 23:35:48 +0800 Subject: [PATCH 07/22] Update comment --- core/http-transports/reqwest/src/lib.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/core/http-transports/reqwest/src/lib.rs b/core/http-transports/reqwest/src/lib.rs index c10d537ce30c..d3cd38f3e6cd 100644 --- a/core/http-transports/reqwest/src/lib.rs +++ b/core/http-transports/reqwest/src/lib.rs @@ -36,8 +36,8 @@ //! //! - **`rustls-no-provider`** — Rustls without a built-in crypto provider. //! You must install a [`rustls::crypto::CryptoProvider`] before building a -//! client. Use this when you want to bring your own provider (e.g. `ring` -//! or a FIPS-certified module). +//! client. Use this when you want to bring your own provider +//! (e.g., a FIPS-certified module). //! //! - **`rustls-webpki-roots`** — Rustls with bundled //! [Mozilla root certificates](https://crates.io/crates/webpki-roots). @@ -46,9 +46,10 @@ //! Good for reproducible builds and environments with a stripped-down root store. //! But you can't update root certificates without rebuilding opendal. //! -//! In application builds, prefer selecting a single backend. In workspace or -//! `--all-features` builds, Cargo may enable multiple backend features via -//! feature unification; the transport picks native TLS when available. +//! In application or language binding builds, prefer selecting a single backend. +//! e.g., native-tls. In workspace or `--all-features` builds, Cargo may enable +//! multiple backend features via feature unification; Users can set a transport +//! by calling a builder to pick desired backend. //! //! # Builder example //! From 1d887a0da3d41f4d179869cf72e7c071ff3c9cba Mon Sep 17 00:00:00 2001 From: Erick Guan <297343+erickguan@users.noreply.github.com> Date: Fri, 3 Jul 2026 01:26:38 +0800 Subject: [PATCH 08/22] simplify reqwest tls backend selection --- core/http-transports/reqwest/README.md | 4 +- core/http-transports/reqwest/src/lib.rs | 246 ++++++++---------------- core/src/lib.rs | 4 +- 3 files changed, 80 insertions(+), 174 deletions(-) diff --git a/core/http-transports/reqwest/README.md b/core/http-transports/reqwest/README.md index 12b6a220e7c4..ef0c50839aac 100644 --- a/core/http-transports/reqwest/README.md +++ b/core/http-transports/reqwest/README.md @@ -64,14 +64,12 @@ opendal-http-transport-reqwest = { version = "0.57", default-features = false, f ```rust use std::time::Duration; -use opendal_http_transport_reqwest::ReqwestTlsBackend; use opendal_http_transport_reqwest::ReqwestTransport; use opendal::HttpTransporter; // You can configure reqwest dynamically and select a compiled TLS backend. -let tls_backend = "rustls".parse::().unwrap(); let transport = ReqwestTransport::builder() - .tls_backend(tls_backend) + .tls_backend("rustls") .configure(|builder| builder.connect_timeout(Duration::from_secs(10))) .build() .unwrap(); diff --git a/core/http-transports/reqwest/src/lib.rs b/core/http-transports/reqwest/src/lib.rs index d3cd38f3e6cd..41e9ebc5c8d5 100644 --- a/core/http-transports/reqwest/src/lib.rs +++ b/core/http-transports/reqwest/src/lib.rs @@ -60,12 +60,11 @@ //! use std::time::Duration; //! //! use opendal_core::HttpTransporter; -//! use opendal_http_transport_reqwest::ReqwestTlsBackend; //! use opendal_http_transport_reqwest::ReqwestTransport; //! //! # fn build() -> opendal_core::Result<()> { //! let transport = ReqwestTransport::builder() -//! .tls_backend("rustls".parse::()?) +//! .tls_backend("rustls") //! .configure(|builder| builder.connect_timeout(Duration::from_secs(10))) //! .build()?; //! let _transport = HttpTransporter::new(transport); @@ -76,10 +75,9 @@ //! Building the transport returns [`ErrorKind::ConfigInvalid`] when the chosen //! TLS backend feature was not compiled into this crate. -use std::fmt::{Debug, Display, Formatter}; +use std::fmt::{Debug, Formatter}; use std::future; use std::mem; -use std::str::FromStr; use std::sync::LazyLock; use futures::TryStreamExt; @@ -112,137 +110,13 @@ compile_error!( "At least one reqwest TLS backend feature must be enabled: native-tls, rustls, rustls-ring, rustls-no-provider, rustls-webpki-roots" ); -/// TLS backend options. -/// -/// Each variant corresponds to one of the Cargo features exposed by this -/// crate. Building a transport with a variant whose feature is not compiled -/// returns [`ErrorKind::ConfigInvalid`]. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum ReqwestTlsBackend { - /// Platform TLS through reqwest's `native-tls` feature. - #[cfg(feature = "native-tls")] - NativeTls, - /// Rustls configured by reqwest with its default crypto provider and platform certificate verification. - #[cfg(feature = "rustls")] - Rustls, - /// Rustls with the ring crypto provider and platform certificate verification. - #[cfg(feature = "rustls-ring")] - RustlsRing, - /// Rustls without a built-in crypto provider through reqwest's `rustls-no-provider` feature. - #[cfg(feature = "rustls-no-provider")] - RustlsNoProvider, - /// Rustls with bundled Mozilla root certificates. - #[cfg(feature = "rustls-webpki-roots")] - RustlsWebpkiRoots, -} - -impl ReqwestTlsBackend { - /// Return the stable feature-style name for this TLS backend. - pub fn as_str(self) -> &'static str { - match self { - #[cfg(feature = "native-tls")] - ReqwestTlsBackend::NativeTls => "native-tls", - #[cfg(feature = "rustls")] - ReqwestTlsBackend::Rustls => "rustls", - #[cfg(feature = "rustls-ring")] - ReqwestTlsBackend::RustlsRing => "rustls-ring", - #[cfg(feature = "rustls-no-provider")] - ReqwestTlsBackend::RustlsNoProvider => "rustls-no-provider", - #[cfg(feature = "rustls-webpki-roots")] - ReqwestTlsBackend::RustlsWebpkiRoots => "rustls-webpki-roots", - } - } -} - -#[allow(clippy::derivable_impls, clippy::needless_return)] -impl Default for ReqwestTlsBackend { - fn default() -> Self { - #[cfg(feature = "native-tls")] - { - return Self::NativeTls; - } - - #[cfg(all(not(feature = "native-tls"), feature = "rustls"))] - { - return Self::Rustls; - } - - #[cfg(all( - not(feature = "native-tls"), - not(feature = "rustls"), - feature = "rustls-ring" - ))] - { - return Self::RustlsRing; - } - - #[cfg(all( - not(feature = "native-tls"), - not(feature = "rustls"), - not(feature = "rustls-ring"), - feature = "rustls-no-provider" - ))] - { - return Self::RustlsNoProvider; - } - - #[cfg(all( - not(feature = "native-tls"), - not(feature = "rustls"), - not(feature = "rustls-ring"), - not(feature = "rustls-no-provider"), - feature = "rustls-webpki-roots" - ))] - { - Self::RustlsWebpkiRoots - } - } -} - -impl Display for ReqwestTlsBackend { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_str(self.as_str()) - } -} - -impl FromStr for ReqwestTlsBackend { - type Err = Error; - - fn from_str(value: &str) -> Result { - let value = value.trim(); - - match value { - #[cfg(feature = "native-tls")] - "native-tls" => Ok(Self::NativeTls), - - #[cfg(feature = "rustls")] - "rustls" => Ok(Self::Rustls), - - #[cfg(feature = "rustls-ring")] - "rustls-ring" => Ok(Self::RustlsRing), - - #[cfg(feature = "rustls-no-provider")] - "rustls-no-provider" => Ok(Self::RustlsNoProvider), - - #[cfg(feature = "rustls-webpki-roots")] - "rustls-webpki-roots" => Ok(Self::RustlsWebpkiRoots), - - _ => Err( - Error::new(ErrorKind::ConfigInvalid, "unknown reqwest TLS backend") - .with_operation("ReqwestTlsBackend::from_str") - .with_context("tls_backend", value), - ), - } - } -} - /// Builder for [`ReqwestTransport`]. /// /// This builder enables applications choose an TLS backend at runtime /// while preserving access to reqwest's own [`reqwest::ClientBuilder`] options. pub struct ReqwestTransportBuilder { client_builder: reqwest::ClientBuilder, - tls_backend: ReqwestTlsBackend, + tls_backend: String, } impl Default for ReqwestTransportBuilder { @@ -256,7 +130,7 @@ impl ReqwestTransportBuilder { pub fn new() -> Self { Self { client_builder: reqwest::Client::builder(), - tls_backend: ReqwestTlsBackend::default(), + tls_backend: default_tls_backend().to_string(), } } @@ -264,17 +138,19 @@ impl ReqwestTransportBuilder { pub fn from_client_builder(client_builder: reqwest::ClientBuilder) -> Self { Self { client_builder, - tls_backend: ReqwestTlsBackend::default(), + tls_backend: default_tls_backend().to_string(), } } /// Select the TLS backend to use while building the reqwest client. /// - /// [`Self::build`] and [`Self::build_client`] return - /// [`ErrorKind::ConfigInvalid`] if the matching Cargo feature is not - /// compiled into this crate. - pub fn tls_backend(mut self, tls_backend: ReqwestTlsBackend) -> Self { - self.tls_backend = tls_backend; + /// [`Self::build`] and [`Self::build_client`] return [`ErrorKind::ConfigInvalid`] + /// if the matching Cargo feature is not compiled into this crate. + /// + /// Supported values are `native-tls`, `rustls`, `rustls-ring`, + /// `rustls-no-provider`, and `rustls-webpki-roots`. + pub fn tls_backend(mut self, tls_backend: impl Into) -> Self { + self.tls_backend = tls_backend.into().trim().to_string(); self } @@ -290,7 +166,7 @@ impl ReqwestTransportBuilder { /// Build a [`reqwest::Client`]. pub fn build_client(self) -> Result { let tls_backend = self.tls_backend; - let client_builder = apply_tls_backend(self.client_builder, tls_backend); + let client_builder = apply_tls_backend(self.client_builder, tls_backend.as_str())?; client_builder.build().map_err(|err| { Error::new(ErrorKind::ConfigInvalid, "reqwest client config is invalid") @@ -344,7 +220,7 @@ impl ReqwestTransport { /// Create a new transport from a [`reqwest::ClientBuilder`] and TLS backend. pub fn from_client_builder( client_builder: reqwest::ClientBuilder, - tls_backend: ReqwestTlsBackend, + tls_backend: impl Into, ) -> Result { ReqwestTransportBuilder::from_client_builder(client_builder) .tls_backend(tls_backend) @@ -354,23 +230,54 @@ impl ReqwestTransport { fn apply_tls_backend( client_builder: reqwest::ClientBuilder, - tls_backend: ReqwestTlsBackend, -) -> reqwest::ClientBuilder { + tls_backend: &str, +) -> Result { match tls_backend { #[cfg(feature = "native-tls")] - ReqwestTlsBackend::NativeTls => client_builder.tls_backend_native(), + "native-tls" => Ok(client_builder.tls_backend_native()), #[cfg(feature = "rustls")] - ReqwestTlsBackend::Rustls => client_builder.tls_backend_rustls(), + "rustls" => Ok(client_builder.tls_backend_rustls()), #[cfg(feature = "rustls-ring")] - ReqwestTlsBackend::RustlsRing => { - client_builder.tls_backend_preconfigured(rustls_ring_tls_config()) - } + "rustls-ring" => Ok(client_builder.tls_backend_preconfigured(rustls_ring_tls_config())), #[cfg(feature = "rustls-no-provider")] - ReqwestTlsBackend::RustlsNoProvider => client_builder.tls_backend_rustls(), + "rustls-no-provider" => Ok(client_builder.tls_backend_rustls()), #[cfg(feature = "rustls-webpki-roots")] - ReqwestTlsBackend::RustlsWebpkiRoots => { - client_builder.tls_backend_preconfigured(webpki_roots_tls_config()) + "rustls-webpki-roots" => { + Ok(client_builder.tls_backend_preconfigured(webpki_roots_tls_config())) } + _ => Err( + Error::new(ErrorKind::ConfigInvalid, "unknown reqwest TLS backend") + .with_operation("ReqwestTransportBuilder::tls_backend") + .with_context("tls_backend", tls_backend), + ), + } +} + +#[allow(unreachable_code, clippy::needless_return)] +fn default_tls_backend() -> &'static str { + #[cfg(feature = "native-tls")] + { + return "native-tls"; + } + + #[cfg(feature = "rustls")] + { + return "rustls"; + } + + #[cfg(feature = "rustls-webpki-roots")] + { + return "rustls-webpki-roots"; + } + + #[cfg(feature = "rustls-ring")] + { + return "rustls-ring"; + } + + #[cfg(feature = "rustls-no-provider")] + { + "rustls-no-provider" } } @@ -409,7 +316,7 @@ impl HttpTransport for ReqwestTransport { let (parts, body) = req.into_parts(); - let url = reqwest::Url::from_str(&uri.to_string()).map_err(|err| { + let url = reqwest::Url::parse(&uri.to_string()).map_err(|err| { Error::new(ErrorKind::Unexpected, "request url is invalid") .with_operation("reqwest::fetch") .with_context("url", uri.to_string()) @@ -533,40 +440,43 @@ mod tests { use super::*; #[test] - fn test_tls_backend_from_str_native_tls() { - // native-tls is the default feature, always compiled in tests. - assert_eq!( - "native-tls".parse::().unwrap(), - ReqwestTlsBackend::NativeTls - ); + fn test_tls_backend_accepts_native_tls() { + let transport = ReqwestTransportBuilder::new() + .tls_backend("native-tls") + .build(); + assert!(transport.is_ok()); } #[test] - fn test_tls_backend_from_str_trims_whitespace() { - assert_eq!( - " native-tls ".parse::().unwrap(), - ReqwestTlsBackend::NativeTls - ); + fn test_tls_backend_trims_whitespace() { + let transport = ReqwestTransportBuilder::new() + .tls_backend(" native-tls ") + .build(); + assert!(transport.is_ok()); } #[test] - fn test_tls_backend_from_str_unknown() { - let err = "bogus".parse::().unwrap_err(); + fn test_tls_backend_unknown() { + let err = ReqwestTransportBuilder::new() + .tls_backend("bogus") + .build() + .unwrap_err(); assert_eq!(err.kind(), ErrorKind::ConfigInvalid); } #[test] - fn test_tls_backend_display_roundtrip() { - let backend = ReqwestTlsBackend::NativeTls; - let s = backend.to_string(); - let parsed: ReqwestTlsBackend = s.parse().unwrap(); - assert_eq!(parsed, backend); + fn test_tls_backend_context_on_unknown() { + let err = ReqwestTransportBuilder::new() + .tls_backend("bogus") + .build() + .unwrap_err(); + assert!(err.to_string().contains("tls_backend: bogus")); } #[test] fn test_builder_defaults_to_native_tls() { let builder = ReqwestTransportBuilder::new(); - assert_eq!(builder.tls_backend, ReqwestTlsBackend::NativeTls); + assert_eq!(builder.tls_backend, "native-tls"); } #[test] diff --git a/core/src/lib.rs b/core/src/lib.rs index 1e4c1b0f40c7..a9c2cf82c7d6 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -25,9 +25,7 @@ pub use opendal_core::*; #[cfg(feature = "http-transport-reqwest")] -pub use opendal_http_transport_reqwest::{ - ReqwestTlsBackend, ReqwestTransport, ReqwestTransportBuilder, -}; +pub use opendal_http_transport_reqwest::{ReqwestTransport, ReqwestTransportBuilder}; #[cfg(feature = "tests")] pub extern crate opendal_testkit as tests; From fed18c96e56fa3cfc68d6ddd91104f17ad2f73e6 Mon Sep 17 00:00:00 2001 From: Erick Guan <297343+erickguan@users.noreply.github.com> Date: Fri, 3 Jul 2026 22:58:53 +0800 Subject: [PATCH 09/22] Address issues in feature configurations, dependencies --- core/Cargo.lock | 3 - core/Cargo.toml | 16 +- core/http-transports/reqwest/Cargo.toml | 27 +-- core/http-transports/reqwest/README.md | 38 ++--- core/http-transports/reqwest/src/lib.rs | 208 +++++++++++++----------- 5 files changed, 144 insertions(+), 148 deletions(-) diff --git a/core/Cargo.lock b/core/Cargo.lock index 54e96140bfae..9af901922770 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -6524,9 +6524,6 @@ dependencies = [ "http-body 1.0.1", "opendal-core", "reqwest", - "rustls 0.23.40", - "rustls-platform-verifier", - "webpki-roots 1.0.7", ] [[package]] diff --git a/core/Cargo.toml b/core/Cargo.toml index 5f3f73728ab6..7c8ed8cc8e03 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -101,26 +101,16 @@ http-transport-reqwest-native-tls = [ "http-transport-reqwest", "opendal-http-transport-reqwest/native-tls", ] -# Use reqwest with Rustls, the aws-lc-rs crypto provider, and platform certificate verification. +# Use reqwest with Rustls, with default aws-lc-rs crypto provider. http-transport-reqwest-rustls = [ "http-transport-reqwest", "opendal-http-transport-reqwest/rustls", ] -# Use reqwest with Rustls, the ring crypto provider, and platform certificate verification. -http-transport-reqwest-rustls-ring = [ - "http-transport-reqwest", - "opendal-http-transport-reqwest/rustls-ring", -] # Use reqwest with Rustls and no built-in crypto provider. http-transport-reqwest-rustls-no-provider = [ "http-transport-reqwest", "opendal-http-transport-reqwest/rustls-no-provider", ] -# Use reqwest with Rustls, the aws-lc-rs crypto provider, and bundled Mozilla root certificates. -http-transport-reqwest-webpki-roots = [ - "http-transport-reqwest", - "opendal-http-transport-reqwest/rustls-webpki-roots", -] internal-tokio-rt = ["opendal-core/internal-tokio-rt"] layers-async-backtrace = ["dep:opendal-layer-async-backtrace"] layers-await-tree = ["dep:opendal-layer-await-tree"] @@ -146,6 +136,10 @@ layers-tail-cut = ["dep:opendal-layer-tail-cut"] layers-throttle = ["dep:opendal-layer-throttle"] layers-timeout = ["dep:opendal-layer-timeout"] layers-tracing = ["dep:opendal-layer-tracing"] +# Deprecated since 0.58; Will delete in 0.59. +reqwest-rustls-no-provider-tls = ["http-transport-reqwest-rustls-no-provider"] +# Deprecated since 0.58; Will delete in 0.59. +reqwest-rustls-tls = ["http-transport-reqwest-rustls"] services-aliyun-drive = ["dep:opendal-service-aliyun-drive"] services-alluxio = ["dep:opendal-service-alluxio"] services-azblob = ["dep:opendal-service-azblob"] diff --git a/core/http-transports/reqwest/Cargo.toml b/core/http-transports/reqwest/Cargo.toml index 1af7e888ad1e..ae49cc466cab 100644 --- a/core/http-transports/reqwest/Cargo.toml +++ b/core/http-transports/reqwest/Cargo.toml @@ -36,24 +36,16 @@ default = ["native-tls"] native-tls = ["reqwest/native-tls"] # Use Rustls with reqwest's default crypto provider and platform certificate verification. rustls = ["reqwest/rustls"] -# Use Rustls with the ring crypto provider and platform certificate verification. -rustls-ring = [ - "reqwest/rustls-no-provider", - "dep:rustls", - "rustls/ring", - # rustls-ring builds its own ClientConfig, so it must pull in the - # platform verifier directly instead of relying on reqwest's rustls setup. - "dep:rustls-platform-verifier", -] # Use Rustls without a built-in crypto provider. +# +# `rustls` builds `aws_lc_rs` crypto provider by default. When using other TLS backends, +# consider features like: +# - ring +# - fips +# Also read more about `rustls-native-certs` crate. +# +# To use WebPKI roots, read more about `webpki_roots` crate as well. rustls-no-provider = ["reqwest/rustls-no-provider"] -# Use Rustls with the aws-lc-rs crypto provider and bundled Mozilla root certificates. -rustls-webpki-roots = [ - "reqwest/rustls-no-provider", - "dep:rustls", - "rustls/aws-lc-rs", - "dep:webpki-roots", -] [dependencies] bytes = { workspace = true } @@ -64,6 +56,3 @@ opendal-core = { path = "../../core", version = "0.57.0", default-features = fal reqwest = { version = "0.13.4", features = [ "stream", ], default-features = false } -rustls = { version = "0.23", optional = true, default-features = false } -rustls-platform-verifier = { version = "0.7", optional = true, default-features = false } -webpki-roots = { version = "1", optional = true } diff --git a/core/http-transports/reqwest/README.md b/core/http-transports/reqwest/README.md index ef0c50839aac..9d73600c8353 100644 --- a/core/http-transports/reqwest/README.md +++ b/core/http-transports/reqwest/README.md @@ -12,7 +12,7 @@ When using Rustls, TLS configuration has two independent axes: | Axis | What it decides | Options | |------|----------------|---------| | **Crypto provider** | Who performs the cryptographic operations (key exchange, symmetric ciphers, hashing) | reqwest's default provider, `ring`, or any custom `CryptoProvider` | -| **Certificate verification** | How the server's TLS certificate chain is validated | Platform verifier (default in `rustls`), bundled Mozilla roots (`rustls-webpki-roots`), or custom | +| **Certificate verification** | How the server's TLS certificate chain is validated | Platform verifier (default in `rustls`), bundled Mozilla roots (`webpki-roots`), or custom | The `native-tls` feature sidesteps both axes by delegating everything to the OS TLS library (SChannel / Secure Transport / OpenSSL). @@ -23,9 +23,7 @@ the OS TLS library (SChannel / Secure Transport / OpenSSL). |---------|----------------|-------------------|----------| | `native-tls` (default) | OS library | OS trust store | You want zero Rust-side TLS config | | `rustls` | reqwest default | Platform verifier | Pure-Rust TLS with OS trust store | -| `rustls-ring` | ring | Platform verifier | Rustls with ring and OS trust store | -| `rustls-webpki-roots` | aws-lc-rs | Bundled Mozilla roots | Fully self-contained, no OS dependency | -| `rustls-no-provider` | **you provide** | **you provide** | BYO crypto (ring, FIPS module, etc.) | +| `rustls-no-provider` | **you provide** | **you provide** | BYO crypto (ring, webpki roots, FIPS module, etc.) | ### Usage via the `opendal` facade crate @@ -44,17 +42,14 @@ one you need: opendal = { version = "0.57", default-features = false, features = ["http-transport-reqwest-rustls"] } ``` +When using `rustls-no-provider`, you must provide crypto or TLS crates. + ### Feature usage with `rustls` The `rustls` feature uses reqwest's own Rustls configuration through `ClientBuilder::tls_backend_rustls()`, so settings such as custom root certificates, client identity, SNI, TLS info, and dangerous certificate verification flags should be configured with reqwest's builder methods. -The `rustls-ring` feature builds a `rustls::ClientConfig` directly so it can -select ring without installing a process-global Rustls crypto provider. -The `rustls-webpki-roots` feature still builds a `rustls::ClientConfig` directly -because `webpki-roots` exposes Rustls trust anchors rather than -`reqwest::Certificate` values. ```toml [dependencies] @@ -103,26 +98,21 @@ fn main() { // 1. Configure your crypto provider and certificate roots. let root_store = rustls::RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); - let tls_config = rustls::ClientConfig::builder_with_provider( - rustls::crypto::ring::default_provider().into(), - ) - .with_safe_default_protocol_versions() - .unwrap() - .with_root_certificates(root_store) - .with_no_client_auth(); + rustls::crypto::aws_lc_rs::default_provider().into(), + ) + .with_safe_default_protocol_versions() + .expect("aws-lc-rs provider must support the default rustls protocol versions") + .with_root_certificates(root_store) + .with_no_client_auth(); // 2. Build a reqwest client with your TLS config. - let client = reqwest::Client::builder() + let transport = ReqwestTransport::builder() + .tls_backend("rustls-no-provider") .tls_backend_preconfigured(tls_config) - .connect_timeout(Duration::from_secs(10)) - .pool_max_idle_per_host(20) - .build() - .unwrap(); - - // 3. Wrap it as a ReqwestTransport and attach to an operator. - let transport = HttpTransporter::new(ReqwestTransport::new(client)); + .build(); + // 3. Use in an operator. let op = opendal::Operator::via_iter("s3", [ ("bucket".to_string(), "my-bucket".to_string()), ("region".to_string(), "us-east-1".to_string()), diff --git a/core/http-transports/reqwest/src/lib.rs b/core/http-transports/reqwest/src/lib.rs index 41e9ebc5c8d5..cd58948d2e5d 100644 --- a/core/http-transports/reqwest/src/lib.rs +++ b/core/http-transports/reqwest/src/lib.rs @@ -31,21 +31,11 @@ //! reqwest with its default crypto provider and platform certificate //! verification. Pure-Rust TLS stack, no system TLS dependency. //! -//! - **`rustls-ring`** — Rustls with the ring crypto provider and platform -//! certificate verification. -//! //! - **`rustls-no-provider`** — Rustls without a built-in crypto provider. //! You must install a [`rustls::crypto::CryptoProvider`] before building a //! client. Use this when you want to bring your own provider //! (e.g., a FIPS-certified module). //! -//! - **`rustls-webpki-roots`** — Rustls with bundled -//! [Mozilla root certificates](https://crates.io/crates/webpki-roots). -//! Fully self-contained: no platform certificate store dependency. -//! When opendal compiles, cargo will download webpki and bundle certificates. -//! Good for reproducible builds and environments with a stripped-down root store. -//! But you can't update root certificates without rebuilding opendal. -//! //! In application or language binding builds, prefer selecting a single backend. //! e.g., native-tls. In workspace or `--all-features` builds, Cargo may enable //! multiple backend features via feature unification; Users can set a transport @@ -56,7 +46,7 @@ //! Use [`ReqwestTransport::builder`] when applications need to select a TLS //! backend at runtime while still configuring reqwest-specific options: //! -//! ```no_run +//! ``` //! use std::time::Duration; //! //! use opendal_core::HttpTransporter; @@ -75,6 +65,7 @@ //! Building the transport returns [`ErrorKind::ConfigInvalid`] when the chosen //! TLS backend feature was not compiled into this crate. +use std::any::Any; use std::fmt::{Debug, Formatter}; use std::future; use std::mem; @@ -99,15 +90,14 @@ fn build_default_client() -> reqwest::Client { .build_client() .expect("failed to build default reqwest client") } + #[cfg(not(any( feature = "native-tls", feature = "rustls", - feature = "rustls-ring", feature = "rustls-no-provider", - feature = "rustls-webpki-roots", )))] compile_error!( - "At least one reqwest TLS backend feature must be enabled: native-tls, rustls, rustls-ring, rustls-no-provider, rustls-webpki-roots" + "At least one reqwest TLS backend feature must be enabled: native-tls, rustls, rustls-no-provider" ); /// Builder for [`ReqwestTransport`]. @@ -117,6 +107,34 @@ compile_error!( pub struct ReqwestTransportBuilder { client_builder: reqwest::ClientBuilder, tls_backend: String, + // A preconfigured TLS backend. We don't want to import ruslts as a direct dependency, + // so we rely [`reqwest::ClientBuilder::tls_backend_preconfigured`] API. + // [`reqwest::ClientBuilder::tls_backend_preconfigured`] is not stable. + // For example, users could provide a [`rustls::ClientConfig`]. + tls_backend_preconfigured: Option>, +} + +const DEFAULT_TLS_BACKEND: &str = get_default_tls_backend(); + +const fn get_default_tls_backend() -> &'static str { + // returns early by a deliberate order when enable multiple features + + #[cfg(feature = "native-tls")] + { + return "native-tls"; + } + + #[allow(unreachable_code)] // useful when compiles multiple features + #[cfg(feature = "rustls")] + { + return "rustls"; + } + + #[allow(unreachable_code)] // useful when compiles multiple features + #[cfg(feature = "rustls-no-provider")] + { + return "rustls-no-provider"; + } } impl Default for ReqwestTransportBuilder { @@ -130,7 +148,8 @@ impl ReqwestTransportBuilder { pub fn new() -> Self { Self { client_builder: reqwest::Client::builder(), - tls_backend: default_tls_backend().to_string(), + tls_backend: DEFAULT_TLS_BACKEND.to_string(), + tls_backend_preconfigured: None, } } @@ -138,19 +157,61 @@ impl ReqwestTransportBuilder { pub fn from_client_builder(client_builder: reqwest::ClientBuilder) -> Self { Self { client_builder, - tls_backend: default_tls_backend().to_string(), + tls_backend: DEFAULT_TLS_BACKEND.to_string(), + tls_backend_preconfigured: None, } } - /// Select the TLS backend to use while building the reqwest client. + /// Select a TLS backend to use while building the reqwest client. /// /// [`Self::build`] and [`Self::build_client`] return [`ErrorKind::ConfigInvalid`] /// if the matching Cargo feature is not compiled into this crate. /// - /// Supported values are `native-tls`, `rustls`, `rustls-ring`, - /// `rustls-no-provider`, and `rustls-webpki-roots`. + /// Supported values are: + /// - `native-tls` + /// - `rustls` + /// - `rustls-no-provider` pub fn tls_backend(mut self, tls_backend: impl Into) -> Self { - self.tls_backend = tls_backend.into().trim().to_string(); + self.tls_backend = tls_backend.into(); + self + } + + /// Set a preconfigured TLS backend to use while building the reqwest client. + /// For example, a user-provided [`rustls::ClientConfig`]. + /// + /// # Examples + /// + /// ```ignore + /// fn rustls_ring_tls_config() -> rustls::ClientConfig { + /// use rustls_platform_verifier::BuilderVerifierExt; + /// + /// rustls::ClientConfig::builder_with_provider(rustls::crypto::ring::default_provider().into()) + /// .with_safe_default_protocol_versions() + /// .expect("ring provider must support the default rustls protocol versions") + /// .with_platform_verifier() + /// .expect("platform verifier must be available") + /// .with_no_client_auth() + /// } + /// builder.tls_backend_preconfigured(rustls_ring_tls_config()); + /// ``` + /// + /// ```ignore + /// fn rustls_webpki_roots_tls_config() -> rustls::ClientConfig { + /// let root_store = + /// rustls::RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); + /// + /// rustls::ClientConfig::builder_with_provider( + /// rustls::crypto::aws_lc_rs::default_provider().into(), + /// ) + /// .with_safe_default_protocol_versions() + /// .expect("aws-lc-rs provider must support the default rustls protocol versions") + /// .with_root_certificates(root_store) + /// .with_no_client_auth() + /// } + /// builder.tls_backend_preconfigured(rustls_webpki_roots_tls_config()); + /// ``` + pub fn tls_backend_preconfigured(mut self, tls_backend_preconfigured: impl Any) -> Self { + self.tls_backend_preconfigured = Some(Box::new(tls_backend_preconfigured)); self } @@ -163,15 +224,27 @@ impl ReqwestTransportBuilder { self } - /// Build a [`reqwest::Client`]. - pub fn build_client(self) -> Result { - let tls_backend = self.tls_backend; - let client_builder = apply_tls_backend(self.client_builder, tls_backend.as_str())?; + fn build_client(self) -> Result { + let mut client_builder = apply_tls_backend(self.client_builder, &self.tls_backend)?; + + if self.tls_backend == "rustls-no-provider" { + if let Some(tls_backend_preconfigured) = self.tls_backend_preconfigured { + client_builder = + client_builder.tls_backend_preconfigured(tls_backend_preconfigured); + } else { + return Err(Error::new( + ErrorKind::ConfigInvalid, + "reqwest TLS backend rustls-no-provider requires a preconfigured TLS backend", + ) + .with_operation("ReqwestTransportBuilder::build") + .with_context("tls_backend", self.tls_backend.as_str())); + } + } client_builder.build().map_err(|err| { Error::new(ErrorKind::ConfigInvalid, "reqwest client config is invalid") .with_operation("ReqwestTransportBuilder::build") - .with_context("tls_backend", tls_backend.as_str()) + .with_context("tls_backend", self.tls_backend.as_str()) .set_source(err) }) } @@ -182,7 +255,7 @@ impl ReqwestTransportBuilder { } } -/// A [`reqwest::Client`] backed HTTP transport. +/// A HTTP transport with [`reqwest::Client`]. #[derive(Clone)] pub struct ReqwestTransport { client: reqwest::Client, @@ -237,14 +310,8 @@ fn apply_tls_backend( "native-tls" => Ok(client_builder.tls_backend_native()), #[cfg(feature = "rustls")] "rustls" => Ok(client_builder.tls_backend_rustls()), - #[cfg(feature = "rustls-ring")] - "rustls-ring" => Ok(client_builder.tls_backend_preconfigured(rustls_ring_tls_config())), #[cfg(feature = "rustls-no-provider")] "rustls-no-provider" => Ok(client_builder.tls_backend_rustls()), - #[cfg(feature = "rustls-webpki-roots")] - "rustls-webpki-roots" => { - Ok(client_builder.tls_backend_preconfigured(webpki_roots_tls_config())) - } _ => Err( Error::new(ErrorKind::ConfigInvalid, "unknown reqwest TLS backend") .with_operation("ReqwestTransportBuilder::tls_backend") @@ -253,60 +320,6 @@ fn apply_tls_backend( } } -#[allow(unreachable_code, clippy::needless_return)] -fn default_tls_backend() -> &'static str { - #[cfg(feature = "native-tls")] - { - return "native-tls"; - } - - #[cfg(feature = "rustls")] - { - return "rustls"; - } - - #[cfg(feature = "rustls-webpki-roots")] - { - return "rustls-webpki-roots"; - } - - #[cfg(feature = "rustls-ring")] - { - return "rustls-ring"; - } - - #[cfg(feature = "rustls-no-provider")] - { - "rustls-no-provider" - } -} - -#[cfg(feature = "rustls-ring")] -fn rustls_ring_tls_config() -> rustls::ClientConfig { - use rustls_platform_verifier::BuilderVerifierExt; - - rustls::ClientConfig::builder_with_provider(rustls::crypto::ring::default_provider().into()) - .with_safe_default_protocol_versions() - .expect("ring provider must support the default rustls protocol versions") - .with_platform_verifier() - .expect("platform verifier must be available") - .with_no_client_auth() -} - -#[cfg(feature = "rustls-webpki-roots")] -fn webpki_roots_tls_config() -> rustls::ClientConfig { - let root_store = - rustls::RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); - - rustls::ClientConfig::builder_with_provider( - rustls::crypto::aws_lc_rs::default_provider().into(), - ) - .with_safe_default_protocol_versions() - .expect("aws-lc-rs provider must support the default rustls protocol versions") - .with_root_certificates(root_store) - .with_no_client_auth() -} - impl HttpTransport for ReqwestTransport { async fn fetch(&self, req: Request) -> Result> { // Uri stores all string alike data in `Bytes` which means @@ -439,6 +452,7 @@ impl http_body::Body for HttpBufferBody { mod tests { use super::*; + #[cfg(feature = "native-tls")] #[test] fn test_tls_backend_accepts_native_tls() { let transport = ReqwestTransportBuilder::new() @@ -447,14 +461,26 @@ mod tests { assert!(transport.is_ok()); } + #[cfg(feature = "rustls")] #[test] - fn test_tls_backend_trims_whitespace() { - let transport = ReqwestTransportBuilder::new() - .tls_backend(" native-tls ") - .build(); + fn test_tls_backend_accepts_rustls() { + let transport = ReqwestTransportBuilder::new().tls_backend("rustls").build(); assert!(transport.is_ok()); } + #[cfg(feature = "rustls-no-provider")] + #[test] + fn test_tls_backend_warns_on_unconfigured_rustls() { + let err = ReqwestTransportBuilder::new() + .tls_backend("rustls-no-provider") + .build() + .unwrap_err(); + assert!( + err.to_string() + .contains("rustls-no-provider requires a preconfigured TLS backend") + ); + } + #[test] fn test_tls_backend_unknown() { let err = ReqwestTransportBuilder::new() @@ -470,7 +496,7 @@ mod tests { .tls_backend("bogus") .build() .unwrap_err(); - assert!(err.to_string().contains("tls_backend: bogus")); + assert!(err.to_string().contains("unknown reqwest TLS backend")); } #[test] From 4487b5981e7e2da12cd16b6e98154b0820ef1667 Mon Sep 17 00:00:00 2001 From: Erick Guan <297343+erickguan@users.noreply.github.com> Date: Fri, 3 Jul 2026 23:00:13 +0800 Subject: [PATCH 10/22] Set default transport to rustls --- core/Cargo.toml | 2 +- core/http-transports/reqwest/Cargo.toml | 2 +- core/http-transports/reqwest/README.md | 4 ++-- core/http-transports/reqwest/src/lib.rs | 12 ++++++------ 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/core/Cargo.toml b/core/Cargo.toml index 7c8ed8cc8e03..f6055f35b389 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -86,7 +86,7 @@ auto-register-services = ["dep:ctor"] blocking = ["opendal-core/blocking"] default = [ "auto-register-services", - "http-transport-reqwest-native-tls", + "http-transport-reqwest-rustls", "executors-tokio", "layers-concurrent-limit", "layers-logging", diff --git a/core/http-transports/reqwest/Cargo.toml b/core/http-transports/reqwest/Cargo.toml index ae49cc466cab..687bf02d1838 100644 --- a/core/http-transports/reqwest/Cargo.toml +++ b/core/http-transports/reqwest/Cargo.toml @@ -31,7 +31,7 @@ version = { workspace = true } all-features = true [features] -default = ["native-tls"] +default = ["rustls"] # Use platform TLS via reqwest's native-tls feature. native-tls = ["reqwest/native-tls"] # Use Rustls with reqwest's default crypto provider and platform certificate verification. diff --git a/core/http-transports/reqwest/README.md b/core/http-transports/reqwest/README.md index 9d73600c8353..3ec16c36fa67 100644 --- a/core/http-transports/reqwest/README.md +++ b/core/http-transports/reqwest/README.md @@ -21,9 +21,9 @@ the OS TLS library (SChannel / Secure Transport / OpenSSL). | Feature | Crypto provider | Certificate roots | Use when | |---------|----------------|-------------------|----------| -| `native-tls` (default) | OS library | OS trust store | You want zero Rust-side TLS config | -| `rustls` | reqwest default | Platform verifier | Pure-Rust TLS with OS trust store | +| `rustls` (default) | reqwest default | Platform verifier | Pure-Rust TLS with OS trust store | | `rustls-no-provider` | **you provide** | **you provide** | BYO crypto (ring, webpki roots, FIPS module, etc.) | +| `native-tls` | OS library | OS trust store | You want zero Rust-side TLS config | ### Usage via the `opendal` facade crate diff --git a/core/http-transports/reqwest/src/lib.rs b/core/http-transports/reqwest/src/lib.rs index cd58948d2e5d..1d28b9196d23 100644 --- a/core/http-transports/reqwest/src/lib.rs +++ b/core/http-transports/reqwest/src/lib.rs @@ -119,12 +119,6 @@ const DEFAULT_TLS_BACKEND: &str = get_default_tls_backend(); const fn get_default_tls_backend() -> &'static str { // returns early by a deliberate order when enable multiple features - #[cfg(feature = "native-tls")] - { - return "native-tls"; - } - - #[allow(unreachable_code)] // useful when compiles multiple features #[cfg(feature = "rustls")] { return "rustls"; @@ -135,6 +129,12 @@ const fn get_default_tls_backend() -> &'static str { { return "rustls-no-provider"; } + + #[allow(unreachable_code)] // useful when compiles multiple features + #[cfg(feature = "native-tls")] + { + "native-tls" + } } impl Default for ReqwestTransportBuilder { From 09d5e0cd1f62867985ae8e6b2dd86e80ed0b5074 Mon Sep 17 00:00:00 2001 From: Erick Guan <297343+erickguan@users.noreply.github.com> Date: Fri, 3 Jul 2026 23:12:07 +0800 Subject: [PATCH 11/22] Fix compilation error in CI --- core/http-transports/reqwest/src/lib.rs | 40 +++++++++++-------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/core/http-transports/reqwest/src/lib.rs b/core/http-transports/reqwest/src/lib.rs index 1d28b9196d23..9f671b0d2222 100644 --- a/core/http-transports/reqwest/src/lib.rs +++ b/core/http-transports/reqwest/src/lib.rs @@ -100,6 +100,23 @@ compile_error!( "At least one reqwest TLS backend feature must be enabled: native-tls, rustls, rustls-no-provider" ); +const DEFAULT_TLS_BACKEND: &str = get_default_tls_backend(); + +const fn get_default_tls_backend() -> &'static str { + // returns by a deliberate order when enable multiple features + + let preference = [ + #[cfg(feature = "rustls")] + "rustls", + #[cfg(feature = "rustls-no-provider")] + "rustls-no-provider", + #[cfg(feature = "native-tls")] + "native-tls", + ]; + + preference[0] +} + /// Builder for [`ReqwestTransport`]. /// /// This builder enables applications choose an TLS backend at runtime @@ -114,29 +131,6 @@ pub struct ReqwestTransportBuilder { tls_backend_preconfigured: Option>, } -const DEFAULT_TLS_BACKEND: &str = get_default_tls_backend(); - -const fn get_default_tls_backend() -> &'static str { - // returns early by a deliberate order when enable multiple features - - #[cfg(feature = "rustls")] - { - return "rustls"; - } - - #[allow(unreachable_code)] // useful when compiles multiple features - #[cfg(feature = "rustls-no-provider")] - { - return "rustls-no-provider"; - } - - #[allow(unreachable_code)] // useful when compiles multiple features - #[cfg(feature = "native-tls")] - { - "native-tls" - } -} - impl Default for ReqwestTransportBuilder { fn default() -> Self { Self::new() From dc4aa7e27b0278fd80113b2e216b65051df53c5a Mon Sep 17 00:00:00 2001 From: Erick Guan <297343+erickguan@users.noreply.github.com> Date: Fri, 3 Jul 2026 23:18:25 +0800 Subject: [PATCH 12/22] Remove non TLS backend build --- core/Cargo.toml | 2 -- core/edge/s3_read_on_wasm/Cargo.toml | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/core/Cargo.toml b/core/Cargo.toml index f6055f35b389..2897612b3603 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -94,8 +94,6 @@ default = [ "layers-timeout", ] executors-tokio = ["opendal-core/executors-tokio"] -# Enable the reqwest HTTP transport without selecting a TLS backend. -http-transport-reqwest = ["dep:opendal-http-transport-reqwest"] # Use reqwest with platform TLS via native-tls. http-transport-reqwest-native-tls = [ "http-transport-reqwest", diff --git a/core/edge/s3_read_on_wasm/Cargo.toml b/core/edge/s3_read_on_wasm/Cargo.toml index 7df92f52bfa2..2d89ac0d13f8 100644 --- a/core/edge/s3_read_on_wasm/Cargo.toml +++ b/core/edge/s3_read_on_wasm/Cargo.toml @@ -29,7 +29,7 @@ crate-type = ["cdylib"] [dependencies] opendal = { path = "../..", default-features = false, features = [ - "http-transport-reqwest", + "http-transport-reqwest-rustls", "services-s3", ] } wasm-bindgen = "0.2.89" From b84e44ef2d8e8f678bf429920e02a9cddefdf940 Mon Sep 17 00:00:00 2001 From: Erick Guan <297343+erickguan@users.noreply.github.com> Date: Fri, 3 Jul 2026 23:28:06 +0800 Subject: [PATCH 13/22] fix Cargo.toml --- core/Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/Cargo.toml b/core/Cargo.toml index 2897612b3603..4b2d72fd59b8 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -96,17 +96,17 @@ default = [ executors-tokio = ["opendal-core/executors-tokio"] # Use reqwest with platform TLS via native-tls. http-transport-reqwest-native-tls = [ - "http-transport-reqwest", + "dep:opendal-http-transport-reqwest", "opendal-http-transport-reqwest/native-tls", ] # Use reqwest with Rustls, with default aws-lc-rs crypto provider. http-transport-reqwest-rustls = [ - "http-transport-reqwest", + "dep:opendal-http-transport-reqwest", "opendal-http-transport-reqwest/rustls", ] # Use reqwest with Rustls and no built-in crypto provider. http-transport-reqwest-rustls-no-provider = [ - "http-transport-reqwest", + "dep:opendal-http-transport-reqwest", "opendal-http-transport-reqwest/rustls-no-provider", ] internal-tokio-rt = ["opendal-core/internal-tokio-rt"] From e8dce1c49eac21c4f48c1b36e80d3390754cf4b3 Mon Sep 17 00:00:00 2001 From: Erick Guan <297343+erickguan@users.noreply.github.com> Date: Fri, 3 Jul 2026 23:37:39 +0800 Subject: [PATCH 14/22] Bring back default but point to default feature --- core/Cargo.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/Cargo.toml b/core/Cargo.toml index 4b2d72fd59b8..147a418f1714 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -94,6 +94,8 @@ default = [ "layers-timeout", ] executors-tokio = ["opendal-core/executors-tokio"] +# Default http-transport-reqwest transport +http-transport-reqwest = ["http-transport-reqwest-rustls"] # Use reqwest with platform TLS via native-tls. http-transport-reqwest-native-tls = [ "dep:opendal-http-transport-reqwest", From ce37d8a1e7200cccc056e4adf104fd9dee14f4e5 Mon Sep 17 00:00:00 2001 From: Erick Guan <297343+erickguan@users.noreply.github.com> Date: Sat, 4 Jul 2026 00:12:49 +0800 Subject: [PATCH 15/22] Fix default feature set --- core/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/Cargo.toml b/core/Cargo.toml index 147a418f1714..c4b641f3b0ad 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -86,7 +86,7 @@ auto-register-services = ["dep:ctor"] blocking = ["opendal-core/blocking"] default = [ "auto-register-services", - "http-transport-reqwest-rustls", + "http-transport-reqwest", "executors-tokio", "layers-concurrent-limit", "layers-logging", From 2f82fc68c57182541fdee4aca36629d2d5cb625c Mon Sep 17 00:00:00 2001 From: Erick Guan <297343+erickguan@users.noreply.github.com> Date: Sat, 4 Jul 2026 00:26:25 +0800 Subject: [PATCH 16/22] Fix default feature --- bindings/dotnet/Cargo.toml | 2 +- core/Cargo.toml | 8 +++++--- core/edge/s3_read_on_wasm/Cargo.toml | 2 +- core/http-transports/reqwest/README.md | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/bindings/dotnet/Cargo.toml b/bindings/dotnet/Cargo.toml index 9a46c5f08b92..8255915b76b6 100644 --- a/bindings/dotnet/Cargo.toml +++ b/bindings/dotnet/Cargo.toml @@ -34,7 +34,7 @@ doc = false # this crate won't be published, we always use the local version opendal = { version = ">=0", path = "../../core", features = [ "blocking", - "http-transport-reqwest-rustls", + "http-transport-reqwest", "executors-tokio", # enabled layers diff --git a/core/Cargo.toml b/core/Cargo.toml index c4b641f3b0ad..9ef51744cb39 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -94,7 +94,9 @@ default = [ "layers-timeout", ] executors-tokio = ["opendal-core/executors-tokio"] -# Default http-transport-reqwest transport +# Default http-transport-reqwest transport feature, used for: +# - install default http transport +# - feature gate for various of bindings dependency http-transport-reqwest = ["http-transport-reqwest-rustls"] # Use reqwest with platform TLS via native-tls. http-transport-reqwest-native-tls = [ @@ -137,9 +139,9 @@ layers-throttle = ["dep:opendal-layer-throttle"] layers-timeout = ["dep:opendal-layer-timeout"] layers-tracing = ["dep:opendal-layer-tracing"] # Deprecated since 0.58; Will delete in 0.59. -reqwest-rustls-no-provider-tls = ["http-transport-reqwest-rustls-no-provider"] +reqwest-rustls-no-provider-tls = ["http-transport-reqwest", "http-transport-reqwest-rustls-no-provider"] # Deprecated since 0.58; Will delete in 0.59. -reqwest-rustls-tls = ["http-transport-reqwest-rustls"] +reqwest-rustls-tls = ["http-transport-reqwest", "http-transport-reqwest-rustls"] services-aliyun-drive = ["dep:opendal-service-aliyun-drive"] services-alluxio = ["dep:opendal-service-alluxio"] services-azblob = ["dep:opendal-service-azblob"] diff --git a/core/edge/s3_read_on_wasm/Cargo.toml b/core/edge/s3_read_on_wasm/Cargo.toml index 2d89ac0d13f8..7df92f52bfa2 100644 --- a/core/edge/s3_read_on_wasm/Cargo.toml +++ b/core/edge/s3_read_on_wasm/Cargo.toml @@ -29,7 +29,7 @@ crate-type = ["cdylib"] [dependencies] opendal = { path = "../..", default-features = false, features = [ - "http-transport-reqwest-rustls", + "http-transport-reqwest", "services-s3", ] } wasm-bindgen = "0.2.89" diff --git a/core/http-transports/reqwest/README.md b/core/http-transports/reqwest/README.md index 3ec16c36fa67..b24c5f2d8274 100644 --- a/core/http-transports/reqwest/README.md +++ b/core/http-transports/reqwest/README.md @@ -39,7 +39,7 @@ To select a different TLS backend, disable default features and enable the one you need: ```toml -opendal = { version = "0.57", default-features = false, features = ["http-transport-reqwest-rustls"] } +opendal = { version = "0.57", default-features = false, features = ["http-transport-reqwest-native-tls"] } ``` When using `rustls-no-provider`, you must provide crypto or TLS crates. From 1e6d1e89454f764bb172a7578b6c9122c0219842 Mon Sep 17 00:00:00 2001 From: Erick Guan <297343+erickguan@users.noreply.github.com> Date: Sat, 4 Jul 2026 15:18:42 +0800 Subject: [PATCH 17/22] Simplify reqwest TLS configuration --- core/http-transports/reqwest/README.md | 189 ++++++++++----- core/http-transports/reqwest/src/lib.rs | 290 +++--------------------- core/src/lib.rs | 2 +- 3 files changed, 163 insertions(+), 318 deletions(-) diff --git a/core/http-transports/reqwest/README.md b/core/http-transports/reqwest/README.md index b24c5f2d8274..38cf27ef5dfc 100644 --- a/core/http-transports/reqwest/README.md +++ b/core/http-transports/reqwest/README.md @@ -7,122 +7,185 @@ This crate provides `ReqwestTransport`, an implementation of OpenDAL's ## TLS configuration -When using Rustls, TLS configuration has two independent axes: - -| Axis | What it decides | Options | -|------|----------------|---------| -| **Crypto provider** | Who performs the cryptographic operations (key exchange, symmetric ciphers, hashing) | reqwest's default provider, `ring`, or any custom `CryptoProvider` | -| **Certificate verification** | How the server's TLS certificate chain is validated | Platform verifier (default in `rustls`), bundled Mozilla roots (`webpki-roots`), or custom | - -The `native-tls` feature sidesteps both axes by delegating everything to -the OS TLS library (SChannel / Secure Transport / OpenSSL). +OpenDAL does not add another TLS backend selector on top of reqwest. Pick the +Cargo feature set you need, build a `reqwest::Client` with reqwest's +`ClientBuilder`, and pass that client to `ReqwestTransport::new`. + +The default feature is `rustls`. When `ReqwestTransport::default()` is used on +native targets, this crate explicitly asks reqwest for the Rustls backend if the +`rustls` feature is compiled in. + +If this crate is built with only `rustls-no-provider`, install a Rustls default +crypto provider before using `ReqwestTransport::default()`, or build a +preconfigured `reqwest::Client` yourself and pass it to `ReqwestTransport::new`. + +### Reqwest TLS resolution + +Reqwest has its own TLS resolution rules. For OpenDAL users, the practical +resolution order is: + +- Explicit reqwest client configuration wins. Call + `ClientBuilder::tls_backend_rustls()`, + `ClientBuilder::tls_backend_native()`, or + `ClientBuilder::tls_backend_preconfigured()`, but reqwest documents that API + as semver-unstable because the concrete TLS config type must match reqwest's + dependency versions. +- Otherwise, reqwest's automatic TLS backend selection applies. The reqwest + `default-tls` feature takes precedence if any crate in the dependency tree + enables it. Disable reqwest defaults with `default-features = false` when you + need deterministic TLS features. +- Cargo features are additive. If multiple TLS backends are compiled by feature + unification, use explicit client configuration instead of relying on automatic + selection. + +See reqwest's TLS documentation: + +- [reqwest TLS module](https://docs.rs/reqwest/latest/reqwest/tls/index.html) +- [ClientBuilder::tls_backend_rustls](https://docs.rs/reqwest/latest/reqwest/struct.ClientBuilder.html#method.tls_backend_rustls) +- [ClientBuilder::tls_backend_native](https://docs.rs/reqwest/latest/reqwest/struct.ClientBuilder.html#method.tls_backend_native) +- [ClientBuilder::tls_backend_preconfigured](https://docs.rs/reqwest/latest/reqwest/struct.ClientBuilder.html#method.tls_backend_preconfigured) ### Feature matrix | Feature | Crypto provider | Certificate roots | Use when | -|---------|----------------|-------------------|----------| -| `rustls` (default) | reqwest default | Platform verifier | Pure-Rust TLS with OS trust store | -| `rustls-no-provider` | **you provide** | **you provide** | BYO crypto (ring, webpki roots, FIPS module, etc.) | -| `native-tls` | OS library | OS trust store | You want zero Rust-side TLS config | +|---------|-----------------|-------------------|----------| +| `rustls` (default) | reqwest default | Platform verifier | Pure-Rust TLS with the system trust store | +| `rustls-no-provider` | You provide | You provide | BYO crypto provider, roots, or FIPS module | +| `native-tls` | OS library | OS trust store | You want platform TLS | -### Usage via the `opendal` facade crate +## Cargo features -Most users depend on `opendal` rather than this crate directly. The facade -installs this transport when any `http-transport-reqwest-*` feature is enabled. +Most users depend on the `opendal` facade crate: ```toml -# Default — reqwest transport with native-tls +[dependencies] +# Default: reqwest transport with rustls. opendal = { version = "0.57" } ``` -To select a different TLS backend, disable default features and enable the -one you need: +To select one transport TLS feature explicitly, disable default features: ```toml -opendal = { version = "0.57", default-features = false, features = ["http-transport-reqwest-native-tls"] } +[dependencies] +opendal = { version = "0.57", default-features = false, features = [ + "http-transport-reqwest-rustls", +] } ``` -When using `rustls-no-provider`, you must provide crypto or TLS crates. +## Code snippets -### Feature usage with `rustls` +### Rustls -The `rustls` feature uses reqwest's own Rustls configuration through -`ClientBuilder::tls_backend_rustls()`, so settings such as custom root -certificates, client identity, SNI, TLS info, and dangerous certificate -verification flags should be configured with reqwest's builder methods. +Use reqwest's Rustls backend explicitly when you build the client: ```toml [dependencies] -opendal-http-transport-reqwest = { version = "0.57", default-features = false, features = ["rustls"] } +opendal = { version = "0.57", default-features = false, features = [ + "http-transport-reqwest-rustls", +] } +reqwest = { version = "0.13.4", default-features = false, features = [ + "rustls", + "stream", +] } ``` ```rust use std::time::Duration; -use opendal_http_transport_reqwest::ReqwestTransport; use opendal::HttpTransporter; +use opendal::ReqwestTransport; + +fn build_transport() -> Result> { + let client = reqwest::Client::builder() + .tls_backend_rustls() + .connect_timeout(Duration::from_secs(10)) + .build()?; + + Ok(HttpTransporter::new(ReqwestTransport::new(client))) +} +``` + +### Native TLS + +Use `native-tls` when you want reqwest to delegate TLS to the platform stack: + +```toml +[dependencies] +opendal = { version = "0.57", default-features = false, features = [ + "http-transport-reqwest-native-tls", +] } +reqwest = { version = "0.13.4", default-features = false, features = [ + "native-tls", + "stream", +] } +``` + +```rust +use opendal::HttpTransporter; +use opendal::ReqwestTransport; + +fn build_transport() -> Result> { + let client = reqwest::Client::builder() + .tls_backend_native() + .build()?; -// You can configure reqwest dynamically and select a compiled TLS backend. -let transport = ReqwestTransport::builder() - .tls_backend("rustls") - .configure(|builder| builder.connect_timeout(Duration::from_secs(10))) - .build() - .unwrap(); + Ok(HttpTransporter::new(ReqwestTransport::new(client))) +} ``` -### Bringing your own reqwest client +### Rustls no provider with WebPKI roots -When you need full control over the TLS stack — custom `ClientConfig`, -client certificates, proxy settings, or connection pool tuning — build a -`reqwest::Client` yourself and wrap it: +Use `rustls-no-provider` when the application owns the Rustls provider and +certificate roots. This example uses `ring` for crypto and `webpki-roots` for +Mozilla roots. ```toml [dependencies] opendal = { version = "0.57", default-features = false, features = [ - "services-s3", "http-transport-reqwest-rustls-no-provider", ] } -opendal-http-transport-reqwest = { version = "0.57", default-features = false, features = ["rustls-no-provider"] } -rustls = { version = "0.23", features = ["ring"], default-features = false } +reqwest = { version = "0.13.4", default-features = false, features = [ + "rustls-no-provider", + "stream", +] } +rustls = { version = "0.23", default-features = false, features = ["ring"] } webpki-roots = "1" ``` ```rust -use std::time::Duration; - use opendal::HttpTransporter; -use opendal::OperationContext; -use opendal_http_transport_reqwest::ReqwestTransport; +use opendal::ReqwestTransport; -fn main() { - // 1. Configure your crypto provider and certificate roots. +fn build_transport() -> Result> { let root_store = rustls::RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); + let tls_config = rustls::ClientConfig::builder_with_provider( - rustls::crypto::aws_lc_rs::default_provider().into(), + rustls::crypto::ring::default_provider().into(), ) - .with_safe_default_protocol_versions() - .expect("aws-lc-rs provider must support the default rustls protocol versions") + .with_safe_default_protocol_versions()? .with_root_certificates(root_store) .with_no_client_auth(); - // 2. Build a reqwest client with your TLS config. - let transport = ReqwestTransport::builder() - .tls_backend("rustls-no-provider") + let client = reqwest::Client::builder() .tls_backend_preconfigured(tls_config) - .build(); - - // 3. Use in an operator. - let op = opendal::Operator::via_iter("s3", [ - ("bucket".to_string(), "my-bucket".to_string()), - ("region".to_string(), "us-east-1".to_string()), - ]) - .expect("failed to build operator") - .with_context(OperationContext::new().with_http_transport(transport)); + .build()?; + + Ok(HttpTransporter::new(ReqwestTransport::new(client))) } ``` -This approach gives you complete ownership over TLS and client. +## Wasm targets + +On `wasm32-unknown-unknown` and `wasm32-none`, reqwest switches to its wasm +client implementation. It uses the browser or host Fetch API instead of hyper. +TLS is provided by the browser or host environment, so the effective choice is +the system TLS stack exposed to that environment. + +This means `native-tls`, `rustls`, `rustls-no-provider`, custom root stores, +and custom Rustls crypto providers do not select the TLS implementation for +wasm requests. See [reqwest's WASM documentation](https://docs.rs/reqwest/latest/reqwest/#wasm) +for the target-specific limitations. ## License and Trademarks diff --git a/core/http-transports/reqwest/src/lib.rs b/core/http-transports/reqwest/src/lib.rs index 9f671b0d2222..0741d0491861 100644 --- a/core/http-transports/reqwest/src/lib.rs +++ b/core/http-transports/reqwest/src/lib.rs @@ -19,8 +19,8 @@ //! //! # TLS backends //! -//! Enable one of the following Cargo features to select a TLS backend. -//! The `default` feature enables `native-tls`. +//! Enable one of the following Cargo features to compile reqwest TLS support. +//! The `default` feature enables `rustls`. //! //! - **`native-tls`** — Platform TLS links against the OS TLS library: //! - Windows: SChannel @@ -36,15 +36,17 @@ //! client. Use this when you want to bring your own provider //! (e.g., a FIPS-certified module). //! -//! In application or language binding builds, prefer selecting a single backend. -//! e.g., native-tls. In workspace or `--all-features` builds, Cargo may enable -//! multiple backend features via feature unification; Users can set a transport -//! by calling a builder to pick desired backend. +//! In application or language binding builds, prefer selecting a single backend +//! feature. In workspace or `--all-features` builds, Cargo may enable multiple +//! backend features via feature unification; build a [`reqwest::Client`] with +//! reqwest's TLS configuration methods and pass it to [`ReqwestTransport::new`] +//! to force the backend you want. //! -//! # Builder example +//! On wasm targets, reqwest uses the Fetch API instead of hyper. TLS is provided +//! by the host environment, so native TLS backend features do not select a TLS +//! stack there. //! -//! Use [`ReqwestTransport::builder`] when applications need to select a TLS -//! backend at runtime while still configuring reqwest-specific options: +//! # Custom reqwest client //! //! ``` //! use std::time::Duration; @@ -52,20 +54,17 @@ //! use opendal_core::HttpTransporter; //! use opendal_http_transport_reqwest::ReqwestTransport; //! -//! # fn build() -> opendal_core::Result<()> { -//! let transport = ReqwestTransport::builder() -//! .tls_backend("rustls") -//! .configure(|builder| builder.connect_timeout(Duration::from_secs(10))) +//! # fn build() -> Result<(), Box> { +//! let client = reqwest::Client::builder() +//! .tls_backend_rustls() +//! .connect_timeout(Duration::from_secs(10)) //! .build()?; +//! let transport = ReqwestTransport::new(client); //! let _transport = HttpTransporter::new(transport); //! # Ok(()) //! # } //! ``` -//! -//! Building the transport returns [`ErrorKind::ConfigInvalid`] when the chosen -//! TLS backend feature was not compiled into this crate. -use std::any::Any; use std::fmt::{Debug, Formatter}; use std::future; use std::mem; @@ -86,8 +85,8 @@ use opendal_core::raw::parse_content_length; static DEFAULT_REQWEST_CLIENT: LazyLock = LazyLock::new(build_default_client); fn build_default_client() -> reqwest::Client { - ReqwestTransportBuilder::new() - .build_client() + default_client_builder() + .build() .expect("failed to build default reqwest client") } @@ -100,153 +99,30 @@ compile_error!( "At least one reqwest TLS backend feature must be enabled: native-tls, rustls, rustls-no-provider" ); -const DEFAULT_TLS_BACKEND: &str = get_default_tls_backend(); - -const fn get_default_tls_backend() -> &'static str { - // returns by a deliberate order when enable multiple features - - let preference = [ - #[cfg(feature = "rustls")] - "rustls", - #[cfg(feature = "rustls-no-provider")] - "rustls-no-provider", - #[cfg(feature = "native-tls")] - "native-tls", - ]; - - preference[0] -} - -/// Builder for [`ReqwestTransport`]. -/// -/// This builder enables applications choose an TLS backend at runtime -/// while preserving access to reqwest's own [`reqwest::ClientBuilder`] options. -pub struct ReqwestTransportBuilder { - client_builder: reqwest::ClientBuilder, - tls_backend: String, - // A preconfigured TLS backend. We don't want to import ruslts as a direct dependency, - // so we rely [`reqwest::ClientBuilder::tls_backend_preconfigured`] API. - // [`reqwest::ClientBuilder::tls_backend_preconfigured`] is not stable. - // For example, users could provide a [`rustls::ClientConfig`]. - tls_backend_preconfigured: Option>, -} - -impl Default for ReqwestTransportBuilder { - fn default() -> Self { - Self::new() - } -} - -impl ReqwestTransportBuilder { - /// Create a new builder from [`reqwest::Client::builder`]. - pub fn new() -> Self { - Self { - client_builder: reqwest::Client::builder(), - tls_backend: DEFAULT_TLS_BACKEND.to_string(), - tls_backend_preconfigured: None, - } - } - - /// Create a new builder from an existing [`reqwest::ClientBuilder`]. - pub fn from_client_builder(client_builder: reqwest::ClientBuilder) -> Self { - Self { - client_builder, - tls_backend: DEFAULT_TLS_BACKEND.to_string(), - tls_backend_preconfigured: None, - } - } - - /// Select a TLS backend to use while building the reqwest client. - /// - /// [`Self::build`] and [`Self::build_client`] return [`ErrorKind::ConfigInvalid`] - /// if the matching Cargo feature is not compiled into this crate. - /// - /// Supported values are: - /// - `native-tls` - /// - `rustls` - /// - `rustls-no-provider` - pub fn tls_backend(mut self, tls_backend: impl Into) -> Self { - self.tls_backend = tls_backend.into(); - self - } +#[cfg(not(target_arch = "wasm32"))] +#[allow(clippy::needless_return, unreachable_code)] +fn default_client_builder() -> reqwest::ClientBuilder { + let builder = reqwest::Client::builder(); - /// Set a preconfigured TLS backend to use while building the reqwest client. - /// For example, a user-provided [`rustls::ClientConfig`]. - /// - /// # Examples - /// - /// ```ignore - /// fn rustls_ring_tls_config() -> rustls::ClientConfig { - /// use rustls_platform_verifier::BuilderVerifierExt; - /// - /// rustls::ClientConfig::builder_with_provider(rustls::crypto::ring::default_provider().into()) - /// .with_safe_default_protocol_versions() - /// .expect("ring provider must support the default rustls protocol versions") - /// .with_platform_verifier() - /// .expect("platform verifier must be available") - /// .with_no_client_auth() - /// } - /// builder.tls_backend_preconfigured(rustls_ring_tls_config()); - /// ``` - /// - /// ```ignore - /// fn rustls_webpki_roots_tls_config() -> rustls::ClientConfig { - /// let root_store = - /// rustls::RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); - /// - /// rustls::ClientConfig::builder_with_provider( - /// rustls::crypto::aws_lc_rs::default_provider().into(), - /// ) - /// .with_safe_default_protocol_versions() - /// .expect("aws-lc-rs provider must support the default rustls protocol versions") - /// .with_root_certificates(root_store) - /// .with_no_client_auth() - /// } - /// builder.tls_backend_preconfigured(rustls_webpki_roots_tls_config()); - /// ``` - pub fn tls_backend_preconfigured(mut self, tls_backend_preconfigured: impl Any) -> Self { - self.tls_backend_preconfigured = Some(Box::new(tls_backend_preconfigured)); - self + #[cfg(feature = "rustls")] + { + return builder.tls_backend_rustls(); } - /// Configure the underlying [`reqwest::ClientBuilder`]. - pub fn configure( - mut self, - configure: impl FnOnce(reqwest::ClientBuilder) -> reqwest::ClientBuilder, - ) -> Self { - self.client_builder = configure(self.client_builder); - self + #[cfg(feature = "native-tls")] + { + return builder.tls_backend_native(); } - fn build_client(self) -> Result { - let mut client_builder = apply_tls_backend(self.client_builder, &self.tls_backend)?; - - if self.tls_backend == "rustls-no-provider" { - if let Some(tls_backend_preconfigured) = self.tls_backend_preconfigured { - client_builder = - client_builder.tls_backend_preconfigured(tls_backend_preconfigured); - } else { - return Err(Error::new( - ErrorKind::ConfigInvalid, - "reqwest TLS backend rustls-no-provider requires a preconfigured TLS backend", - ) - .with_operation("ReqwestTransportBuilder::build") - .with_context("tls_backend", self.tls_backend.as_str())); - } - } - - client_builder.build().map_err(|err| { - Error::new(ErrorKind::ConfigInvalid, "reqwest client config is invalid") - .with_operation("ReqwestTransportBuilder::build") - .with_context("tls_backend", self.tls_backend.as_str()) - .set_source(err) - }) + #[cfg(feature = "rustls-no-provider")] + { + return builder.tls_backend_rustls(); } +} - /// Build a [`ReqwestTransport`]. - pub fn build(self) -> Result { - self.build_client().map(ReqwestTransport::new) - } +#[cfg(target_arch = "wasm32")] +fn default_client_builder() -> reqwest::ClientBuilder { + reqwest::Client::builder() } /// A HTTP transport with [`reqwest::Client`]. @@ -274,44 +150,10 @@ impl From for ReqwestTransport { } impl ReqwestTransport { - /// Create a builder for [`ReqwestTransport`]. - pub fn builder() -> ReqwestTransportBuilder { - ReqwestTransportBuilder::new() - } - /// Create a new transport from a [`reqwest::Client`]. pub fn new(client: reqwest::Client) -> Self { Self { client } } - - /// Create a new transport from a [`reqwest::ClientBuilder`] and TLS backend. - pub fn from_client_builder( - client_builder: reqwest::ClientBuilder, - tls_backend: impl Into, - ) -> Result { - ReqwestTransportBuilder::from_client_builder(client_builder) - .tls_backend(tls_backend) - .build() - } -} - -fn apply_tls_backend( - client_builder: reqwest::ClientBuilder, - tls_backend: &str, -) -> Result { - match tls_backend { - #[cfg(feature = "native-tls")] - "native-tls" => Ok(client_builder.tls_backend_native()), - #[cfg(feature = "rustls")] - "rustls" => Ok(client_builder.tls_backend_rustls()), - #[cfg(feature = "rustls-no-provider")] - "rustls-no-provider" => Ok(client_builder.tls_backend_rustls()), - _ => Err( - Error::new(ErrorKind::ConfigInvalid, "unknown reqwest TLS backend") - .with_operation("ReqwestTransportBuilder::tls_backend") - .with_context("tls_backend", tls_backend), - ), - } } impl HttpTransport for ReqwestTransport { @@ -446,67 +288,7 @@ impl http_body::Body for HttpBufferBody { mod tests { use super::*; - #[cfg(feature = "native-tls")] - #[test] - fn test_tls_backend_accepts_native_tls() { - let transport = ReqwestTransportBuilder::new() - .tls_backend("native-tls") - .build(); - assert!(transport.is_ok()); - } - - #[cfg(feature = "rustls")] - #[test] - fn test_tls_backend_accepts_rustls() { - let transport = ReqwestTransportBuilder::new().tls_backend("rustls").build(); - assert!(transport.is_ok()); - } - - #[cfg(feature = "rustls-no-provider")] - #[test] - fn test_tls_backend_warns_on_unconfigured_rustls() { - let err = ReqwestTransportBuilder::new() - .tls_backend("rustls-no-provider") - .build() - .unwrap_err(); - assert!( - err.to_string() - .contains("rustls-no-provider requires a preconfigured TLS backend") - ); - } - - #[test] - fn test_tls_backend_unknown() { - let err = ReqwestTransportBuilder::new() - .tls_backend("bogus") - .build() - .unwrap_err(); - assert_eq!(err.kind(), ErrorKind::ConfigInvalid); - } - - #[test] - fn test_tls_backend_context_on_unknown() { - let err = ReqwestTransportBuilder::new() - .tls_backend("bogus") - .build() - .unwrap_err(); - assert!(err.to_string().contains("unknown reqwest TLS backend")); - } - - #[test] - fn test_builder_defaults_to_native_tls() { - let builder = ReqwestTransportBuilder::new(); - assert_eq!(builder.tls_backend, "native-tls"); - } - - #[test] - fn test_builder_configure() { - let transport = ReqwestTransportBuilder::new() - .configure(|b| b.connect_timeout(std::time::Duration::from_secs(5))) - .build(); - assert!(transport.is_ok()); - } - + #[cfg(any(feature = "rustls", feature = "native-tls"))] #[test] fn test_default_transport_succeeds() { let transport = ReqwestTransport::default(); diff --git a/core/src/lib.rs b/core/src/lib.rs index a9c2cf82c7d6..549699b1303d 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -25,7 +25,7 @@ pub use opendal_core::*; #[cfg(feature = "http-transport-reqwest")] -pub use opendal_http_transport_reqwest::{ReqwestTransport, ReqwestTransportBuilder}; +pub use opendal_http_transport_reqwest::ReqwestTransport; #[cfg(feature = "tests")] pub extern crate opendal_testkit as tests; From eab6d50083eaa9b8baa889af6fa5917349a82f2f Mon Sep 17 00:00:00 2001 From: Erick Guan <297343+erickguan@users.noreply.github.com> Date: Sat, 4 Jul 2026 16:09:00 +0800 Subject: [PATCH 18/22] Document reqwest webpki roots TLS setup --- core/http-transports/reqwest/README.md | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/core/http-transports/reqwest/README.md b/core/http-transports/reqwest/README.md index 38cf27ef5dfc..86ffc0e8bdce 100644 --- a/core/http-transports/reqwest/README.md +++ b/core/http-transports/reqwest/README.md @@ -133,11 +133,12 @@ fn build_transport() -> Result> { } ``` -### Rustls no provider with WebPKI roots +### rustls-no-provider + webpki-roots Use `rustls-no-provider` when the application owns the Rustls provider and -certificate roots. This example uses `ring` for crypto and `webpki-roots` for -Mozilla roots. +certificate roots. This example only configures Mozilla roots from +`webpki-roots`; install or configure the Rustls crypto provider in application +startup. ```toml [dependencies] @@ -148,7 +149,7 @@ reqwest = { version = "0.13.4", default-features = false, features = [ "rustls-no-provider", "stream", ] } -rustls = { version = "0.23", default-features = false, features = ["ring"] } +rustls = { version = "0.23", default-features = false, features = ["std"] } webpki-roots = "1" ``` @@ -160,10 +161,7 @@ fn build_transport() -> Result> { let root_store = rustls::RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); - let tls_config = rustls::ClientConfig::builder_with_provider( - rustls::crypto::ring::default_provider().into(), - ) - .with_safe_default_protocol_versions()? + let tls_config = rustls::ClientConfig::builder() .with_root_certificates(root_store) .with_no_client_auth(); From 9aee9e808d10a0d2622546ace9d0362831a38be2 Mon Sep 17 00:00:00 2001 From: Erick Guan <297343+erickguan@users.noreply.github.com> Date: Sat, 4 Jul 2026 16:29:51 +0800 Subject: [PATCH 19/22] Prettify comment --- core/http-transports/reqwest/README.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/core/http-transports/reqwest/README.md b/core/http-transports/reqwest/README.md index 86ffc0e8bdce..ca312a970935 100644 --- a/core/http-transports/reqwest/README.md +++ b/core/http-transports/reqwest/README.md @@ -72,7 +72,7 @@ opendal = { version = "0.57", default-features = false, features = [ ] } ``` -## Code snippets +## Build different TLS backends ### Rustls @@ -133,7 +133,9 @@ fn build_transport() -> Result> { } ``` -### rustls-no-provider + webpki-roots +### rustls-no-provider + +When using `wekpki-roots`, you could use `rustls-no-provider`. Use `rustls-no-provider` when the application owns the Rustls provider and certificate roots. This example only configures Mozilla roots from @@ -173,9 +175,11 @@ fn build_transport() -> Result> { } ``` -## Wasm targets +To use `ring` crate, please refer `rustls` documentation. + +## WASM targets -On `wasm32-unknown-unknown` and `wasm32-none`, reqwest switches to its wasm +On `wasm32-unknown-unknown` and `wasm32-none`, reqwest switches to its WASM client implementation. It uses the browser or host Fetch API instead of hyper. TLS is provided by the browser or host environment, so the effective choice is the system TLS stack exposed to that environment. From d615f064bf18789d6036b7c66cbc338854a6bed1 Mon Sep 17 00:00:00 2001 From: Erick Guan <297343+erickguan@users.noreply.github.com> Date: Sat, 4 Jul 2026 16:51:49 +0800 Subject: [PATCH 20/22] Remove opendal_http_transport_reqwest export in core --- core/src/lib.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/core/src/lib.rs b/core/src/lib.rs index 549699b1303d..2457fd2e1f4c 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -24,9 +24,6 @@ pub use opendal_core::*; -#[cfg(feature = "http-transport-reqwest")] -pub use opendal_http_transport_reqwest::ReqwestTransport; - #[cfg(feature = "tests")] pub extern crate opendal_testkit as tests; From c46ac0f64b22fe6a815b713195e632e7b8e1084d Mon Sep 17 00:00:00 2001 From: Erick Guan <297343+erickguan@users.noreply.github.com> Date: Sat, 4 Jul 2026 16:56:21 +0800 Subject: [PATCH 21/22] fix linting --- core/Cargo.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/Cargo.toml b/core/Cargo.toml index 9ef51744cb39..78dc695274e8 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -139,7 +139,10 @@ layers-throttle = ["dep:opendal-layer-throttle"] layers-timeout = ["dep:opendal-layer-timeout"] layers-tracing = ["dep:opendal-layer-tracing"] # Deprecated since 0.58; Will delete in 0.59. -reqwest-rustls-no-provider-tls = ["http-transport-reqwest", "http-transport-reqwest-rustls-no-provider"] +reqwest-rustls-no-provider-tls = [ + "http-transport-reqwest", + "http-transport-reqwest-rustls-no-provider", +] # Deprecated since 0.58; Will delete in 0.59. reqwest-rustls-tls = ["http-transport-reqwest", "http-transport-reqwest-rustls"] services-aliyun-drive = ["dep:opendal-service-aliyun-drive"] From 3eaf1920e70eedc2315dfacc7ec33e4d916ff7d6 Mon Sep 17 00:00:00 2001 From: Erick Guan <297343+erickguan@users.noreply.github.com> Date: Sun, 5 Jul 2026 08:47:33 +0800 Subject: [PATCH 22/22] Remove default builder --- core/http-transports/reqwest/src/lib.rs | 43 +------------------------ 1 file changed, 1 insertion(+), 42 deletions(-) diff --git a/core/http-transports/reqwest/src/lib.rs b/core/http-transports/reqwest/src/lib.rs index 0741d0491861..11a0ea0a1157 100644 --- a/core/http-transports/reqwest/src/lib.rs +++ b/core/http-transports/reqwest/src/lib.rs @@ -82,48 +82,7 @@ use opendal_core::Result; use opendal_core::raw::parse_content_encoding; use opendal_core::raw::parse_content_length; -static DEFAULT_REQWEST_CLIENT: LazyLock = LazyLock::new(build_default_client); - -fn build_default_client() -> reqwest::Client { - default_client_builder() - .build() - .expect("failed to build default reqwest client") -} - -#[cfg(not(any( - feature = "native-tls", - feature = "rustls", - feature = "rustls-no-provider", -)))] -compile_error!( - "At least one reqwest TLS backend feature must be enabled: native-tls, rustls, rustls-no-provider" -); - -#[cfg(not(target_arch = "wasm32"))] -#[allow(clippy::needless_return, unreachable_code)] -fn default_client_builder() -> reqwest::ClientBuilder { - let builder = reqwest::Client::builder(); - - #[cfg(feature = "rustls")] - { - return builder.tls_backend_rustls(); - } - - #[cfg(feature = "native-tls")] - { - return builder.tls_backend_native(); - } - - #[cfg(feature = "rustls-no-provider")] - { - return builder.tls_backend_rustls(); - } -} - -#[cfg(target_arch = "wasm32")] -fn default_client_builder() -> reqwest::ClientBuilder { - reqwest::Client::builder() -} +static DEFAULT_REQWEST_CLIENT: LazyLock = LazyLock::new(reqwest::Client::new); /// A HTTP transport with [`reqwest::Client`]. #[derive(Clone)]