Skip to content

Commit 370cae7

Browse files
committed
Make reqwest an optional feature
1 parent 50e6a13 commit 370cae7

23 files changed

+137
-93
lines changed

.github/workflows/object_store.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,10 +164,10 @@ jobs:
164164
rustup default stable
165165
166166
- name: Run object_store tests
167-
run: cargo test --features=aws,azure,gcp,http
167+
run: cargo test --features=aws,azure,gcp,http,reqwest
168168

169169
- name: Run object_store tests (AWS native conditional put)
170-
run: cargo test --features=aws
170+
run: cargo test --features=aws,reqwest
171171
env:
172172
AWS_CONDITIONAL_PUT: etag
173173
AWS_COPY_IF_NOT_EXISTS: multipart

object_store/Cargo.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,14 @@ walkdir = { version = "2", optional = true }
4747
base64 = { version = "0.22", default-features = false, features = ["std"], optional = true }
4848
form_urlencoded = { version = "1.2", optional = true }
4949
http = { version = "1.2.0", optional = true }
50+
http-body = { version = "1.0.1", optional = true}
5051
http-body-util = { version = "0.1", optional = true }
5152
httparse = { version = "1.8.0", default-features = false, features = ["std"], optional = true }
5253
hyper = { version = "1.2", default-features = false, optional = true }
5354
md-5 = { version = "0.10.6", default-features = false, optional = true }
5455
quick-xml = { version = "0.37.0", features = ["serialize", "overlapped-lists"], optional = true }
5556
rand = { version = "0.8", default-features = false, features = ["std", "std_rng"], optional = true }
56-
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-native-roots", "http2"], optional = true }
57+
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-native-roots", "http2", "stream"], optional = true }
5758
ring = { version = "0.17", default-features = false, features = ["std"], optional = true }
5859
rustls-pemfile = { version = "2.0", default-features = false, features = ["std"], optional = true }
5960
serde = { version = "1.0", default-features = false, features = ["derive"], optional = true }
@@ -66,7 +67,8 @@ nix = { version = "0.29.0", features = ["fs"] }
6667

6768
[features]
6869
default = ["fs"]
69-
cloud = ["serde", "serde_json", "quick-xml", "hyper", "reqwest", "reqwest/stream", "chrono/serde", "base64", "rand", "ring", "dep:http", "http-body-util", "form_urlencoded", "serde_urlencoded"]
70+
reqwest = ["dep:reqwest", "hyper"]
71+
cloud = ["serde", "serde_json", "quick-xml", "chrono/serde", "base64", "rand", "ring", "dep:http", "http-body", "http-body-util", "form_urlencoded", "serde_urlencoded"]
7072
azure = ["cloud", "httparse"]
7173
fs = ["walkdir"]
7274
gcp = ["cloud", "rustls-pemfile"]

object_store/src/aws/builder.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,14 @@ use crate::aws::{
2323
AmazonS3, AwsCredential, AwsCredentialProvider, Checksum, S3ConditionalPut, S3CopyIfNotExists,
2424
STORE,
2525
};
26-
use crate::client::{HttpConnector, ReqwestConnector, TokenCredentialProvider};
26+
use crate::client::{default_connector, HttpConnector, TokenCredentialProvider};
2727
use crate::config::ConfigValue;
2828
use crate::{ClientConfigKey, ClientOptions, Result, RetryConfig, StaticCredentialProvider};
2929
use base64::prelude::BASE64_STANDARD;
3030
use base64::Engine;
31+
use http::{HeaderMap, HeaderValue};
3132
use itertools::Itertools;
3233
use md5::{Digest, Md5};
33-
use reqwest::header::{HeaderMap, HeaderValue};
3434
use serde::{Deserialize, Serialize};
3535
use std::str::FromStr;
3636
use std::sync::Arc;
@@ -897,9 +897,10 @@ impl AmazonS3Builder {
897897
self.parse_url(&url)?;
898898
}
899899

900-
let http = self
901-
.http_connector
902-
.unwrap_or_else(|| Arc::new(ReqwestConnector::default()));
900+
let http = match self.http_connector {
901+
Some(connector) => connector,
902+
None => default_connector()?,
903+
};
903904

904905
let bucket = self.bucket_name.ok_or(Error::MissingBucketName)?;
905906
let region = self.region.unwrap_or_else(|| "us-east-1".to_string());

object_store/src/aws/credential.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -770,7 +770,7 @@ struct CreateSessionOutput {
770770
credentials: SessionCredentials,
771771
}
772772

773-
#[cfg(test)]
773+
#[cfg(all(test, feature = "reqwest"))]
774774
mod tests {
775775
use super::*;
776776
use crate::client::mock_server::MockServer;

object_store/src/aws/mod.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@
3131
use async_trait::async_trait;
3232
use futures::stream::BoxStream;
3333
use futures::{StreamExt, TryStreamExt};
34-
use reqwest::header::{HeaderName, IF_MATCH, IF_NONE_MATCH};
35-
use reqwest::{Method, StatusCode};
34+
use http::header::{IF_MATCH, IF_NONE_MATCH};
35+
use http::{HeaderName, Method, StatusCode};
3636
use std::{sync::Arc, time::Duration};
3737
use url::Url;
3838

@@ -58,12 +58,16 @@ mod client;
5858
mod credential;
5959
mod dynamo;
6060
mod precondition;
61-
mod resolve;
6261

6362
pub use builder::{AmazonS3Builder, AmazonS3ConfigKey};
6463
pub use checksum::Checksum;
6564
pub use dynamo::DynamoCommit;
6665
pub use precondition::{S3ConditionalPut, S3CopyIfNotExists};
66+
67+
#[cfg(feature = "reqwest")]
68+
mod resolve;
69+
70+
#[cfg(feature = "reqwest")]
6771
pub use resolve::resolve_bucket_region;
6872

6973
/// This struct is used to maintain the URI path encoding
@@ -115,7 +119,7 @@ impl Signer for AmazonS3 {
115119
/// ```
116120
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
117121
/// # use object_store::{aws::AmazonS3Builder, path::Path, signer::Signer};
118-
/// # use reqwest::Method;
122+
/// # use http::Method;
119123
/// # use std::time::Duration;
120124
/// #
121125
/// let region = "us-east-1";

object_store/src/aws/precondition.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ pub enum S3CopyIfNotExists {
4545
/// other than 412.
4646
///
4747
/// Encoded as `header-with-status:<HEADER_NAME>:<HEADER_VALUE>:<STATUS>` ignoring whitespace
48-
HeaderWithStatus(String, String, reqwest::StatusCode),
48+
HeaderWithStatus(String, String, http::StatusCode),
4949
/// Native Amazon S3 supports copy if not exists through a multipart upload
5050
/// where the upload copies an existing object and is completed only if the
5151
/// new object does not already exist.

object_store/src/azure/builder.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use crate::azure::credential::{
2121
ImdsManagedIdentityProvider, WorkloadIdentityOAuthProvider,
2222
};
2323
use crate::azure::{AzureCredential, AzureCredentialProvider, MicrosoftAzure, STORE};
24-
use crate::client::{HttpConnector, ReqwestConnector, TokenCredentialProvider};
24+
use crate::client::{default_connector, HttpConnector, TokenCredentialProvider};
2525
use crate::config::ConfigValue;
2626
use crate::{ClientConfigKey, ClientOptions, Result, RetryConfig, StaticCredentialProvider};
2727
use percent_encoding::percent_decode_str;
@@ -907,9 +907,10 @@ impl MicrosoftAzureBuilder {
907907
Arc::new(StaticCredentialProvider::new(credential))
908908
};
909909

910-
let http = self
911-
.http_connector
912-
.unwrap_or_else(|| Arc::new(ReqwestConnector::default()));
910+
let http = match self.http_connector {
911+
Some(connector) => connector,
912+
None => default_connector()?,
913+
};
913914

914915
let (is_emulator, storage_url, auth, account) = if self.use_emulator.get()? {
915916
let account_name = self

object_store/src/azure/client.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1157,7 +1157,7 @@ pub(crate) struct UserDelegationKey {
11571157
pub value: String,
11581158
}
11591159

1160-
#[cfg(test)]
1160+
#[cfg(all(test, feature = "reqwest"))]
11611161
mod tests {
11621162
use super::*;
11631163
use crate::StaticCredentialProvider;

object_store/src/azure/credential.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1062,7 +1062,7 @@ impl CredentialProvider for AzureCliCredential {
10621062
}
10631063
}
10641064

1065-
#[cfg(test)]
1065+
#[cfg(all(test, feature = "reqwest"))]
10661066
mod tests {
10671067
use futures::executor::block_on;
10681068
use http::{Response, StatusCode};

object_store/src/azure/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ use crate::{
3131
};
3232
use async_trait::async_trait;
3333
use futures::stream::{BoxStream, StreamExt, TryStreamExt};
34-
use reqwest::Method;
34+
use http::Method;
3535
use std::fmt::Debug;
3636
use std::sync::Arc;
3737
use std::time::Duration;
@@ -170,7 +170,7 @@ impl Signer for MicrosoftAzure {
170170
/// ```
171171
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
172172
/// # use object_store::{azure::MicrosoftAzureBuilder, path::Path, signer::Signer};
173-
/// # use reqwest::Method;
173+
/// # use http::Method;
174174
/// # use std::time::Duration;
175175
/// #
176176
/// let azure = MicrosoftAzureBuilder::new()

object_store/src/client/body.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ use crate::{collect_bytes, PutPayload};
2020
use bytes::Bytes;
2121
use futures::stream::BoxStream;
2222
use futures::StreamExt;
23+
use http_body::{Body, Frame, SizeHint};
2324
use http_body_util::combinators::BoxBody;
2425
use http_body_util::{BodyExt, Full};
25-
use hyper::body::{Body, Frame, SizeHint};
2626
use std::pin::Pin;
2727
use std::task::{Context, Poll};
2828

@@ -39,6 +39,7 @@ impl HttpRequestBody {
3939
Self(Inner::Bytes(Bytes::new()))
4040
}
4141

42+
#[cfg(feature = "reqwest")]
4243
pub(crate) fn into_reqwest(self) -> reqwest::Body {
4344
match self.0 {
4445
Inner::Bytes(b) => b.into(),

object_store/src/client/connection.rs

Lines changed: 53 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,9 @@
1717

1818
use crate::client::body::{HttpRequest, HttpResponse};
1919
use crate::client::builder::{HttpRequestBuilder, RequestBuilderError};
20-
use crate::client::HttpResponseBody;
2120
use crate::ClientOptions;
2221
use async_trait::async_trait;
2322
use http::{Method, Uri};
24-
use http_body_util::BodyExt;
2523
use std::error::Error;
2624
use std::sync::Arc;
2725

@@ -83,7 +81,8 @@ impl HttpError {
8381
}
8482
}
8583

86-
pub(crate) fn reqwest(e: reqwest::Error) -> Self {
84+
#[cfg(feature = "reqwest")]
85+
pub(crate) fn reqwest(e: ::reqwest::Error) -> Self {
8786
let mut kind = if e.is_timeout() {
8887
HttpErrorKind::Timeout
8988
} else if e.is_connect() {
@@ -199,39 +198,63 @@ impl HttpClient {
199198
}
200199
}
201200

202-
#[async_trait]
203-
impl HttpService for reqwest::Client {
204-
async fn call(&self, req: HttpRequest) -> Result<HttpResponse, HttpError> {
205-
let (parts, body) = req.into_parts();
206-
207-
let url = parts.uri.to_string().parse().unwrap();
208-
let mut req = reqwest::Request::new(parts.method, url);
209-
*req.headers_mut() = parts.headers;
210-
*req.body_mut() = Some(body.into_reqwest());
211-
212-
let r = self.execute(req).await.map_err(HttpError::reqwest)?;
213-
let res: http::Response<reqwest::Body> = r.into();
214-
let (parts, body) = res.into_parts();
215-
216-
let body = HttpResponseBody::new(body.map_err(HttpError::reqwest));
217-
Ok(HttpResponse::from_parts(parts, body))
218-
}
219-
}
220-
221201
/// A factory for [`HttpClient`]
222202
pub trait HttpConnector: std::fmt::Debug + Send + Sync + 'static {
223203
/// Create a new [`HttpClient`] with the provided [`ClientOptions`]
224204
fn connect(&self, options: &ClientOptions) -> crate::Result<HttpClient>;
225205
}
226206

227-
/// [`HttpConnector`] using [`reqwest::Client`]
228-
#[derive(Debug, Default)]
229-
#[allow(missing_copy_implementations)]
230-
pub struct ReqwestConnector {}
207+
#[cfg(feature = "reqwest")]
208+
pub(crate) fn default_connector() -> crate::Result<Arc<dyn HttpConnector>> {
209+
Ok(Arc::new(ReqwestConnector::default()))
210+
}
211+
212+
#[cfg(not(feature = "reqwest"))]
213+
pub(crate) fn default_connector() -> crate::Result<Arc<dyn HttpConnector>> {
214+
const MSG: &str = "No HTTP Client, either enable reqwest feature or override HttpConnector";
215+
216+
Err(crate::Error::NotSupported {
217+
source: MSG.to_string().into(),
218+
})
219+
}
220+
221+
#[cfg(feature = "reqwest")]
222+
mod reqwest {
223+
use super::*;
224+
use crate::client::HttpResponseBody;
225+
use ::reqwest::{Client, Request};
226+
use http_body_util::BodyExt;
227+
#[async_trait]
228+
impl HttpService for Client {
229+
async fn call(&self, req: HttpRequest) -> Result<HttpResponse, HttpError> {
230+
let (parts, body) = req.into_parts();
231+
232+
let url = parts.uri.to_string().parse().unwrap();
233+
let mut req = Request::new(parts.method, url);
234+
*req.headers_mut() = parts.headers;
235+
*req.body_mut() = Some(body.into_reqwest());
236+
237+
let r = self.execute(req).await.map_err(HttpError::reqwest)?;
238+
let res: http::Response<_> = r.into();
239+
let (parts, body) = res.into_parts();
240+
241+
let body = HttpResponseBody::new(body.map_err(HttpError::reqwest));
242+
Ok(HttpResponse::from_parts(parts, body))
243+
}
244+
}
245+
246+
/// [`HttpConnector`] using [`reqwest::Client`]
247+
#[derive(Debug, Default)]
248+
#[allow(missing_copy_implementations)]
249+
pub struct ReqwestConnector {}
231250

232-
impl HttpConnector for ReqwestConnector {
233-
fn connect(&self, options: &ClientOptions) -> crate::Result<HttpClient> {
234-
let client = options.client()?;
235-
Ok(HttpClient::new(client))
251+
impl HttpConnector for ReqwestConnector {
252+
fn connect(&self, options: &ClientOptions) -> crate::Result<HttpClient> {
253+
let client = options.client()?;
254+
Ok(HttpClient::new(client))
255+
}
236256
}
237257
}
258+
259+
#[cfg(feature = "reqwest")]
260+
pub use reqwest::*;

object_store/src/client/get.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,10 @@ use crate::{Attribute, Attributes, GetOptions, GetRange, GetResult, GetResultPay
2424
use async_trait::async_trait;
2525
use futures::{StreamExt, TryStreamExt};
2626
use http::header::{
27-
CACHE_CONTROL, CONTENT_DISPOSITION, CONTENT_ENCODING, CONTENT_LANGUAGE, CONTENT_RANGE,
28-
CONTENT_TYPE,
27+
ToStrError, CACHE_CONTROL, CONTENT_DISPOSITION, CONTENT_ENCODING, CONTENT_LANGUAGE,
28+
CONTENT_RANGE, CONTENT_TYPE,
2929
};
3030
use http::StatusCode;
31-
use reqwest::header::ToStrError;
3231

3332
/// A client that can perform a get request
3433
#[async_trait]

object_store/src/client/header.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ pub(crate) enum Error {
4949
MissingEtag,
5050

5151
#[error("Received header containing non-ASCII data")]
52-
BadHeader { source: reqwest::header::ToStrError },
52+
BadHeader { source: http::header::ToStrError },
5353

5454
#[error("Last-Modified Header missing from response")]
5555
MissingLastModified,

0 commit comments

Comments
 (0)