1use std::str::FromStr;
2use std::time::Duration;
3
4use reqwest::header::{HeaderMap, HeaderValue};
5use reqwest::{Client, ClientBuilder, NoProxy, Proxy};
6use serde::{Deserialize, Serialize};
7
8use crate::config::*;
9use crate::error::{Error, Result};
10
11static DEFAULT_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);
12
13fn map_client_error(e: reqwest::Error) -> Error {
14 Error::Generic {
15 source: Box::new(e),
16 }
17}
18
19#[derive(PartialEq, Eq, Hash, Clone, Debug, Copy, Deserialize, Serialize)]
21#[non_exhaustive]
22pub enum ClientConfigKey {
23 AllowHttp,
25 AllowInvalidCertificates,
35 ConnectTimeout,
37 Http1Only,
39 Http2KeepAliveInterval,
41 Http2KeepAliveTimeout,
43 Http2KeepAliveWhileIdle,
45 Http2MaxFrameSize,
47 Http2Only,
49 PoolIdleTimeout,
53 PoolMaxIdlePerHost,
55 ProxyUrl,
57 ProxyCaCertificate,
59 ProxyExcludes,
61 Timeout,
66 UserAgent,
68}
69
70impl AsRef<str> for ClientConfigKey {
71 fn as_ref(&self) -> &str {
72 match self {
73 Self::AllowHttp => "allow_http",
74 Self::AllowInvalidCertificates => "allow_invalid_certificates",
75 Self::ConnectTimeout => "connect_timeout",
76 Self::Http1Only => "http1_only",
77 Self::Http2Only => "http2_only",
78 Self::Http2KeepAliveInterval => "http2_keep_alive_interval",
79 Self::Http2KeepAliveTimeout => "http2_keep_alive_timeout",
80 Self::Http2KeepAliveWhileIdle => "http2_keep_alive_while_idle",
81 Self::Http2MaxFrameSize => "http2_max_frame_size",
82 Self::PoolIdleTimeout => "pool_idle_timeout",
83 Self::PoolMaxIdlePerHost => "pool_max_idle_per_host",
84 Self::ProxyUrl => "proxy_url",
85 Self::ProxyCaCertificate => "proxy_ca_certificate",
86 Self::ProxyExcludes => "proxy_excludes",
87 Self::Timeout => "timeout",
88 Self::UserAgent => "user_agent",
89 }
90 }
91}
92
93impl FromStr for ClientConfigKey {
94 type Err = Error;
95
96 fn from_str(s: &str) -> Result<Self, Self::Err> {
97 match s {
98 "allow_http" => Ok(Self::AllowHttp),
99 "allow_invalid_certificates" => Ok(Self::AllowInvalidCertificates),
100 "connect_timeout" => Ok(Self::ConnectTimeout),
101 "http1_only" => Ok(Self::Http1Only),
102 "http2_only" => Ok(Self::Http2Only),
103 "http2_keep_alive_interval" => Ok(Self::Http2KeepAliveInterval),
104 "http2_keep_alive_timeout" => Ok(Self::Http2KeepAliveTimeout),
105 "http2_keep_alive_while_idle" => Ok(Self::Http2KeepAliveWhileIdle),
106 "http2_max_frame_size" => Ok(Self::Http2MaxFrameSize),
107 "pool_idle_timeout" => Ok(Self::PoolIdleTimeout),
108 "pool_max_idle_per_host" => Ok(Self::PoolMaxIdlePerHost),
109 "proxy_url" => Ok(Self::ProxyUrl),
110 "proxy_ca_certificate" => Ok(Self::ProxyCaCertificate),
111 "proxy_excludes" => Ok(Self::ProxyExcludes),
112 "timeout" => Ok(Self::Timeout),
113 "user_agent" => Ok(Self::UserAgent),
114 _ => Err(Error::UnknownConfigurationKey { key: s.into() }),
115 }
116 }
117}
118
119#[derive(Debug, Clone)]
124pub struct Certificate(reqwest::tls::Certificate);
125
126impl Certificate {
127 pub fn from_pem(pem: &[u8]) -> Result<Self> {
142 Ok(Self(
143 reqwest::tls::Certificate::from_pem(pem).map_err(map_client_error)?,
144 ))
145 }
146
147 pub fn from_pem_bundle(pem_bundle: &[u8]) -> Result<Vec<Self>> {
153 Ok(reqwest::tls::Certificate::from_pem_bundle(pem_bundle)
154 .map_err(map_client_error)?
155 .into_iter()
156 .map(Self)
157 .collect())
158 }
159
160 pub fn from_der(der: &[u8]) -> Result<Self> {
162 Ok(Self(
163 reqwest::tls::Certificate::from_der(der).map_err(map_client_error)?,
164 ))
165 }
166}
167
168#[derive(Debug, Clone)]
170pub struct ClientOptions {
171 user_agent: Option<ConfigValue<HeaderValue>>,
172 root_certificates: Vec<Certificate>,
173 default_headers: Option<HeaderMap>,
174 proxy_url: Option<String>,
175 proxy_ca_certificate: Option<String>,
176 proxy_excludes: Option<String>,
177 allow_http: ConfigValue<bool>,
178 allow_insecure: ConfigValue<bool>,
179 timeout: Option<ConfigValue<Duration>>,
180 connect_timeout: Option<ConfigValue<Duration>>,
181 pool_idle_timeout: Option<ConfigValue<Duration>>,
182 pool_max_idle_per_host: Option<ConfigValue<usize>>,
183 http2_keep_alive_interval: Option<ConfigValue<Duration>>,
184 http2_keep_alive_timeout: Option<ConfigValue<Duration>>,
185 http2_keep_alive_while_idle: ConfigValue<bool>,
186 http2_max_frame_size: Option<ConfigValue<u32>>,
187 http1_only: ConfigValue<bool>,
188 http2_only: ConfigValue<bool>,
189}
190
191impl Default for ClientOptions {
192 fn default() -> Self {
193 Self {
201 user_agent: None,
202 root_certificates: Default::default(),
203 default_headers: None,
204 proxy_url: None,
205 proxy_ca_certificate: None,
206 proxy_excludes: None,
207 allow_http: Default::default(),
208 allow_insecure: Default::default(),
209 timeout: Some(Duration::from_secs(30).into()),
210 connect_timeout: Some(Duration::from_secs(5).into()),
211 pool_idle_timeout: None,
212 pool_max_idle_per_host: None,
213 http2_keep_alive_interval: None,
214 http2_keep_alive_timeout: None,
215 http2_keep_alive_while_idle: Default::default(),
216 http2_max_frame_size: None,
217 http1_only: true.into(),
221 http2_only: Default::default(),
222 }
223 }
224}
225
226impl ClientOptions {
227 pub fn new() -> Self {
229 Default::default()
230 }
231
232 pub fn with_config(mut self, key: ClientConfigKey, value: impl Into<String>) -> Self {
234 match key {
235 ClientConfigKey::AllowHttp => self.allow_http.parse(value),
236 ClientConfigKey::AllowInvalidCertificates => self.allow_insecure.parse(value),
237 ClientConfigKey::ConnectTimeout => {
238 self.connect_timeout = Some(ConfigValue::Deferred(value.into()))
239 }
240 ClientConfigKey::Http1Only => self.http1_only.parse(value),
241 ClientConfigKey::Http2Only => self.http2_only.parse(value),
242 ClientConfigKey::Http2KeepAliveInterval => {
243 self.http2_keep_alive_interval = Some(ConfigValue::Deferred(value.into()))
244 }
245 ClientConfigKey::Http2KeepAliveTimeout => {
246 self.http2_keep_alive_timeout = Some(ConfigValue::Deferred(value.into()))
247 }
248 ClientConfigKey::Http2KeepAliveWhileIdle => {
249 self.http2_keep_alive_while_idle.parse(value)
250 }
251 ClientConfigKey::Http2MaxFrameSize => {
252 self.http2_max_frame_size = Some(ConfigValue::Deferred(value.into()))
253 }
254 ClientConfigKey::PoolIdleTimeout => {
255 self.pool_idle_timeout = Some(ConfigValue::Deferred(value.into()))
256 }
257 ClientConfigKey::PoolMaxIdlePerHost => {
258 self.pool_max_idle_per_host = Some(ConfigValue::Deferred(value.into()))
259 }
260 ClientConfigKey::ProxyUrl => self.proxy_url = Some(value.into()),
261 ClientConfigKey::ProxyCaCertificate => self.proxy_ca_certificate = Some(value.into()),
262 ClientConfigKey::ProxyExcludes => self.proxy_excludes = Some(value.into()),
263 ClientConfigKey::Timeout => self.timeout = Some(ConfigValue::Deferred(value.into())),
264 ClientConfigKey::UserAgent => {
265 self.user_agent = Some(ConfigValue::Deferred(value.into()))
266 }
267 }
268 self
269 }
270
271 pub fn get_config_value(&self, key: &ClientConfigKey) -> Option<String> {
273 match key {
274 ClientConfigKey::AllowHttp => Some(self.allow_http.to_string()),
275 ClientConfigKey::AllowInvalidCertificates => Some(self.allow_insecure.to_string()),
276 ClientConfigKey::ConnectTimeout => self.connect_timeout.as_ref().map(fmt_duration),
277 ClientConfigKey::Http1Only => Some(self.http1_only.to_string()),
278 ClientConfigKey::Http2KeepAliveInterval => {
279 self.http2_keep_alive_interval.as_ref().map(fmt_duration)
280 }
281 ClientConfigKey::Http2KeepAliveTimeout => {
282 self.http2_keep_alive_timeout.as_ref().map(fmt_duration)
283 }
284 ClientConfigKey::Http2KeepAliveWhileIdle => {
285 Some(self.http2_keep_alive_while_idle.to_string())
286 }
287 ClientConfigKey::Http2MaxFrameSize => {
288 self.http2_max_frame_size.as_ref().map(|v| v.to_string())
289 }
290 ClientConfigKey::Http2Only => Some(self.http2_only.to_string()),
291 ClientConfigKey::PoolIdleTimeout => self.pool_idle_timeout.as_ref().map(fmt_duration),
292 ClientConfigKey::PoolMaxIdlePerHost => {
293 self.pool_max_idle_per_host.as_ref().map(|v| v.to_string())
294 }
295 ClientConfigKey::ProxyUrl => self.proxy_url.clone(),
296 ClientConfigKey::ProxyCaCertificate => self.proxy_ca_certificate.clone(),
297 ClientConfigKey::ProxyExcludes => self.proxy_excludes.clone(),
298 ClientConfigKey::Timeout => self.timeout.as_ref().map(fmt_duration),
299 ClientConfigKey::UserAgent => self
300 .user_agent
301 .as_ref()
302 .and_then(|v| v.get().ok())
303 .and_then(|v| v.to_str().ok().map(|s| s.to_string())),
304 }
305 }
306
307 pub fn with_user_agent(mut self, agent: HeaderValue) -> Self {
311 self.user_agent = Some(agent.into());
312 self
313 }
314
315 pub fn with_root_certificate(mut self, certificate: Certificate) -> Self {
320 self.root_certificates.push(certificate);
321 self
322 }
323
324 pub fn with_default_headers(mut self, headers: HeaderMap) -> Self {
326 self.default_headers = Some(headers);
327 self
328 }
329
330 pub fn with_allow_http(mut self, allow_http: bool) -> Self {
334 self.allow_http = allow_http.into();
335 self
336 }
337 pub fn with_allow_invalid_certificates(mut self, allow_insecure: bool) -> Self {
349 self.allow_insecure = allow_insecure.into();
350 self
351 }
352
353 pub fn with_http1_only(mut self) -> Self {
357 self.http2_only = false.into();
358 self.http1_only = true.into();
359 self
360 }
361
362 pub fn with_http2_only(mut self) -> Self {
364 self.http1_only = false.into();
365 self.http2_only = true.into();
366 self
367 }
368
369 pub fn with_allow_http2(mut self) -> Self {
371 self.http1_only = false.into();
372 self.http2_only = false.into();
373 self
374 }
375
376 pub fn with_proxy_url(mut self, proxy_url: impl Into<String>) -> Self {
378 self.proxy_url = Some(proxy_url.into());
379 self
380 }
381
382 pub fn with_proxy_ca_certificate(mut self, proxy_ca_certificate: impl Into<String>) -> Self {
384 self.proxy_ca_certificate = Some(proxy_ca_certificate.into());
385 self
386 }
387
388 pub fn with_proxy_excludes(mut self, proxy_excludes: impl Into<String>) -> Self {
390 self.proxy_excludes = Some(proxy_excludes.into());
391 self
392 }
393
394 pub fn with_timeout(mut self, timeout: Duration) -> Self {
401 self.timeout = Some(ConfigValue::Parsed(timeout));
402 self
403 }
404
405 pub fn with_timeout_disabled(mut self) -> Self {
409 self.timeout = None;
410 self
411 }
412
413 pub fn with_connect_timeout(mut self, timeout: Duration) -> Self {
417 self.connect_timeout = Some(ConfigValue::Parsed(timeout));
418 self
419 }
420
421 pub fn with_connect_timeout_disabled(mut self) -> Self {
425 self.connect_timeout = None;
426 self
427 }
428
429 pub fn with_pool_idle_timeout(mut self, timeout: Duration) -> Self {
435 self.pool_idle_timeout = Some(ConfigValue::Parsed(timeout));
436 self
437 }
438
439 pub fn with_pool_max_idle_per_host(mut self, max: usize) -> Self {
443 self.pool_max_idle_per_host = Some(max.into());
444 self
445 }
446
447 pub fn with_http2_keep_alive_interval(mut self, interval: Duration) -> Self {
451 self.http2_keep_alive_interval = Some(ConfigValue::Parsed(interval));
452 self
453 }
454
455 pub fn with_http2_keep_alive_timeout(mut self, interval: Duration) -> Self {
462 self.http2_keep_alive_timeout = Some(ConfigValue::Parsed(interval));
463 self
464 }
465
466 pub fn with_http2_keep_alive_while_idle(mut self) -> Self {
473 self.http2_keep_alive_while_idle = true.into();
474 self
475 }
476
477 pub fn with_http2_max_frame_size(mut self, sz: u32) -> Self {
481 self.http2_max_frame_size = Some(ConfigValue::Parsed(sz));
482 self
483 }
484
485 pub(crate) fn metadata_client(&self) -> Result<Client> {
491 self.clone()
492 .with_allow_http(true)
493 .with_connect_timeout(Duration::from_secs(1))
494 .client()
495 }
496
497 pub(crate) fn client(&self) -> Result<Client> {
498 let mut builder = ClientBuilder::new();
499
500 match &self.user_agent {
501 Some(user_agent) => builder = builder.user_agent(user_agent.get()?),
502 None => builder = builder.user_agent(DEFAULT_USER_AGENT),
503 }
504
505 if let Some(headers) = &self.default_headers {
506 builder = builder.default_headers(headers.clone())
507 }
508
509 if let Some(proxy) = &self.proxy_url {
510 let mut proxy = Proxy::all(proxy).map_err(map_client_error)?;
511
512 if let Some(certificate) = &self.proxy_ca_certificate {
513 let certificate = reqwest::tls::Certificate::from_pem(certificate.as_bytes())
514 .map_err(map_client_error)?;
515
516 builder = builder.add_root_certificate(certificate);
517 }
518
519 if let Some(proxy_excludes) = &self.proxy_excludes {
520 let no_proxy = NoProxy::from_string(proxy_excludes);
521
522 proxy = proxy.no_proxy(no_proxy);
523 }
524
525 builder = builder.proxy(proxy);
526 }
527
528 for certificate in &self.root_certificates {
529 builder = builder.add_root_certificate(certificate.0.clone());
530 }
531
532 if let Some(timeout) = &self.timeout {
533 builder = builder.timeout(timeout.get()?)
534 }
535
536 if let Some(timeout) = &self.connect_timeout {
537 builder = builder.connect_timeout(timeout.get()?)
538 }
539
540 if let Some(timeout) = &self.pool_idle_timeout {
541 builder = builder.pool_idle_timeout(timeout.get()?)
542 }
543
544 if let Some(max) = &self.pool_max_idle_per_host {
545 builder = builder.pool_max_idle_per_host(max.get()?)
546 }
547
548 if let Some(interval) = &self.http2_keep_alive_interval {
549 builder = builder.http2_keep_alive_interval(interval.get()?)
550 }
551
552 if let Some(interval) = &self.http2_keep_alive_timeout {
553 builder = builder.http2_keep_alive_timeout(interval.get()?)
554 }
555
556 if self.http2_keep_alive_while_idle.get()? {
557 builder = builder.http2_keep_alive_while_idle(true)
558 }
559
560 if let Some(sz) = &self.http2_max_frame_size {
561 builder = builder.http2_max_frame_size(Some(sz.get()?))
562 }
563
564 if self.http1_only.get()? {
565 builder = builder.http1_only()
566 }
567
568 if self.http2_only.get()? {
569 builder = builder.http2_prior_knowledge()
570 }
571
572 if self.allow_insecure.get()? {
573 builder = builder.danger_accept_invalid_certs(true)
574 }
575
576 builder = builder.no_gzip();
579
580 builder
581 .https_only(!self.allow_http.get()?)
582 .build()
583 .map_err(map_client_error)
584 }
585}
586
587#[cfg(test)]
588mod tests {
589 use super::*;
590 use std::collections::HashMap;
591
592 #[test]
593 fn client_test_config_from_map() {
594 let allow_http = "true".to_string();
595 let allow_invalid_certificates = "false".to_string();
596 let connect_timeout = "90 seconds".to_string();
597 let http1_only = "true".to_string();
598 let http2_only = "false".to_string();
599 let http2_keep_alive_interval = "90 seconds".to_string();
600 let http2_keep_alive_timeout = "91 seconds".to_string();
601 let http2_keep_alive_while_idle = "92 seconds".to_string();
602 let http2_max_frame_size = "1337".to_string();
603 let pool_idle_timeout = "93 seconds".to_string();
604 let pool_max_idle_per_host = "94".to_string();
605 let proxy_url = "https://fake_proxy_url".to_string();
606 let timeout = "95 seconds".to_string();
607 let user_agent = "object_store:fake_user_agent".to_string();
608
609 let options = HashMap::from([
610 ("allow_http", allow_http.clone()),
611 (
612 "allow_invalid_certificates",
613 allow_invalid_certificates.clone(),
614 ),
615 ("connect_timeout", connect_timeout.clone()),
616 ("http1_only", http1_only.clone()),
617 ("http2_only", http2_only.clone()),
618 (
619 "http2_keep_alive_interval",
620 http2_keep_alive_interval.clone(),
621 ),
622 ("http2_keep_alive_timeout", http2_keep_alive_timeout.clone()),
623 (
624 "http2_keep_alive_while_idle",
625 http2_keep_alive_while_idle.clone(),
626 ),
627 ("http2_max_frame_size", http2_max_frame_size.clone()),
628 ("pool_idle_timeout", pool_idle_timeout.clone()),
629 ("pool_max_idle_per_host", pool_max_idle_per_host.clone()),
630 ("proxy_url", proxy_url.clone()),
631 ("timeout", timeout.clone()),
632 ("user_agent", user_agent.clone()),
633 ]);
634
635 let builder = options
636 .into_iter()
637 .fold(ClientOptions::new(), |builder, (key, value)| {
638 builder.with_config(key.parse().unwrap(), value)
639 });
640
641 assert_eq!(
642 builder
643 .get_config_value(&ClientConfigKey::AllowHttp)
644 .unwrap(),
645 allow_http
646 );
647 assert_eq!(
648 builder
649 .get_config_value(&ClientConfigKey::AllowInvalidCertificates)
650 .unwrap(),
651 allow_invalid_certificates
652 );
653 assert_eq!(
654 builder
655 .get_config_value(&ClientConfigKey::ConnectTimeout)
656 .unwrap(),
657 connect_timeout
658 );
659 assert_eq!(
660 builder
661 .get_config_value(&ClientConfigKey::Http1Only)
662 .unwrap(),
663 http1_only
664 );
665 assert_eq!(
666 builder
667 .get_config_value(&ClientConfigKey::Http2Only)
668 .unwrap(),
669 http2_only
670 );
671 assert_eq!(
672 builder
673 .get_config_value(&ClientConfigKey::Http2KeepAliveInterval)
674 .unwrap(),
675 http2_keep_alive_interval
676 );
677 assert_eq!(
678 builder
679 .get_config_value(&ClientConfigKey::Http2KeepAliveTimeout)
680 .unwrap(),
681 http2_keep_alive_timeout
682 );
683 assert_eq!(
684 builder
685 .get_config_value(&ClientConfigKey::Http2KeepAliveWhileIdle)
686 .unwrap(),
687 http2_keep_alive_while_idle
688 );
689 assert_eq!(
690 builder
691 .get_config_value(&ClientConfigKey::Http2MaxFrameSize)
692 .unwrap(),
693 http2_max_frame_size
694 );
695
696 assert_eq!(
697 builder
698 .get_config_value(&ClientConfigKey::PoolIdleTimeout)
699 .unwrap(),
700 pool_idle_timeout
701 );
702 assert_eq!(
703 builder
704 .get_config_value(&ClientConfigKey::PoolMaxIdlePerHost)
705 .unwrap(),
706 pool_max_idle_per_host
707 );
708 assert_eq!(
709 builder
710 .get_config_value(&ClientConfigKey::ProxyUrl)
711 .unwrap(),
712 proxy_url
713 );
714 assert_eq!(
715 builder.get_config_value(&ClientConfigKey::Timeout).unwrap(),
716 timeout
717 );
718 assert_eq!(
719 builder
720 .get_config_value(&ClientConfigKey::UserAgent)
721 .unwrap(),
722 user_agent
723 );
724 }
725}