1pub(crate) mod backoff;
23
24#[cfg(not(target_arch = "wasm32"))]
25mod dns;
26#[cfg(not(target_arch = "wasm32"))]
27pub use dns::{DnsError, DnsFuture, DnsResolver};
28
29#[cfg(not(target_arch = "wasm32"))]
30#[cfg(test)]
31pub(crate) mod mock_server;
32
33pub(crate) mod retry;
34
35#[cfg(any(feature = "aws-base", feature = "gcp-base", feature = "azure-base"))]
36pub(crate) mod pagination;
37
38pub(crate) mod get;
39
40#[cfg(any(feature = "aws-base", feature = "gcp-base", feature = "azure-base"))]
41pub(crate) mod list;
42
43#[cfg(any(feature = "aws-base", feature = "gcp-base", feature = "azure-base"))]
44pub(crate) mod token;
45
46pub(crate) mod header;
47
48#[cfg(any(feature = "aws-base", feature = "gcp-base"))]
49pub(crate) mod s3;
50
51pub(crate) mod builder;
52mod http;
53
54#[cfg(any(feature = "aws-base", feature = "gcp-base", feature = "azure-base"))]
55pub(crate) mod parts;
56pub use http::*;
57
58#[cfg(any(feature = "aws-base", feature = "gcp-base", feature = "azure-base"))]
59mod crypto;
60
61#[cfg(any(feature = "aws-base", feature = "gcp-base", feature = "azure-base"))]
62pub use crypto::*;
63
64use ::http::header::{HeaderMap, HeaderValue};
65use async_trait::async_trait;
66use serde::{Deserialize, Serialize};
67use std::collections::HashMap;
68use std::str::FromStr;
69use std::sync::Arc;
70use std::time::Duration;
71
72#[cfg(all(feature = "reqwest", not(target_arch = "wasm32")))]
73use reqwest::{NoProxy, Proxy};
74
75use crate::config::{ConfigValue, fmt_duration};
76use crate::path::Path;
77use crate::{GetOptions, Result};
78
79#[cfg(feature = "reqwest")]
80fn map_client_error(e: reqwest::Error) -> super::Error {
81 super::Error::Generic {
82 store: "HTTP client",
83 source: Box::new(e),
84 }
85}
86
87#[cfg(feature = "reqwest")]
88static DEFAULT_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);
89
90#[derive(PartialEq, Eq, Hash, Clone, Debug, Copy, Deserialize, Serialize)]
92#[non_exhaustive]
93pub enum ClientConfigKey {
94 AllowHttp,
99 AllowInvalidCertificates,
116 NoSystemCertificates,
124 ConnectTimeout,
129 DefaultContentType,
134 Http1Only,
139 Http2KeepAliveInterval,
144 Http2KeepAliveTimeout,
149 Http2KeepAliveWhileIdle,
154 Http2MaxFrameSize,
159 Http2Only,
164 PoolIdleTimeout,
171 PoolMaxIdlePerHost,
176 ProxyUrl,
181 ProxyCaCertificate,
186 ProxyExcludes,
191 RandomizeAddresses,
206 ReadTimeout,
215 Timeout,
223 UserAgent,
228}
229
230impl AsRef<str> for ClientConfigKey {
231 fn as_ref(&self) -> &str {
232 match self {
233 Self::AllowHttp => "allow_http",
234 Self::AllowInvalidCertificates => "allow_invalid_certificates",
235 Self::NoSystemCertificates => "disable_system_certificates",
236 Self::ConnectTimeout => "connect_timeout",
237 Self::DefaultContentType => "default_content_type",
238 Self::Http1Only => "http1_only",
239 Self::Http2Only => "http2_only",
240 Self::Http2KeepAliveInterval => "http2_keep_alive_interval",
241 Self::Http2KeepAliveTimeout => "http2_keep_alive_timeout",
242 Self::Http2KeepAliveWhileIdle => "http2_keep_alive_while_idle",
243 Self::Http2MaxFrameSize => "http2_max_frame_size",
244 Self::PoolIdleTimeout => "pool_idle_timeout",
245 Self::PoolMaxIdlePerHost => "pool_max_idle_per_host",
246 Self::ProxyUrl => "proxy_url",
247 Self::ProxyCaCertificate => "proxy_ca_certificate",
248 Self::ProxyExcludes => "proxy_excludes",
249 Self::RandomizeAddresses => "randomize_addresses",
250 Self::ReadTimeout => "read_timeout",
251 Self::Timeout => "timeout",
252 Self::UserAgent => "user_agent",
253 }
254 }
255}
256
257impl FromStr for ClientConfigKey {
258 type Err = super::Error;
259
260 fn from_str(s: &str) -> Result<Self, Self::Err> {
261 match s {
262 "allow_http" => Ok(Self::AllowHttp),
263 "allow_invalid_certificates" => Ok(Self::AllowInvalidCertificates),
264 "disable_system_certificates" => Ok(Self::NoSystemCertificates),
265 "connect_timeout" => Ok(Self::ConnectTimeout),
266 "default_content_type" => Ok(Self::DefaultContentType),
267 "http1_only" => Ok(Self::Http1Only),
268 "http2_only" => Ok(Self::Http2Only),
269 "http2_keep_alive_interval" => Ok(Self::Http2KeepAliveInterval),
270 "http2_keep_alive_timeout" => Ok(Self::Http2KeepAliveTimeout),
271 "http2_keep_alive_while_idle" => Ok(Self::Http2KeepAliveWhileIdle),
272 "http2_max_frame_size" => Ok(Self::Http2MaxFrameSize),
273 "pool_idle_timeout" => Ok(Self::PoolIdleTimeout),
274 "pool_max_idle_per_host" => Ok(Self::PoolMaxIdlePerHost),
275 "proxy_url" => Ok(Self::ProxyUrl),
276 "proxy_ca_certificate" => Ok(Self::ProxyCaCertificate),
277 "proxy_excludes" => Ok(Self::ProxyExcludes),
278 "randomize_addresses" => Ok(Self::RandomizeAddresses),
279 "read_timeout" => Ok(Self::ReadTimeout),
280 "timeout" => Ok(Self::Timeout),
281 "user_agent" => Ok(Self::UserAgent),
282 _ => Err(super::Error::UnknownConfigurationKey {
283 store: "HTTP",
284 key: s.into(),
285 }),
286 }
287 }
288}
289
290#[derive(Debug, Clone)]
295#[cfg(all(feature = "reqwest", not(target_arch = "wasm32")))]
296pub struct Certificate(reqwest::tls::Certificate);
297
298#[cfg(all(feature = "reqwest", not(target_arch = "wasm32")))]
299impl Certificate {
300 pub fn from_pem(pem: &[u8]) -> Result<Self> {
315 Ok(Self(
316 reqwest::tls::Certificate::from_pem(pem).map_err(map_client_error)?,
317 ))
318 }
319
320 pub fn from_pem_bundle(pem_bundle: &[u8]) -> Result<Vec<Self>> {
326 Ok(reqwest::tls::Certificate::from_pem_bundle(pem_bundle)
327 .map_err(map_client_error)?
328 .into_iter()
329 .map(Self)
330 .collect())
331 }
332
333 pub fn from_der(der: &[u8]) -> Result<Self> {
335 Ok(Self(
336 reqwest::tls::Certificate::from_der(der).map_err(map_client_error)?,
337 ))
338 }
339}
340
341#[derive(Debug, Clone)]
343pub struct ClientOptions {
344 user_agent: Option<ConfigValue<HeaderValue>>,
345 #[cfg(all(feature = "reqwest", not(target_arch = "wasm32")))]
346 root_certificates: Vec<Certificate>,
347 no_system_certificates: ConfigValue<bool>,
348 content_type_map: HashMap<String, String>,
349 default_content_type: Option<String>,
350 default_headers: Option<HeaderMap>,
351 proxy_url: Option<String>,
352 proxy_ca_certificate: Option<String>,
353 proxy_excludes: Option<String>,
354 allow_http: ConfigValue<bool>,
355 allow_invalid_certificates: ConfigValue<bool>,
356 timeout: Option<ConfigValue<Duration>>,
357 connect_timeout: Option<ConfigValue<Duration>>,
358 read_timeout: Option<ConfigValue<Duration>>,
359 pool_idle_timeout: Option<ConfigValue<Duration>>,
360 pool_max_idle_per_host: Option<ConfigValue<usize>>,
361 http2_keep_alive_interval: Option<ConfigValue<Duration>>,
362 http2_keep_alive_timeout: Option<ConfigValue<Duration>>,
363 http2_keep_alive_while_idle: ConfigValue<bool>,
364 http2_max_frame_size: Option<ConfigValue<u32>>,
365 http1_only: ConfigValue<bool>,
366 http2_only: ConfigValue<bool>,
367 randomize_addresses: ConfigValue<bool>,
368 #[cfg(not(target_arch = "wasm32"))]
369 dns_resolver: Option<Arc<dyn DnsResolver>>,
370}
371
372impl Default for ClientOptions {
373 fn default() -> Self {
374 Self {
382 user_agent: None,
383 #[cfg(all(feature = "reqwest", not(target_arch = "wasm32")))]
384 root_certificates: Default::default(),
385 no_system_certificates: false.into(),
386 content_type_map: Default::default(),
387 default_content_type: None,
388 default_headers: None,
389 proxy_url: None,
390 proxy_ca_certificate: None,
391 proxy_excludes: None,
392 allow_http: Default::default(),
393 allow_invalid_certificates: Default::default(),
394 timeout: Some(Duration::from_secs(30).into()),
395 connect_timeout: Some(Duration::from_secs(5).into()),
396 read_timeout: None,
397 pool_idle_timeout: None,
398 pool_max_idle_per_host: None,
399 http2_keep_alive_interval: None,
400 http2_keep_alive_timeout: None,
401 http2_keep_alive_while_idle: Default::default(),
402 http2_max_frame_size: None,
403 http1_only: true.into(),
407 http2_only: Default::default(),
408 randomize_addresses: true.into(),
409 #[cfg(not(target_arch = "wasm32"))]
410 dns_resolver: Default::default(),
411 }
412 }
413}
414
415impl ClientOptions {
416 pub fn new() -> Self {
418 Default::default()
419 }
420
421 pub fn with_config(mut self, key: ClientConfigKey, value: impl Into<String>) -> Self {
423 match key {
424 ClientConfigKey::AllowHttp => self.allow_http.parse(value),
425 ClientConfigKey::AllowInvalidCertificates => {
426 self.allow_invalid_certificates.parse(value)
427 }
428 ClientConfigKey::NoSystemCertificates => self.no_system_certificates.parse(value),
429 ClientConfigKey::ConnectTimeout => {
430 self.connect_timeout = Some(ConfigValue::Deferred(value.into()))
431 }
432 ClientConfigKey::ReadTimeout => {
433 self.read_timeout = Some(ConfigValue::Deferred(value.into()))
434 }
435 ClientConfigKey::DefaultContentType => self.default_content_type = Some(value.into()),
436 ClientConfigKey::Http1Only => self.http1_only.parse(value),
437 ClientConfigKey::Http2Only => self.http2_only.parse(value),
438 ClientConfigKey::Http2KeepAliveInterval => {
439 self.http2_keep_alive_interval = Some(ConfigValue::Deferred(value.into()))
440 }
441 ClientConfigKey::Http2KeepAliveTimeout => {
442 self.http2_keep_alive_timeout = Some(ConfigValue::Deferred(value.into()))
443 }
444 ClientConfigKey::Http2KeepAliveWhileIdle => {
445 self.http2_keep_alive_while_idle.parse(value)
446 }
447 ClientConfigKey::Http2MaxFrameSize => {
448 self.http2_max_frame_size = Some(ConfigValue::Deferred(value.into()))
449 }
450 ClientConfigKey::PoolIdleTimeout => {
451 self.pool_idle_timeout = Some(ConfigValue::Deferred(value.into()))
452 }
453 ClientConfigKey::PoolMaxIdlePerHost => {
454 self.pool_max_idle_per_host = Some(ConfigValue::Deferred(value.into()))
455 }
456 ClientConfigKey::ProxyUrl => self.proxy_url = Some(value.into()),
457 ClientConfigKey::ProxyCaCertificate => self.proxy_ca_certificate = Some(value.into()),
458 ClientConfigKey::ProxyExcludes => self.proxy_excludes = Some(value.into()),
459 ClientConfigKey::RandomizeAddresses => {
460 self.randomize_addresses.parse(value);
461 }
462 ClientConfigKey::Timeout => self.timeout = Some(ConfigValue::Deferred(value.into())),
463 ClientConfigKey::UserAgent => {
464 self.user_agent = Some(ConfigValue::Deferred(value.into()))
465 }
466 }
467 self
468 }
469
470 pub fn get_config_value(&self, key: &ClientConfigKey) -> Option<String> {
472 match key {
473 ClientConfigKey::AllowHttp => Some(self.allow_http.to_string()),
474 ClientConfigKey::AllowInvalidCertificates => {
475 Some(self.allow_invalid_certificates.to_string())
476 }
477 ClientConfigKey::NoSystemCertificates => Some(self.no_system_certificates.to_string()),
478 ClientConfigKey::ConnectTimeout => self.connect_timeout.as_ref().map(fmt_duration),
479 ClientConfigKey::ReadTimeout => self.read_timeout.as_ref().map(fmt_duration),
480 ClientConfigKey::DefaultContentType => self.default_content_type.clone(),
481 ClientConfigKey::Http1Only => Some(self.http1_only.to_string()),
482 ClientConfigKey::Http2KeepAliveInterval => {
483 self.http2_keep_alive_interval.as_ref().map(fmt_duration)
484 }
485 ClientConfigKey::Http2KeepAliveTimeout => {
486 self.http2_keep_alive_timeout.as_ref().map(fmt_duration)
487 }
488 ClientConfigKey::Http2KeepAliveWhileIdle => {
489 Some(self.http2_keep_alive_while_idle.to_string())
490 }
491 ClientConfigKey::Http2MaxFrameSize => {
492 self.http2_max_frame_size.as_ref().map(|v| v.to_string())
493 }
494 ClientConfigKey::Http2Only => Some(self.http2_only.to_string()),
495 ClientConfigKey::PoolIdleTimeout => self.pool_idle_timeout.as_ref().map(fmt_duration),
496 ClientConfigKey::PoolMaxIdlePerHost => {
497 self.pool_max_idle_per_host.as_ref().map(|v| v.to_string())
498 }
499 ClientConfigKey::ProxyUrl => self.proxy_url.clone(),
500 ClientConfigKey::ProxyCaCertificate => self.proxy_ca_certificate.clone(),
501 ClientConfigKey::ProxyExcludes => self.proxy_excludes.clone(),
502 ClientConfigKey::RandomizeAddresses => Some(self.randomize_addresses.to_string()),
503 ClientConfigKey::Timeout => self.timeout.as_ref().map(fmt_duration),
504 ClientConfigKey::UserAgent => self
505 .user_agent
506 .as_ref()
507 .and_then(|v| v.get().ok())
508 .and_then(|v| v.to_str().ok().map(|s| s.to_string())),
509 }
510 }
511
512 pub fn with_user_agent(mut self, agent: HeaderValue) -> Self {
516 self.user_agent = Some(agent.into());
517 self
518 }
519
520 #[cfg(all(feature = "reqwest", not(target_arch = "wasm32")))]
525 pub fn with_root_certificate(mut self, certificate: Certificate) -> Self {
526 self.root_certificates.push(certificate);
527 self
528 }
529
530 pub fn with_default_content_type(mut self, mime: impl Into<String>) -> Self {
532 self.default_content_type = Some(mime.into());
533 self
534 }
535
536 pub fn with_content_type_for_suffix(
538 mut self,
539 extension: impl Into<String>,
540 mime: impl Into<String>,
541 ) -> Self {
542 self.content_type_map.insert(extension.into(), mime.into());
543 self
544 }
545
546 pub fn with_default_headers(mut self, headers: HeaderMap) -> Self {
548 self.default_headers = Some(headers);
549 self
550 }
551
552 pub fn with_allow_http(mut self, allow_http: bool) -> Self {
558 self.allow_http = allow_http.into();
559 self
560 }
561
562 pub fn with_allow_invalid_certificates(mut self, allow_invalid_certificates: bool) -> Self {
580 self.allow_invalid_certificates = allow_invalid_certificates.into();
581 self
582 }
583
584 pub fn with_no_system_certificates(mut self, no_certs: bool) -> Self {
594 self.no_system_certificates = no_certs.into();
595 self
596 }
597
598 pub fn with_http1_only(mut self) -> Self {
608 self.http2_only = false.into();
609 self.http1_only = true.into();
610 self
611 }
612
613 pub fn with_http2_only(mut self) -> Self {
623 self.http1_only = false.into();
624 self.http2_only = true.into();
625 self
626 }
627
628 pub fn with_allow_http2(mut self) -> Self {
638 self.http1_only = false.into();
639 self.http2_only = false.into();
640 self
641 }
642
643 pub fn with_proxy_url(mut self, proxy_url: impl Into<String>) -> Self {
645 self.proxy_url = Some(proxy_url.into());
646 self
647 }
648
649 pub fn with_proxy_ca_certificate(mut self, proxy_ca_certificate: impl Into<String>) -> Self {
651 self.proxy_ca_certificate = Some(proxy_ca_certificate.into());
652 self
653 }
654
655 pub fn with_proxy_excludes(mut self, proxy_excludes: impl Into<String>) -> Self {
657 self.proxy_excludes = Some(proxy_excludes.into());
658 self
659 }
660
661 pub fn with_timeout(mut self, timeout: Duration) -> Self {
677 self.timeout = Some(ConfigValue::Parsed(timeout));
678 self
679 }
680
681 pub fn with_timeout_disabled(mut self) -> Self {
686 self.timeout = None;
687 self
688 }
689
690 pub fn with_connect_timeout(mut self, timeout: Duration) -> Self {
706 self.connect_timeout = Some(ConfigValue::Parsed(timeout));
707 self
708 }
709
710 pub fn with_connect_timeout_disabled(mut self) -> Self {
715 self.connect_timeout = None;
716 self
717 }
718
719 pub fn with_read_timeout(mut self, timeout: Duration) -> Self {
736 self.read_timeout = Some(ConfigValue::Parsed(timeout));
737 self
738 }
739
740 pub fn with_read_timeout_disabled(mut self) -> Self {
745 self.read_timeout = None;
746 self
747 }
748
749 pub fn with_pool_idle_timeout(mut self, timeout: Duration) -> Self {
755 self.pool_idle_timeout = Some(ConfigValue::Parsed(timeout));
756 self
757 }
758
759 pub fn with_pool_max_idle_per_host(mut self, max: usize) -> Self {
763 self.pool_max_idle_per_host = Some(max.into());
764 self
765 }
766
767 pub fn with_http2_keep_alive_interval(mut self, interval: Duration) -> Self {
771 self.http2_keep_alive_interval = Some(ConfigValue::Parsed(interval));
772 self
773 }
774
775 pub fn with_http2_keep_alive_timeout(mut self, interval: Duration) -> Self {
782 self.http2_keep_alive_timeout = Some(ConfigValue::Parsed(interval));
783 self
784 }
785
786 pub fn with_http2_keep_alive_while_idle(mut self) -> Self {
793 self.http2_keep_alive_while_idle = true.into();
794 self
795 }
796
797 pub fn with_http2_max_frame_size(mut self, sz: u32) -> Self {
801 self.http2_max_frame_size = Some(ConfigValue::Parsed(sz));
802 self
803 }
804
805 #[cfg(not(target_arch = "wasm32"))]
820 pub fn with_dns_resolver(mut self, resolver: Arc<dyn DnsResolver>) -> Self {
821 self.dns_resolver = Some(resolver);
822 self
823 }
824
825 #[cfg(not(target_arch = "wasm32"))]
827 pub fn dns_resolver(&self) -> Option<&Arc<dyn DnsResolver>> {
828 self.dns_resolver.as_ref()
829 }
830
831 pub fn get_default_headers(&self) -> Option<&HeaderMap> {
833 self.default_headers.as_ref()
834 }
835
836 pub fn get_content_type(&self, path: &Path) -> Option<&str> {
845 match path.extension() {
846 Some(extension) => match self.content_type_map.get(extension) {
847 Some(ct) => Some(ct.as_str()),
848 None => self.default_content_type.as_deref(),
849 },
850 None => self.default_content_type.as_deref(),
851 }
852 }
853
854 #[cfg(any(feature = "aws-base", feature = "gcp-base", feature = "azure-base"))]
860 pub(crate) fn metadata_options(&self) -> Self {
861 self.clone()
862 .with_allow_http(true)
863 .with_connect_timeout(Duration::from_secs(1))
864 }
865
866 #[cfg(all(feature = "reqwest", not(target_arch = "wasm32")))]
867 pub(crate) fn client(&self) -> Result<reqwest::Client> {
868 let mut builder = reqwest::ClientBuilder::new();
869
870 match &self.user_agent {
871 Some(user_agent) => builder = builder.user_agent(user_agent.get()?),
872 None => builder = builder.user_agent(DEFAULT_USER_AGENT),
873 }
874
875 if let Some(headers) = &self.default_headers {
876 builder = builder.default_headers(headers.clone())
877 }
878
879 if let Some(proxy) = &self.proxy_url {
880 let mut proxy = Proxy::all(proxy).map_err(map_client_error)?;
881
882 if let Some(certificate) = &self.proxy_ca_certificate {
883 let certificate = reqwest::tls::Certificate::from_pem(certificate.as_bytes())
884 .map_err(map_client_error)?;
885
886 builder = builder.tls_certs_merge(std::iter::once(certificate));
887 }
888
889 if let Some(proxy_excludes) = &self.proxy_excludes {
890 let no_proxy = NoProxy::from_string(proxy_excludes);
891
892 proxy = proxy.no_proxy(no_proxy);
893 }
894
895 builder = builder.proxy(proxy);
896 }
897
898 let certs = self
899 .root_certificates
900 .iter()
901 .map(|certificate| certificate.0.clone());
902
903 if self.no_system_certificates.get()? {
904 builder = builder.tls_certs_only(certs);
905 } else {
906 builder = builder.tls_certs_merge(certs);
907 }
908
909 if let Some(timeout) = &self.timeout {
910 builder = builder.timeout(timeout.get()?)
911 }
912
913 if let Some(timeout) = &self.connect_timeout {
914 builder = builder.connect_timeout(timeout.get()?)
915 }
916
917 if let Some(timeout) = &self.read_timeout {
918 builder = builder.read_timeout(timeout.get()?)
919 }
920
921 if let Some(timeout) = &self.pool_idle_timeout {
922 builder = builder.pool_idle_timeout(timeout.get()?)
923 }
924
925 if let Some(max) = &self.pool_max_idle_per_host {
926 builder = builder.pool_max_idle_per_host(max.get()?)
927 }
928
929 if let Some(interval) = &self.http2_keep_alive_interval {
930 builder = builder.http2_keep_alive_interval(interval.get()?)
931 }
932
933 if let Some(interval) = &self.http2_keep_alive_timeout {
934 builder = builder.http2_keep_alive_timeout(interval.get()?)
935 }
936
937 if self.http2_keep_alive_while_idle.get()? {
938 builder = builder.http2_keep_alive_while_idle(true)
939 }
940
941 if let Some(sz) = &self.http2_max_frame_size {
942 builder = builder.http2_max_frame_size(Some(sz.get()?))
943 }
944
945 if self.http1_only.get()? {
946 builder = builder.http1_only()
947 }
948
949 if self.http2_only.get()? {
950 builder = builder.http2_prior_knowledge()
951 }
952
953 if self.allow_invalid_certificates.get()? {
954 builder = builder.danger_accept_invalid_certs(true)
955 }
956
957 builder = builder.no_gzip().no_brotli().no_zstd().no_deflate();
962
963 let resolver: Option<Arc<dyn DnsResolver>> = match &self.dns_resolver {
964 Some(resolver) => Some(Arc::clone(resolver)),
965 None if self.randomize_addresses.get()? => Some(Arc::new(dns::ShuffleResolver)),
966 None => None,
967 };
968 if let Some(resolver) = resolver {
969 builder = builder.dns_resolver(Arc::new(dns::ReqwestResolver(resolver)));
970 }
971
972 builder
973 .https_only(!self.allow_http.get()?)
974 .build()
975 .map_err(map_client_error)
976 }
977
978 #[cfg(all(feature = "reqwest", target_arch = "wasm32", target_os = "unknown"))]
979 pub(crate) fn client(&self) -> Result<reqwest::Client> {
980 let mut builder = reqwest::ClientBuilder::new();
981
982 match &self.user_agent {
983 Some(user_agent) => builder = builder.user_agent(user_agent.get()?),
984 None => builder = builder.user_agent(DEFAULT_USER_AGENT),
985 }
986
987 if let Some(headers) = &self.default_headers {
988 builder = builder.default_headers(headers.clone())
989 }
990
991 builder.build().map_err(map_client_error)
992 }
993}
994
995pub(crate) trait GetOptionsExt {
996 fn with_get_options(self, options: GetOptions) -> Self;
997}
998
999impl GetOptionsExt for HttpRequestBuilder {
1000 fn with_get_options(mut self, options: GetOptions) -> Self {
1001 use hyper::header::*;
1002
1003 let GetOptions {
1004 if_match,
1005 if_none_match,
1006 if_modified_since,
1007 if_unmodified_since,
1008 range,
1009 version: _,
1010 head: _,
1011 extensions,
1012 } = options;
1013
1014 if let Some(range) = range {
1015 self = self.header(RANGE, range.to_string());
1016 }
1017
1018 if let Some(tag) = if_match {
1019 self = self.header(IF_MATCH, tag);
1020 }
1021
1022 if let Some(tag) = if_none_match {
1023 self = self.header(IF_NONE_MATCH, tag);
1024 }
1025
1026 const DATE_FORMAT: &str = "%a, %d %b %Y %H:%M:%S GMT";
1027 if let Some(date) = if_unmodified_since {
1028 self = self.header(IF_UNMODIFIED_SINCE, date.format(DATE_FORMAT).to_string());
1029 }
1030
1031 if let Some(date) = if_modified_since {
1032 self = self.header(IF_MODIFIED_SINCE, date.format(DATE_FORMAT).to_string());
1033 }
1034
1035 self = self.extensions(extensions);
1036
1037 self
1038 }
1039}
1040
1041#[async_trait]
1043pub trait CredentialProvider: std::fmt::Debug + Send + Sync {
1044 type Credential;
1046
1047 async fn get_credential(&self) -> Result<Arc<Self::Credential>>;
1049}
1050
1051#[derive(Debug)]
1053pub struct StaticCredentialProvider<T> {
1054 credential: Arc<T>,
1055}
1056
1057impl<T> StaticCredentialProvider<T> {
1058 pub fn new(credential: T) -> Self {
1060 Self {
1061 credential: Arc::new(credential),
1062 }
1063 }
1064}
1065
1066#[async_trait]
1067impl<T> CredentialProvider for StaticCredentialProvider<T>
1068where
1069 T: std::fmt::Debug + Send + Sync,
1070{
1071 type Credential = T;
1072
1073 async fn get_credential(&self) -> Result<Arc<T>> {
1074 Ok(Arc::clone(&self.credential))
1075 }
1076}
1077
1078#[cfg(any(feature = "aws-base", feature = "azure-base", feature = "gcp-base"))]
1079mod cloud {
1080 use super::*;
1081 use crate::RetryConfig;
1082 use crate::client::token::{TemporaryToken, TokenCache};
1083
1084 #[derive(Debug)]
1086 pub(crate) struct TokenCredentialProvider<T: TokenProvider> {
1087 inner: T,
1088 client: HttpClient,
1089 retry: RetryConfig,
1090 cache: TokenCache<Arc<T::Credential>>,
1091 }
1092
1093 impl<T: TokenProvider> TokenCredentialProvider<T> {
1094 pub(crate) fn new(inner: T, client: HttpClient, retry: RetryConfig) -> Self {
1095 Self {
1096 inner,
1097 client,
1098 retry,
1099 cache: Default::default(),
1100 }
1101 }
1102
1103 #[cfg(any(feature = "aws-base", feature = "gcp-base"))]
1105 pub(crate) fn with_min_ttl(mut self, min_ttl: Duration) -> Self {
1106 self.cache = self.cache.with_min_ttl(min_ttl);
1107 self
1108 }
1109 }
1110
1111 #[async_trait]
1112 impl<T: TokenProvider> CredentialProvider for TokenCredentialProvider<T> {
1113 type Credential = T::Credential;
1114
1115 async fn get_credential(&self) -> Result<Arc<Self::Credential>> {
1116 self.cache
1117 .get_or_insert_with(|| self.inner.fetch_token(&self.client, &self.retry))
1118 .await
1119 }
1120 }
1121
1122 #[async_trait]
1123 pub(crate) trait TokenProvider: std::fmt::Debug + Send + Sync {
1124 type Credential: std::fmt::Debug + Send + Sync;
1125
1126 async fn fetch_token(
1127 &self,
1128 client: &HttpClient,
1129 retry: &RetryConfig,
1130 ) -> Result<TemporaryToken<Arc<Self::Credential>>>;
1131 }
1132}
1133
1134use crate::client::builder::HttpRequestBuilder;
1135#[cfg(any(feature = "aws-base", feature = "azure-base", feature = "gcp-base"))]
1136pub(crate) use cloud::*;
1137
1138#[cfg(test)]
1139mod tests {
1140 use super::*;
1141 use std::collections::HashMap;
1142
1143 #[test]
1144 fn client_test_config_from_map() {
1145 let allow_http = "true".to_string();
1146 let allow_invalid_certificates = "false".to_string();
1147 let connect_timeout = "90 seconds".to_string();
1148 let default_content_type = "object_store:fake_default_content_type".to_string();
1149 let http1_only = "true".to_string();
1150 let http2_only = "false".to_string();
1151 let http2_keep_alive_interval = "90 seconds".to_string();
1152 let http2_keep_alive_timeout = "91 seconds".to_string();
1153 let http2_keep_alive_while_idle = "92 seconds".to_string();
1154 let http2_max_frame_size = "1337".to_string();
1155 let no_system_certificates = "true".to_string();
1156 let pool_idle_timeout = "93 seconds".to_string();
1157 let pool_max_idle_per_host = "94".to_string();
1158 let proxy_url = "https://fake_proxy_url".to_string();
1159 let read_timeout = "45 seconds".to_string();
1160 let timeout = "95 seconds".to_string();
1161 let user_agent = "object_store:fake_user_agent".to_string();
1162
1163 let options = HashMap::from([
1164 ("allow_http", allow_http.clone()),
1165 (
1166 "allow_invalid_certificates",
1167 allow_invalid_certificates.clone(),
1168 ),
1169 ("connect_timeout", connect_timeout.clone()),
1170 ("default_content_type", default_content_type.clone()),
1171 ("http1_only", http1_only.clone()),
1172 ("http2_only", http2_only.clone()),
1173 (
1174 "http2_keep_alive_interval",
1175 http2_keep_alive_interval.clone(),
1176 ),
1177 ("http2_keep_alive_timeout", http2_keep_alive_timeout.clone()),
1178 (
1179 "http2_keep_alive_while_idle",
1180 http2_keep_alive_while_idle.clone(),
1181 ),
1182 ("http2_max_frame_size", http2_max_frame_size.clone()),
1183 (
1184 "disable_system_certificates",
1185 no_system_certificates.clone(),
1186 ),
1187 ("pool_idle_timeout", pool_idle_timeout.clone()),
1188 ("pool_max_idle_per_host", pool_max_idle_per_host.clone()),
1189 ("proxy_url", proxy_url.clone()),
1190 ("read_timeout", read_timeout.clone()),
1191 ("timeout", timeout.clone()),
1192 ("user_agent", user_agent.clone()),
1193 ]);
1194
1195 let builder = options
1196 .into_iter()
1197 .fold(ClientOptions::new(), |builder, (key, value)| {
1198 builder.with_config(key.parse().unwrap(), value)
1199 });
1200
1201 assert_eq!(
1202 builder
1203 .get_config_value(&ClientConfigKey::AllowHttp)
1204 .unwrap(),
1205 allow_http
1206 );
1207 assert_eq!(
1208 builder
1209 .get_config_value(&ClientConfigKey::AllowInvalidCertificates)
1210 .unwrap(),
1211 allow_invalid_certificates
1212 );
1213 assert_eq!(
1214 builder
1215 .get_config_value(&ClientConfigKey::ConnectTimeout)
1216 .unwrap(),
1217 connect_timeout
1218 );
1219 assert_eq!(
1220 builder
1221 .get_config_value(&ClientConfigKey::DefaultContentType)
1222 .unwrap(),
1223 default_content_type
1224 );
1225 assert_eq!(
1226 builder
1227 .get_config_value(&ClientConfigKey::Http1Only)
1228 .unwrap(),
1229 http1_only
1230 );
1231 assert_eq!(
1232 builder
1233 .get_config_value(&ClientConfigKey::Http2Only)
1234 .unwrap(),
1235 http2_only
1236 );
1237 assert_eq!(
1238 builder
1239 .get_config_value(&ClientConfigKey::Http2KeepAliveInterval)
1240 .unwrap(),
1241 http2_keep_alive_interval
1242 );
1243 assert_eq!(
1244 builder
1245 .get_config_value(&ClientConfigKey::Http2KeepAliveTimeout)
1246 .unwrap(),
1247 http2_keep_alive_timeout
1248 );
1249 assert_eq!(
1250 builder
1251 .get_config_value(&ClientConfigKey::Http2KeepAliveWhileIdle)
1252 .unwrap(),
1253 http2_keep_alive_while_idle
1254 );
1255 assert_eq!(
1256 builder
1257 .get_config_value(&ClientConfigKey::Http2MaxFrameSize)
1258 .unwrap(),
1259 http2_max_frame_size
1260 );
1261 assert_eq!(
1262 builder
1263 .get_config_value(&ClientConfigKey::NoSystemCertificates)
1264 .unwrap(),
1265 no_system_certificates
1266 );
1267
1268 assert_eq!(
1269 builder
1270 .get_config_value(&ClientConfigKey::PoolIdleTimeout)
1271 .unwrap(),
1272 pool_idle_timeout
1273 );
1274 assert_eq!(
1275 builder
1276 .get_config_value(&ClientConfigKey::PoolMaxIdlePerHost)
1277 .unwrap(),
1278 pool_max_idle_per_host
1279 );
1280 assert_eq!(
1281 builder
1282 .get_config_value(&ClientConfigKey::ProxyUrl)
1283 .unwrap(),
1284 proxy_url
1285 );
1286 assert_eq!(
1287 builder
1288 .get_config_value(&ClientConfigKey::ReadTimeout)
1289 .unwrap(),
1290 read_timeout
1291 );
1292 assert_eq!(
1293 builder.get_config_value(&ClientConfigKey::Timeout).unwrap(),
1294 timeout
1295 );
1296 assert_eq!(
1297 builder
1298 .get_config_value(&ClientConfigKey::UserAgent)
1299 .unwrap(),
1300 user_agent
1301 );
1302 }
1303
1304 #[tokio::test]
1305 #[cfg(all(feature = "reqwest", not(target_arch = "wasm32")))]
1306 async fn test_custom_dns_resolver() {
1307 use crate::client::mock_server::MockServer;
1308 use std::net::{IpAddr, Ipv4Addr};
1309 use std::sync::atomic::{AtomicUsize, Ordering};
1310
1311 #[derive(Debug, Default)]
1312 struct CountingResolver(AtomicUsize);
1313
1314 impl DnsResolver for CountingResolver {
1315 fn resolve(&self, host: &str) -> DnsFuture {
1316 assert_eq!(host, "localhost");
1317 self.0.fetch_add(1, Ordering::SeqCst);
1318 Box::pin(async { Ok(vec![IpAddr::V4(Ipv4Addr::LOCALHOST)]) })
1319 }
1320 }
1321
1322 let server = MockServer::new().await;
1323 server.push(::http::Response::new("hello".to_string()));
1324
1325 let resolver = Arc::new(CountingResolver::default());
1326 let client = ClientOptions::new()
1327 .with_allow_http(true)
1328 .with_dns_resolver(Arc::clone(&resolver) as Arc<dyn DnsResolver>)
1329 .client()
1330 .unwrap();
1331
1332 let parsed = url::Url::parse(server.url()).unwrap();
1334 let url = format!("http://localhost:{}/", parsed.port().unwrap());
1335 let resp = client.get(url).send().await.unwrap();
1336 assert!(resp.status().is_success());
1337 assert_eq!(resolver.0.load(Ordering::SeqCst), 1);
1338 }
1339}