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;