This is an automated email from the ASF dual-hosted git repository.
Xuanwo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/opendal.git
The following commit(s) were added to refs/heads/main by this push:
new b6cff68be feat(http-transport-reqwest): allow tls options (#7798)
b6cff68be is described below
commit b6cff68be231008699a5c166ca0042189bc796c9
Author: Erick Guan <[email protected]>
AuthorDate: Mon Jul 6 16:04:37 2026 +0800
feat(http-transport-reqwest): allow tls options (#7798)
* feat(http-transport-reqwest): allow tls options
* Update reqwest feature
* fix reqwest tls backend configuration
* fix reqwest rustls webpki feature name
* add reqwest rustls ring backend
* clarify reqwest tls backend default
* Update comment
* simplify reqwest tls backend selection
* Address issues in feature configurations, dependencies
* Set default transport to rustls
* Fix compilation error in CI
* Remove non TLS backend build
* fix Cargo.toml
* Bring back default but point to default feature
* Fix default feature set
* Fix default feature
* Simplify reqwest TLS configuration
* Document reqwest webpki roots TLS setup
* Prettify comment
* Remove opendal_http_transport_reqwest export in core
* fix linting
* Remove default builder
---
bindings/dotnet/Cargo.toml | 2 +-
bindings/java/Cargo.toml | 2 +-
core/Cargo.lock | 19 ++++
core/Cargo.toml | 30 +++--
core/http-transports/reqwest/Cargo.toml | 14 ++-
core/http-transports/reqwest/README.md | 196 ++++++++++++++++++++++++++++++++
core/http-transports/reqwest/src/lib.rs | 81 +++++++++++--
core/services/hf/Cargo.toml | 4 +-
8 files changed, 324 insertions(+), 24 deletions(-)
diff --git a/bindings/dotnet/Cargo.toml b/bindings/dotnet/Cargo.toml
index f1d508d71..9044b08bf 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",
"executors-tokio",
# enabled layers
diff --git a/bindings/java/Cargo.toml b/bindings/java/Cargo.toml
index 9870e3930..f75d42e76 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 1a34d396d..e6e71ce02 100644
--- a/core/Cargo.lock
+++ b/core/Cargo.lock
@@ -4674,6 +4674,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"
@@ -9432,11 +9448,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",
@@ -9447,6 +9465,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 7613f9bed..54691545a 100644
--- a/core/Cargo.toml
+++ b/core/Cargo.toml
@@ -87,7 +87,6 @@ blocking = ["opendal-core/blocking"]
default = [
"auto-register-services",
"http-transport-reqwest",
- "reqwest-rustls-tls",
"executors-tokio",
"layers-concurrent-limit",
"layers-logging",
@@ -95,7 +94,25 @@ default = [
"layers-timeout",
]
executors-tokio = ["opendal-core/executors-tokio"]
-http-transport-reqwest = ["dep:opendal-http-transport-reqwest"]
+# 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 = [
+ "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 = [
+ "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 = [
+ "dep:opendal-http-transport-reqwest",
+ "opendal-http-transport-reqwest/rustls-no-provider",
+]
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 +138,13 @@ 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",
- "opendal-http-transport-reqwest/rustls-no-provider",
-]
-reqwest-rustls-tls = [
- "http-transport-reqwest",
- "opendal-http-transport-reqwest/rustls",
+ "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"]
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 1b72144e2..687bf02d1 100644
--- a/core/http-transports/reqwest/Cargo.toml
+++ b/core/http-transports/reqwest/Cargo.toml
@@ -31,8 +31,20 @@ version = { workspace = true }
all-features = true
[features]
-default = []
+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.
rustls = ["reqwest/rustls"]
+# 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"]
[dependencies]
diff --git a/core/http-transports/reqwest/README.md
b/core/http-transports/reqwest/README.md
new file mode 100644
index 000000000..ca312a970
--- /dev/null
+++ b/core/http-transports/reqwest/README.md
@@ -0,0 +1,196 @@
+# 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
+
+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 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 |
+
+## Cargo features
+
+Most users depend on the `opendal` facade crate:
+
+```toml
+[dependencies]
+# Default: reqwest transport with rustls.
+opendal = { version = "0.57" }
+```
+
+To select one transport TLS feature explicitly, disable default features:
+
+```toml
+[dependencies]
+opendal = { version = "0.57", default-features = false, features = [
+ "http-transport-reqwest-rustls",
+] }
+```
+
+## Build different TLS backends
+
+### Rustls
+
+Use reqwest's Rustls backend explicitly when you build the client:
+
+```toml
+[dependencies]
+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::HttpTransporter;
+use opendal::ReqwestTransport;
+
+fn build_transport() -> Result<HttpTransporter, Box<dyn std::error::Error>> {
+ 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<HttpTransporter, Box<dyn std::error::Error>> {
+ let client = reqwest::Client::builder()
+ .tls_backend_native()
+ .build()?;
+
+ Ok(HttpTransporter::new(ReqwestTransport::new(client)))
+}
+```
+
+### 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
+`webpki-roots`; install or configure the Rustls crypto provider in application
+startup.
+
+```toml
+[dependencies]
+opendal = { version = "0.57", default-features = false, features = [
+ "http-transport-reqwest-rustls-no-provider",
+] }
+reqwest = { version = "0.13.4", default-features = false, features = [
+ "rustls-no-provider",
+ "stream",
+] }
+rustls = { version = "0.23", default-features = false, features = ["std"] }
+webpki-roots = "1"
+```
+
+```rust
+use opendal::HttpTransporter;
+use opendal::ReqwestTransport;
+
+fn build_transport() -> Result<HttpTransporter, Box<dyn std::error::Error>> {
+ let root_store =
+
rustls::RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
+
+ let tls_config = rustls::ClientConfig::builder()
+ .with_root_certificates(root_store)
+ .with_no_client_auth();
+
+ let client = reqwest::Client::builder()
+ .tls_backend_preconfigured(tls_config)
+ .build()?;
+
+ Ok(HttpTransporter::new(ReqwestTransport::new(client)))
+}
+```
+
+To use `ring` crate, please refer `rustls` documentation.
+
+## 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
+
+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 6f7c49b3a..11a0ea0a1 100644
--- a/core/http-transports/reqwest/src/lib.rs
+++ b/core/http-transports/reqwest/src/lib.rs
@@ -16,14 +16,58 @@
// under the License.
//! Reqwest based HTTP transport for Apache OpenDAL.
+//!
+//! # TLS backends
+//!
+//! 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
+//! - macOS: Secure Transport
+//! - Linux: OpenSSL, requires system development packages
+//!
+//! - **`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., a FIPS-certified module).
+//!
+//! 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.
+//!
+//! 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.
+//!
+//! # Custom reqwest client
+//!
+//! ```
+//! use std::time::Duration;
+//!
+//! use opendal_core::HttpTransporter;
+//! use opendal_http_transport_reqwest::ReqwestTransport;
+//!
+//! # fn build() -> Result<(), Box<dyn std::error::Error>> {
+//! 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(())
+//! # }
+//! ```
-#![deny(missing_docs)]
-
-use std::fmt::Debug;
-use std::fmt::Formatter;
+use std::fmt::{Debug, Formatter};
use std::future;
use std::mem;
-use std::str::FromStr;
use std::sync::LazyLock;
use futures::TryStreamExt;
@@ -40,11 +84,7 @@ use opendal_core::raw::parse_content_length;
static DEFAULT_REQWEST_CLIENT: LazyLock<reqwest::Client> =
LazyLock::new(reqwest::Client::new);
-/// A [`reqwest::Client`] backed HTTP transport.
-///
-/// # Notes
-///
-/// Reqwest must be configured with a TLS feature before sending HTTPS
requests.
+/// A HTTP transport with [`reqwest::Client`].
#[derive(Clone)]
pub struct ReqwestTransport {
client: reqwest::Client,
@@ -84,7 +124,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())
@@ -202,3 +242,22 @@ impl http_body::Body for HttpBufferBody {
http_body::SizeHint::with_exact(self.0.len() as u64)
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[cfg(any(feature = "rustls", feature = "native-tls"))]
+ #[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 c159da107..205d0cd7f 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"] }