1#[cfg(any(feature = "native-tls", feature = "__rustls",))]
2use std::any::Any;
3use std::net::IpAddr;
4use std::sync::Arc;
5use std::time::Duration;
6use std::{collections::HashMap, convert::TryInto, net::SocketAddr};
7use std::{fmt, str};
8
9use bytes::Bytes;
10use http::header::{
11 Entry, HeaderMap, HeaderValue, ACCEPT, ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_LENGTH,
12 CONTENT_TYPE, LOCATION, PROXY_AUTHORIZATION, RANGE, REFERER, TRANSFER_ENCODING, USER_AGENT,
13};
14use http::uri::Scheme;
15use http::Uri;
16use hyper_util::client::legacy::connect::HttpConnector;
17#[cfg(feature = "default-tls")]
18use native_tls_crate::TlsConnector;
19use pin_project_lite::pin_project;
20use std::future::Future;
21use std::pin::Pin;
22use std::task::{Context, Poll};
23use tokio::time::Sleep;
24
25use super::decoder::Accepts;
26use super::request::{Request, RequestBuilder};
27use super::response::Response;
28use super::Body;
29#[cfg(feature = "http3")]
30use crate::async_impl::h3_client::connect::H3Connector;
31#[cfg(feature = "http3")]
32use crate::async_impl::h3_client::{H3Client, H3ResponseFuture};
33use crate::connect::Connector;
34#[cfg(feature = "cookies")]
35use crate::cookie;
36#[cfg(feature = "hickory-dns")]
37use crate::dns::hickory::HickoryDnsResolver;
38use crate::dns::{gai::GaiResolver, DnsResolverWithOverrides, DynResolver, Resolve};
39use crate::error;
40use crate::into_url::try_uri;
41use crate::redirect::{self, remove_sensitive_headers};
42#[cfg(feature = "__rustls")]
43use crate::tls::CertificateRevocationList;
44#[cfg(feature = "__tls")]
45use crate::tls::{self, TlsBackend};
46#[cfg(feature = "__tls")]
47use crate::Certificate;
48#[cfg(any(feature = "native-tls", feature = "__rustls"))]
49use crate::Identity;
50use crate::{IntoUrl, Method, Proxy, StatusCode, Url};
51use log::debug;
52#[cfg(feature = "http3")]
53use quinn::TransportConfig;
54#[cfg(feature = "http3")]
55use quinn::VarInt;
56
57type HyperResponseFuture = hyper_util::client::legacy::ResponseFuture;
58
59#[derive(Clone)]
73pub struct Client {
74 inner: Arc<ClientRef>,
75}
76
77#[must_use]
79pub struct ClientBuilder {
80 config: Config,
81}
82
83enum HttpVersionPref {
84 Http1,
85 #[cfg(feature = "http2")]
86 Http2,
87 #[cfg(feature = "http3")]
88 Http3,
89 All,
90}
91
92struct Config {
93 accepts: Accepts,
95 headers: HeaderMap,
96 #[cfg(feature = "__tls")]
97 hostname_verification: bool,
98 #[cfg(feature = "__tls")]
99 certs_verification: bool,
100 #[cfg(feature = "__tls")]
101 tls_sni: bool,
102 connect_timeout: Option<Duration>,
103 connection_verbose: bool,
104 pool_idle_timeout: Option<Duration>,
105 pool_max_idle_per_host: usize,
106 tcp_keepalive: Option<Duration>,
107 #[cfg(any(feature = "native-tls", feature = "__rustls"))]
108 identity: Option<Identity>,
109 proxies: Vec<Proxy>,
110 auto_sys_proxy: bool,
111 redirect_policy: redirect::Policy,
112 referer: bool,
113 read_timeout: Option<Duration>,
114 timeout: Option<Duration>,
115 #[cfg(feature = "__tls")]
116 root_certs: Vec<Certificate>,
117 #[cfg(feature = "__tls")]
118 tls_built_in_root_certs: bool,
119 #[cfg(feature = "rustls-tls-webpki-roots-no-provider")]
120 tls_built_in_certs_webpki: bool,
121 #[cfg(feature = "rustls-tls-native-roots-no-provider")]
122 tls_built_in_certs_native: bool,
123 #[cfg(feature = "__rustls")]
124 crls: Vec<CertificateRevocationList>,
125 #[cfg(feature = "__tls")]
126 min_tls_version: Option<tls::Version>,
127 #[cfg(feature = "__tls")]
128 max_tls_version: Option<tls::Version>,
129 #[cfg(feature = "__tls")]
130 tls_info: bool,
131 #[cfg(feature = "__tls")]
132 tls: TlsBackend,
133 http_version_pref: HttpVersionPref,
134 http09_responses: bool,
135 http1_title_case_headers: bool,
136 http1_allow_obsolete_multiline_headers_in_responses: bool,
137 http1_ignore_invalid_headers_in_responses: bool,
138 http1_allow_spaces_after_header_name_in_responses: bool,
139 #[cfg(feature = "http2")]
140 http2_initial_stream_window_size: Option<u32>,
141 #[cfg(feature = "http2")]
142 http2_initial_connection_window_size: Option<u32>,
143 #[cfg(feature = "http2")]
144 http2_adaptive_window: bool,
145 #[cfg(feature = "http2")]
146 http2_max_frame_size: Option<u32>,
147 #[cfg(feature = "http2")]
148 http2_keep_alive_interval: Option<Duration>,
149 #[cfg(feature = "http2")]
150 http2_keep_alive_timeout: Option<Duration>,
151 #[cfg(feature = "http2")]
152 http2_keep_alive_while_idle: bool,
153 local_address: Option<IpAddr>,
154 #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
155 interface: Option<String>,
156 nodelay: bool,
157 #[cfg(feature = "cookies")]
158 cookie_store: Option<Arc<dyn cookie::CookieStore>>,
159 hickory_dns: bool,
160 error: Option<crate::Error>,
161 https_only: bool,
162 #[cfg(feature = "http3")]
163 tls_enable_early_data: bool,
164 #[cfg(feature = "http3")]
165 quic_max_idle_timeout: Option<Duration>,
166 #[cfg(feature = "http3")]
167 quic_stream_receive_window: Option<VarInt>,
168 #[cfg(feature = "http3")]
169 quic_receive_window: Option<VarInt>,
170 #[cfg(feature = "http3")]
171 quic_send_window: Option<u64>,
172 dns_overrides: HashMap<String, Vec<SocketAddr>>,
173 dns_resolver: Option<Arc<dyn Resolve>>,
174}
175
176impl Default for ClientBuilder {
177 fn default() -> Self {
178 Self::new()
179 }
180}
181
182impl ClientBuilder {
183 pub fn new() -> ClientBuilder {
187 let mut headers: HeaderMap<HeaderValue> = HeaderMap::with_capacity(2);
188 headers.insert(ACCEPT, HeaderValue::from_static("*/*"));
189
190 ClientBuilder {
191 config: Config {
192 error: None,
193 accepts: Accepts::default(),
194 headers,
195 #[cfg(feature = "__tls")]
196 hostname_verification: true,
197 #[cfg(feature = "__tls")]
198 certs_verification: true,
199 #[cfg(feature = "__tls")]
200 tls_sni: true,
201 connect_timeout: None,
202 connection_verbose: false,
203 pool_idle_timeout: Some(Duration::from_secs(90)),
204 pool_max_idle_per_host: std::usize::MAX,
205 tcp_keepalive: None, proxies: Vec::new(),
209 auto_sys_proxy: true,
210 redirect_policy: redirect::Policy::default(),
211 referer: true,
212 read_timeout: None,
213 timeout: None,
214 #[cfg(feature = "__tls")]
215 root_certs: Vec::new(),
216 #[cfg(feature = "__tls")]
217 tls_built_in_root_certs: true,
218 #[cfg(feature = "rustls-tls-webpki-roots-no-provider")]
219 tls_built_in_certs_webpki: true,
220 #[cfg(feature = "rustls-tls-native-roots-no-provider")]
221 tls_built_in_certs_native: true,
222 #[cfg(any(feature = "native-tls", feature = "__rustls"))]
223 identity: None,
224 #[cfg(feature = "__rustls")]
225 crls: vec![],
226 #[cfg(feature = "__tls")]
227 min_tls_version: None,
228 #[cfg(feature = "__tls")]
229 max_tls_version: None,
230 #[cfg(feature = "__tls")]
231 tls_info: false,
232 #[cfg(feature = "__tls")]
233 tls: TlsBackend::default(),
234 http_version_pref: HttpVersionPref::All,
235 http09_responses: false,
236 http1_title_case_headers: false,
237 http1_allow_obsolete_multiline_headers_in_responses: false,
238 http1_ignore_invalid_headers_in_responses: false,
239 http1_allow_spaces_after_header_name_in_responses: false,
240 #[cfg(feature = "http2")]
241 http2_initial_stream_window_size: None,
242 #[cfg(feature = "http2")]
243 http2_initial_connection_window_size: None,
244 #[cfg(feature = "http2")]
245 http2_adaptive_window: false,
246 #[cfg(feature = "http2")]
247 http2_max_frame_size: None,
248 #[cfg(feature = "http2")]
249 http2_keep_alive_interval: None,
250 #[cfg(feature = "http2")]
251 http2_keep_alive_timeout: None,
252 #[cfg(feature = "http2")]
253 http2_keep_alive_while_idle: false,
254 local_address: None,
255 #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
256 interface: None,
257 nodelay: true,
258 hickory_dns: cfg!(feature = "hickory-dns"),
259 #[cfg(feature = "cookies")]
260 cookie_store: None,
261 https_only: false,
262 dns_overrides: HashMap::new(),
263 #[cfg(feature = "http3")]
264 tls_enable_early_data: false,
265 #[cfg(feature = "http3")]
266 quic_max_idle_timeout: None,
267 #[cfg(feature = "http3")]
268 quic_stream_receive_window: None,
269 #[cfg(feature = "http3")]
270 quic_receive_window: None,
271 #[cfg(feature = "http3")]
272 quic_send_window: None,
273 dns_resolver: None,
274 },
275 }
276 }
277
278 pub fn build(self) -> crate::Result<Client> {
285 let config = self.config;
286
287 if let Some(err) = config.error {
288 return Err(err);
289 }
290
291 let mut proxies = config.proxies;
292 if config.auto_sys_proxy {
293 proxies.push(Proxy::system());
294 }
295 let proxies = Arc::new(proxies);
296
297 #[allow(unused)]
298 #[cfg(feature = "http3")]
299 let mut h3_connector = None;
300
301 let mut connector = {
302 #[cfg(feature = "__tls")]
303 fn user_agent(headers: &HeaderMap) -> Option<HeaderValue> {
304 headers.get(USER_AGENT).cloned()
305 }
306
307 let mut resolver: Arc<dyn Resolve> = match config.hickory_dns {
308 false => Arc::new(GaiResolver::new()),
309 #[cfg(feature = "hickory-dns")]
310 true => Arc::new(HickoryDnsResolver::default()),
311 #[cfg(not(feature = "hickory-dns"))]
312 true => unreachable!("hickory-dns shouldn't be enabled unless the feature is"),
313 };
314 if let Some(dns_resolver) = config.dns_resolver {
315 resolver = dns_resolver;
316 }
317 if !config.dns_overrides.is_empty() {
318 resolver = Arc::new(DnsResolverWithOverrides::new(
319 resolver,
320 config.dns_overrides,
321 ));
322 }
323 let mut http = HttpConnector::new_with_resolver(DynResolver::new(resolver.clone()));
324 http.set_connect_timeout(config.connect_timeout);
325
326 #[cfg(all(feature = "http3", feature = "__rustls"))]
327 let build_h3_connector =
328 |resolver,
329 tls,
330 quic_max_idle_timeout: Option<Duration>,
331 quic_stream_receive_window,
332 quic_receive_window,
333 quic_send_window,
334 local_address,
335 http_version_pref: &HttpVersionPref| {
336 let mut transport_config = TransportConfig::default();
337
338 if let Some(max_idle_timeout) = quic_max_idle_timeout {
339 transport_config.max_idle_timeout(Some(
340 max_idle_timeout.try_into().map_err(error::builder)?,
341 ));
342 }
343
344 if let Some(stream_receive_window) = quic_stream_receive_window {
345 transport_config.stream_receive_window(stream_receive_window);
346 }
347
348 if let Some(receive_window) = quic_receive_window {
349 transport_config.receive_window(receive_window);
350 }
351
352 if let Some(send_window) = quic_send_window {
353 transport_config.send_window(send_window);
354 }
355
356 let res = H3Connector::new(
357 DynResolver::new(resolver),
358 tls,
359 local_address,
360 transport_config,
361 );
362
363 match res {
364 Ok(connector) => Ok(Some(connector)),
365 Err(err) => {
366 if let HttpVersionPref::Http3 = http_version_pref {
367 Err(error::builder(err))
368 } else {
369 Ok(None)
370 }
371 }
372 }
373 };
374
375 #[cfg(feature = "__tls")]
376 match config.tls {
377 #[cfg(feature = "default-tls")]
378 TlsBackend::Default => {
379 let mut tls = TlsConnector::builder();
380
381 #[cfg(all(feature = "native-tls-alpn", not(feature = "http3")))]
382 {
383 match config.http_version_pref {
384 HttpVersionPref::Http1 => {
385 tls.request_alpns(&["http/1.1"]);
386 }
387 #[cfg(feature = "http2")]
388 HttpVersionPref::Http2 => {
389 tls.request_alpns(&["h2"]);
390 }
391 HttpVersionPref::All => {
392 tls.request_alpns(&["h2", "http/1.1"]);
393 }
394 }
395 }
396
397 tls.danger_accept_invalid_hostnames(!config.hostname_verification);
398
399 tls.danger_accept_invalid_certs(!config.certs_verification);
400
401 tls.use_sni(config.tls_sni);
402
403 tls.disable_built_in_roots(!config.tls_built_in_root_certs);
404
405 for cert in config.root_certs {
406 cert.add_to_native_tls(&mut tls);
407 }
408
409 #[cfg(feature = "native-tls")]
410 {
411 if let Some(id) = config.identity {
412 id.add_to_native_tls(&mut tls)?;
413 }
414 }
415 #[cfg(all(feature = "__rustls", not(feature = "native-tls")))]
416 {
417 if let Some(_id) = config.identity {
419 return Err(crate::error::builder("incompatible TLS identity type"));
420 }
421 }
422
423 if let Some(min_tls_version) = config.min_tls_version {
424 let protocol = min_tls_version.to_native_tls().ok_or_else(|| {
425 crate::error::builder("invalid minimum TLS version for backend")
429 })?;
430 tls.min_protocol_version(Some(protocol));
431 }
432
433 if let Some(max_tls_version) = config.max_tls_version {
434 let protocol = max_tls_version.to_native_tls().ok_or_else(|| {
435 crate::error::builder("invalid maximum TLS version for backend")
440 })?;
441 tls.max_protocol_version(Some(protocol));
442 }
443
444 Connector::new_default_tls(
445 http,
446 tls,
447 proxies.clone(),
448 user_agent(&config.headers),
449 config.local_address,
450 #[cfg(any(
451 target_os = "android",
452 target_os = "fuchsia",
453 target_os = "linux"
454 ))]
455 config.interface.as_deref(),
456 config.nodelay,
457 config.tls_info,
458 )?
459 }
460 #[cfg(feature = "native-tls")]
461 TlsBackend::BuiltNativeTls(conn) => Connector::from_built_default_tls(
462 http,
463 conn,
464 proxies.clone(),
465 user_agent(&config.headers),
466 config.local_address,
467 #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
468 config.interface.as_deref(),
469 config.nodelay,
470 config.tls_info,
471 ),
472 #[cfg(feature = "__rustls")]
473 TlsBackend::BuiltRustls(conn) => {
474 #[cfg(feature = "http3")]
475 {
476 h3_connector = build_h3_connector(
477 resolver,
478 conn.clone(),
479 config.quic_max_idle_timeout,
480 config.quic_stream_receive_window,
481 config.quic_receive_window,
482 config.quic_send_window,
483 config.local_address,
484 &config.http_version_pref,
485 )?;
486 }
487
488 Connector::new_rustls_tls(
489 http,
490 conn,
491 proxies.clone(),
492 user_agent(&config.headers),
493 config.local_address,
494 #[cfg(any(
495 target_os = "android",
496 target_os = "fuchsia",
497 target_os = "linux"
498 ))]
499 config.interface.as_deref(),
500 config.nodelay,
501 config.tls_info,
502 )
503 }
504 #[cfg(feature = "__rustls")]
505 TlsBackend::Rustls => {
506 use crate::tls::{IgnoreHostname, NoVerifier};
507
508 let mut root_cert_store = rustls::RootCertStore::empty();
510 for cert in config.root_certs {
511 cert.add_to_rustls(&mut root_cert_store)?;
512 }
513
514 #[cfg(feature = "rustls-tls-webpki-roots-no-provider")]
515 if config.tls_built_in_certs_webpki {
516 root_cert_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
517 }
518
519 #[cfg(feature = "rustls-tls-native-roots-no-provider")]
520 if config.tls_built_in_certs_native {
521 let mut valid_count = 0;
522 let mut invalid_count = 0;
523
524 let load_results = rustls_native_certs::load_native_certs();
525 for cert in load_results.certs {
526 match root_cert_store.add(cert.into()) {
530 Ok(_) => valid_count += 1,
531 Err(err) => {
532 invalid_count += 1;
533 log::debug!("rustls failed to parse DER certificate: {err:?}");
534 }
535 }
536 }
537 if valid_count == 0 && invalid_count > 0 {
538 let err = if load_results.errors.is_empty() {
539 crate::error::builder(
540 "zero valid certificates found in native root store",
541 )
542 } else {
543 use std::fmt::Write as _;
544 let mut acc = String::new();
545 for err in load_results.errors {
546 let _ = writeln!(&mut acc, "{err}");
547 }
548
549 crate::error::builder(acc)
550 };
551
552 return Err(err);
553 }
554 }
555
556 let mut versions = rustls::ALL_VERSIONS.to_vec();
558
559 if let Some(min_tls_version) = config.min_tls_version {
560 versions.retain(|&supported_version| {
561 match tls::Version::from_rustls(supported_version.version) {
562 Some(version) => version >= min_tls_version,
563 None => true,
566 }
567 });
568 }
569
570 if let Some(max_tls_version) = config.max_tls_version {
571 versions.retain(|&supported_version| {
572 match tls::Version::from_rustls(supported_version.version) {
573 Some(version) => version <= max_tls_version,
574 None => false,
575 }
576 });
577 }
578
579 if versions.is_empty() {
580 return Err(crate::error::builder("empty supported tls versions"));
581 }
582
583 let provider = rustls::crypto::CryptoProvider::get_default()
586 .map(|arc| arc.clone())
587 .unwrap_or_else(|| {
588 #[cfg(not(feature = "__rustls-ring"))]
589 panic!("No provider set");
590
591 #[cfg(feature = "__rustls-ring")]
592 Arc::new(rustls::crypto::ring::default_provider())
593 });
594
595 let signature_algorithms = provider.signature_verification_algorithms;
597 let config_builder =
598 rustls::ClientConfig::builder_with_provider(provider.clone())
599 .with_protocol_versions(&versions)
600 .map_err(|_| crate::error::builder("invalid TLS versions"))?;
601
602 let config_builder = if !config.certs_verification {
603 config_builder
604 .dangerous()
605 .with_custom_certificate_verifier(Arc::new(NoVerifier))
606 } else if !config.hostname_verification {
607 config_builder
608 .dangerous()
609 .with_custom_certificate_verifier(Arc::new(IgnoreHostname::new(
610 root_cert_store,
611 signature_algorithms,
612 )))
613 } else {
614 if config.crls.is_empty() {
615 config_builder.with_root_certificates(root_cert_store)
616 } else {
617 let crls = config
618 .crls
619 .iter()
620 .map(|e| e.as_rustls_crl())
621 .collect::<Vec<_>>();
622 let verifier =
623 rustls::client::WebPkiServerVerifier::builder_with_provider(
624 Arc::new(root_cert_store),
625 provider,
626 )
627 .with_crls(crls)
628 .build()
629 .map_err(|_| {
630 crate::error::builder("invalid TLS verification settings")
631 })?;
632 config_builder.with_webpki_verifier(verifier)
633 }
634 };
635
636 let mut tls = if let Some(id) = config.identity {
638 id.add_to_rustls(config_builder)?
639 } else {
640 config_builder.with_no_client_auth()
641 };
642
643 tls.enable_sni = config.tls_sni;
644
645 match config.http_version_pref {
647 HttpVersionPref::Http1 => {
648 tls.alpn_protocols = vec!["http/1.1".into()];
649 }
650 #[cfg(feature = "http2")]
651 HttpVersionPref::Http2 => {
652 tls.alpn_protocols = vec!["h2".into()];
653 }
654 #[cfg(feature = "http3")]
655 HttpVersionPref::Http3 => {
656 tls.alpn_protocols = vec!["h3".into()];
657 }
658 HttpVersionPref::All => {
659 tls.alpn_protocols = vec![
660 #[cfg(feature = "http2")]
661 "h2".into(),
662 "http/1.1".into(),
663 ];
664 }
665 }
666
667 #[cfg(feature = "http3")]
668 {
669 tls.enable_early_data = config.tls_enable_early_data;
670
671 h3_connector = build_h3_connector(
672 resolver,
673 tls.clone(),
674 config.quic_max_idle_timeout,
675 config.quic_stream_receive_window,
676 config.quic_receive_window,
677 config.quic_send_window,
678 config.local_address,
679 &config.http_version_pref,
680 )?;
681 }
682
683 Connector::new_rustls_tls(
684 http,
685 tls,
686 proxies.clone(),
687 user_agent(&config.headers),
688 config.local_address,
689 #[cfg(any(
690 target_os = "android",
691 target_os = "fuchsia",
692 target_os = "linux"
693 ))]
694 config.interface.as_deref(),
695 config.nodelay,
696 config.tls_info,
697 )
698 }
699 #[cfg(any(feature = "native-tls", feature = "__rustls",))]
700 TlsBackend::UnknownPreconfigured => {
701 return Err(crate::error::builder(
702 "Unknown TLS backend passed to `use_preconfigured_tls`",
703 ));
704 }
705 }
706
707 #[cfg(not(feature = "__tls"))]
708 Connector::new(
709 http,
710 proxies.clone(),
711 config.local_address,
712 #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
713 config.interface.as_deref(),
714 config.nodelay,
715 )
716 };
717
718 connector.set_timeout(config.connect_timeout);
719 connector.set_verbose(config.connection_verbose);
720
721 let mut builder =
722 hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new());
723 #[cfg(feature = "http2")]
724 {
725 if matches!(config.http_version_pref, HttpVersionPref::Http2) {
726 builder.http2_only(true);
727 }
728
729 if let Some(http2_initial_stream_window_size) = config.http2_initial_stream_window_size
730 {
731 builder.http2_initial_stream_window_size(http2_initial_stream_window_size);
732 }
733 if let Some(http2_initial_connection_window_size) =
734 config.http2_initial_connection_window_size
735 {
736 builder.http2_initial_connection_window_size(http2_initial_connection_window_size);
737 }
738 if config.http2_adaptive_window {
739 builder.http2_adaptive_window(true);
740 }
741 if let Some(http2_max_frame_size) = config.http2_max_frame_size {
742 builder.http2_max_frame_size(http2_max_frame_size);
743 }
744 if let Some(http2_keep_alive_interval) = config.http2_keep_alive_interval {
745 builder.http2_keep_alive_interval(http2_keep_alive_interval);
746 }
747 if let Some(http2_keep_alive_timeout) = config.http2_keep_alive_timeout {
748 builder.http2_keep_alive_timeout(http2_keep_alive_timeout);
749 }
750 if config.http2_keep_alive_while_idle {
751 builder.http2_keep_alive_while_idle(true);
752 }
753 }
754
755 builder.timer(hyper_util::rt::TokioTimer::new());
756 builder.pool_timer(hyper_util::rt::TokioTimer::new());
757 builder.pool_idle_timeout(config.pool_idle_timeout);
758 builder.pool_max_idle_per_host(config.pool_max_idle_per_host);
759 connector.set_keepalive(config.tcp_keepalive);
760
761 if config.http09_responses {
762 builder.http09_responses(true);
763 }
764
765 if config.http1_title_case_headers {
766 builder.http1_title_case_headers(true);
767 }
768
769 if config.http1_allow_obsolete_multiline_headers_in_responses {
770 builder.http1_allow_obsolete_multiline_headers_in_responses(true);
771 }
772
773 if config.http1_ignore_invalid_headers_in_responses {
774 builder.http1_ignore_invalid_headers_in_responses(true);
775 }
776
777 if config.http1_allow_spaces_after_header_name_in_responses {
778 builder.http1_allow_spaces_after_header_name_in_responses(true);
779 }
780
781 let proxies_maybe_http_auth = proxies.iter().any(|p| p.maybe_has_http_auth());
782
783 Ok(Client {
784 inner: Arc::new(ClientRef {
785 accepts: config.accepts,
786 #[cfg(feature = "cookies")]
787 cookie_store: config.cookie_store,
788 #[cfg(feature = "http3")]
791 h3_client: match h3_connector {
792 Some(h3_connector) => {
793 Some(H3Client::new(h3_connector, config.pool_idle_timeout))
794 }
795 None => None,
796 },
797 hyper: builder.build(connector),
798 headers: config.headers,
799 redirect_policy: config.redirect_policy,
800 referer: config.referer,
801 read_timeout: config.read_timeout,
802 request_timeout: config.timeout,
803 proxies,
804 proxies_maybe_http_auth,
805 https_only: config.https_only,
806 }),
807 })
808 }
809
810 pub fn user_agent<V>(mut self, value: V) -> ClientBuilder
833 where
834 V: TryInto<HeaderValue>,
835 V::Error: Into<http::Error>,
836 {
837 match value.try_into() {
838 Ok(value) => {
839 self.config.headers.insert(USER_AGENT, value);
840 }
841 Err(e) => {
842 self.config.error = Some(crate::error::builder(e.into()));
843 }
844 };
845 self
846 }
847 pub fn default_headers(mut self, headers: HeaderMap) -> ClientBuilder {
871 for (key, value) in headers.iter() {
872 self.config.headers.insert(key, value.clone());
873 }
874 self
875 }
876
877 #[cfg(feature = "cookies")]
892 #[cfg_attr(docsrs, doc(cfg(feature = "cookies")))]
893 pub fn cookie_store(mut self, enable: bool) -> ClientBuilder {
894 if enable {
895 self.cookie_provider(Arc::new(cookie::Jar::default()))
896 } else {
897 self.config.cookie_store = None;
898 self
899 }
900 }
901
902 #[cfg(feature = "cookies")]
916 #[cfg_attr(docsrs, doc(cfg(feature = "cookies")))]
917 pub fn cookie_provider<C: cookie::CookieStore + 'static>(
918 mut self,
919 cookie_store: Arc<C>,
920 ) -> ClientBuilder {
921 self.config.cookie_store = Some(cookie_store as _);
922 self
923 }
924
925 #[cfg(feature = "gzip")]
942 #[cfg_attr(docsrs, doc(cfg(feature = "gzip")))]
943 pub fn gzip(mut self, enable: bool) -> ClientBuilder {
944 self.config.accepts.gzip = enable;
945 self
946 }
947
948 #[cfg(feature = "brotli")]
965 #[cfg_attr(docsrs, doc(cfg(feature = "brotli")))]
966 pub fn brotli(mut self, enable: bool) -> ClientBuilder {
967 self.config.accepts.brotli = enable;
968 self
969 }
970
971 #[cfg(feature = "zstd")]
988 #[cfg_attr(docsrs, doc(cfg(feature = "zstd")))]
989 pub fn zstd(mut self, enable: bool) -> ClientBuilder {
990 self.config.accepts.zstd = enable;
991 self
992 }
993
994 #[cfg(feature = "deflate")]
1011 #[cfg_attr(docsrs, doc(cfg(feature = "deflate")))]
1012 pub fn deflate(mut self, enable: bool) -> ClientBuilder {
1013 self.config.accepts.deflate = enable;
1014 self
1015 }
1016
1017 pub fn no_gzip(self) -> ClientBuilder {
1023 #[cfg(feature = "gzip")]
1024 {
1025 self.gzip(false)
1026 }
1027
1028 #[cfg(not(feature = "gzip"))]
1029 {
1030 self
1031 }
1032 }
1033
1034 pub fn no_brotli(self) -> ClientBuilder {
1040 #[cfg(feature = "brotli")]
1041 {
1042 self.brotli(false)
1043 }
1044
1045 #[cfg(not(feature = "brotli"))]
1046 {
1047 self
1048 }
1049 }
1050
1051 pub fn no_zstd(self) -> ClientBuilder {
1057 #[cfg(feature = "zstd")]
1058 {
1059 self.zstd(false)
1060 }
1061
1062 #[cfg(not(feature = "zstd"))]
1063 {
1064 self
1065 }
1066 }
1067
1068 pub fn no_deflate(self) -> ClientBuilder {
1074 #[cfg(feature = "deflate")]
1075 {
1076 self.deflate(false)
1077 }
1078
1079 #[cfg(not(feature = "deflate"))]
1080 {
1081 self
1082 }
1083 }
1084
1085 pub fn redirect(mut self, policy: redirect::Policy) -> ClientBuilder {
1091 self.config.redirect_policy = policy;
1092 self
1093 }
1094
1095 pub fn referer(mut self, enable: bool) -> ClientBuilder {
1099 self.config.referer = enable;
1100 self
1101 }
1102
1103 pub fn proxy(mut self, proxy: Proxy) -> ClientBuilder {
1111 self.config.proxies.push(proxy);
1112 self.config.auto_sys_proxy = false;
1113 self
1114 }
1115
1116 pub fn no_proxy(mut self) -> ClientBuilder {
1124 self.config.proxies.clear();
1125 self.config.auto_sys_proxy = false;
1126 self
1127 }
1128
1129 pub fn timeout(mut self, timeout: Duration) -> ClientBuilder {
1138 self.config.timeout = Some(timeout);
1139 self
1140 }
1141
1142 pub fn read_timeout(mut self, timeout: Duration) -> ClientBuilder {
1150 self.config.read_timeout = Some(timeout);
1151 self
1152 }
1153
1154 pub fn connect_timeout(mut self, timeout: Duration) -> ClientBuilder {
1163 self.config.connect_timeout = Some(timeout);
1164 self
1165 }
1166
1167 pub fn connection_verbose(mut self, verbose: bool) -> ClientBuilder {
1174 self.config.connection_verbose = verbose;
1175 self
1176 }
1177
1178 pub fn pool_idle_timeout<D>(mut self, val: D) -> ClientBuilder
1186 where
1187 D: Into<Option<Duration>>,
1188 {
1189 self.config.pool_idle_timeout = val.into();
1190 self
1191 }
1192
1193 pub fn pool_max_idle_per_host(mut self, max: usize) -> ClientBuilder {
1195 self.config.pool_max_idle_per_host = max;
1196 self
1197 }
1198
1199 pub fn http1_title_case_headers(mut self) -> ClientBuilder {
1201 self.config.http1_title_case_headers = true;
1202 self
1203 }
1204
1205 pub fn http1_allow_obsolete_multiline_headers_in_responses(
1211 mut self,
1212 value: bool,
1213 ) -> ClientBuilder {
1214 self.config
1215 .http1_allow_obsolete_multiline_headers_in_responses = value;
1216 self
1217 }
1218
1219 pub fn http1_ignore_invalid_headers_in_responses(mut self, value: bool) -> ClientBuilder {
1221 self.config.http1_ignore_invalid_headers_in_responses = value;
1222 self
1223 }
1224
1225 pub fn http1_allow_spaces_after_header_name_in_responses(
1231 mut self,
1232 value: bool,
1233 ) -> ClientBuilder {
1234 self.config
1235 .http1_allow_spaces_after_header_name_in_responses = value;
1236 self
1237 }
1238
1239 pub fn http1_only(mut self) -> ClientBuilder {
1241 self.config.http_version_pref = HttpVersionPref::Http1;
1242 self
1243 }
1244
1245 pub fn http09_responses(mut self) -> ClientBuilder {
1247 self.config.http09_responses = true;
1248 self
1249 }
1250
1251 #[cfg(feature = "http2")]
1253 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
1254 pub fn http2_prior_knowledge(mut self) -> ClientBuilder {
1255 self.config.http_version_pref = HttpVersionPref::Http2;
1256 self
1257 }
1258
1259 #[cfg(feature = "http3")]
1261 #[cfg_attr(docsrs, doc(cfg(all(reqwest_unstable, feature = "http3",))))]
1262 pub fn http3_prior_knowledge(mut self) -> ClientBuilder {
1263 self.config.http_version_pref = HttpVersionPref::Http3;
1264 self
1265 }
1266
1267 #[cfg(feature = "http2")]
1271 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
1272 pub fn http2_initial_stream_window_size(mut self, sz: impl Into<Option<u32>>) -> ClientBuilder {
1273 self.config.http2_initial_stream_window_size = sz.into();
1274 self
1275 }
1276
1277 #[cfg(feature = "http2")]
1281 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
1282 pub fn http2_initial_connection_window_size(
1283 mut self,
1284 sz: impl Into<Option<u32>>,
1285 ) -> ClientBuilder {
1286 self.config.http2_initial_connection_window_size = sz.into();
1287 self
1288 }
1289
1290 #[cfg(feature = "http2")]
1295 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
1296 pub fn http2_adaptive_window(mut self, enabled: bool) -> ClientBuilder {
1297 self.config.http2_adaptive_window = enabled;
1298 self
1299 }
1300
1301 #[cfg(feature = "http2")]
1305 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
1306 pub fn http2_max_frame_size(mut self, sz: impl Into<Option<u32>>) -> ClientBuilder {
1307 self.config.http2_max_frame_size = sz.into();
1308 self
1309 }
1310
1311 #[cfg(feature = "http2")]
1316 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
1317 pub fn http2_keep_alive_interval(
1318 mut self,
1319 interval: impl Into<Option<Duration>>,
1320 ) -> ClientBuilder {
1321 self.config.http2_keep_alive_interval = interval.into();
1322 self
1323 }
1324
1325 #[cfg(feature = "http2")]
1331 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
1332 pub fn http2_keep_alive_timeout(mut self, timeout: Duration) -> ClientBuilder {
1333 self.config.http2_keep_alive_timeout = Some(timeout);
1334 self
1335 }
1336
1337 #[cfg(feature = "http2")]
1344 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
1345 pub fn http2_keep_alive_while_idle(mut self, enabled: bool) -> ClientBuilder {
1346 self.config.http2_keep_alive_while_idle = enabled;
1347 self
1348 }
1349
1350 pub fn tcp_nodelay(mut self, enabled: bool) -> ClientBuilder {
1356 self.config.nodelay = enabled;
1357 self
1358 }
1359
1360 pub fn local_address<T>(mut self, addr: T) -> ClientBuilder
1374 where
1375 T: Into<Option<IpAddr>>,
1376 {
1377 self.config.local_address = addr.into();
1378 self
1379 }
1380
1381 #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
1394 pub fn interface(mut self, interface: &str) -> ClientBuilder {
1395 self.config.interface = Some(interface.to_string());
1396 self
1397 }
1398
1399 pub fn tcp_keepalive<D>(mut self, val: D) -> ClientBuilder
1403 where
1404 D: Into<Option<Duration>>,
1405 {
1406 self.config.tcp_keepalive = val.into();
1407 self
1408 }
1409
1410 #[cfg(feature = "__tls")]
1422 #[cfg_attr(
1423 docsrs,
1424 doc(cfg(any(
1425 feature = "default-tls",
1426 feature = "native-tls",
1427 feature = "rustls-tls"
1428 )))
1429 )]
1430 pub fn add_root_certificate(mut self, cert: Certificate) -> ClientBuilder {
1431 self.config.root_certs.push(cert);
1432 self
1433 }
1434
1435 #[cfg(feature = "__rustls")]
1442 #[cfg_attr(docsrs, doc(cfg(feature = "rustls-tls")))]
1443 pub fn add_crl(mut self, crl: CertificateRevocationList) -> ClientBuilder {
1444 self.config.crls.push(crl);
1445 self
1446 }
1447
1448 #[cfg(feature = "__rustls")]
1455 #[cfg_attr(docsrs, doc(cfg(feature = "rustls-tls")))]
1456 pub fn add_crls(
1457 mut self,
1458 crls: impl IntoIterator<Item = CertificateRevocationList>,
1459 ) -> ClientBuilder {
1460 self.config.crls.extend(crls);
1461 self
1462 }
1463
1464 #[cfg(feature = "__tls")]
1482 #[cfg_attr(
1483 docsrs,
1484 doc(cfg(any(
1485 feature = "default-tls",
1486 feature = "native-tls",
1487 feature = "rustls-tls"
1488 )))
1489 )]
1490 pub fn tls_built_in_root_certs(mut self, tls_built_in_root_certs: bool) -> ClientBuilder {
1491 self.config.tls_built_in_root_certs = tls_built_in_root_certs;
1492
1493 #[cfg(feature = "rustls-tls-webpki-roots-no-provider")]
1494 {
1495 self.config.tls_built_in_certs_webpki = tls_built_in_root_certs;
1496 }
1497
1498 #[cfg(feature = "rustls-tls-native-roots-no-provider")]
1499 {
1500 self.config.tls_built_in_certs_native = tls_built_in_root_certs;
1501 }
1502
1503 self
1504 }
1505
1506 #[cfg(feature = "rustls-tls-webpki-roots-no-provider")]
1510 #[cfg_attr(docsrs, doc(cfg(feature = "rustls-tls-webpki-roots-no-provider")))]
1511 pub fn tls_built_in_webpki_certs(mut self, enabled: bool) -> ClientBuilder {
1512 self.config.tls_built_in_certs_webpki = enabled;
1513 self
1514 }
1515
1516 #[cfg(feature = "rustls-tls-native-roots-no-provider")]
1520 #[cfg_attr(docsrs, doc(cfg(feature = "rustls-tls-native-roots-no-provider")))]
1521 pub fn tls_built_in_native_certs(mut self, enabled: bool) -> ClientBuilder {
1522 self.config.tls_built_in_certs_native = enabled;
1523 self
1524 }
1525
1526 #[cfg(any(feature = "native-tls", feature = "__rustls"))]
1533 #[cfg_attr(docsrs, doc(cfg(any(feature = "native-tls", feature = "rustls-tls"))))]
1534 pub fn identity(mut self, identity: Identity) -> ClientBuilder {
1535 self.config.identity = Some(identity);
1536 self
1537 }
1538
1539 #[cfg(feature = "__tls")]
1555 #[cfg_attr(
1556 docsrs,
1557 doc(cfg(any(
1558 feature = "default-tls",
1559 feature = "native-tls",
1560 feature = "rustls-tls"
1561 )))
1562 )]
1563 pub fn danger_accept_invalid_hostnames(
1564 mut self,
1565 accept_invalid_hostname: bool,
1566 ) -> ClientBuilder {
1567 self.config.hostname_verification = !accept_invalid_hostname;
1568 self
1569 }
1570
1571 #[cfg(feature = "__tls")]
1588 #[cfg_attr(
1589 docsrs,
1590 doc(cfg(any(
1591 feature = "default-tls",
1592 feature = "native-tls",
1593 feature = "rustls-tls"
1594 )))
1595 )]
1596 pub fn danger_accept_invalid_certs(mut self, accept_invalid_certs: bool) -> ClientBuilder {
1597 self.config.certs_verification = !accept_invalid_certs;
1598 self
1599 }
1600
1601 #[cfg(feature = "__tls")]
1610 #[cfg_attr(
1611 docsrs,
1612 doc(cfg(any(
1613 feature = "default-tls",
1614 feature = "native-tls",
1615 feature = "rustls-tls"
1616 )))
1617 )]
1618 pub fn tls_sni(mut self, tls_sni: bool) -> ClientBuilder {
1619 self.config.tls_sni = tls_sni;
1620 self
1621 }
1622
1623 #[cfg(feature = "__tls")]
1639 #[cfg_attr(
1640 docsrs,
1641 doc(cfg(any(
1642 feature = "default-tls",
1643 feature = "native-tls",
1644 feature = "rustls-tls"
1645 )))
1646 )]
1647 pub fn min_tls_version(mut self, version: tls::Version) -> ClientBuilder {
1648 self.config.min_tls_version = Some(version);
1649 self
1650 }
1651
1652 #[cfg(feature = "__tls")]
1671 #[cfg_attr(
1672 docsrs,
1673 doc(cfg(any(
1674 feature = "default-tls",
1675 feature = "native-tls",
1676 feature = "rustls-tls"
1677 )))
1678 )]
1679 pub fn max_tls_version(mut self, version: tls::Version) -> ClientBuilder {
1680 self.config.max_tls_version = Some(version);
1681 self
1682 }
1683
1684 #[cfg(feature = "native-tls")]
1693 #[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))]
1694 pub fn use_native_tls(mut self) -> ClientBuilder {
1695 self.config.tls = TlsBackend::Default;
1696 self
1697 }
1698
1699 #[cfg(feature = "__rustls")]
1708 #[cfg_attr(docsrs, doc(cfg(feature = "rustls-tls")))]
1709 pub fn use_rustls_tls(mut self) -> ClientBuilder {
1710 self.config.tls = TlsBackend::Rustls;
1711 self
1712 }
1713
1714 #[cfg(any(feature = "native-tls", feature = "__rustls",))]
1733 #[cfg_attr(docsrs, doc(cfg(any(feature = "native-tls", feature = "rustls-tls"))))]
1734 pub fn use_preconfigured_tls(mut self, tls: impl Any) -> ClientBuilder {
1735 let mut tls = Some(tls);
1736 #[cfg(feature = "native-tls")]
1737 {
1738 if let Some(conn) = (&mut tls as &mut dyn Any).downcast_mut::<Option<TlsConnector>>() {
1739 let tls = conn.take().expect("is definitely Some");
1740 let tls = crate::tls::TlsBackend::BuiltNativeTls(tls);
1741 self.config.tls = tls;
1742 return self;
1743 }
1744 }
1745 #[cfg(feature = "__rustls")]
1746 {
1747 if let Some(conn) =
1748 (&mut tls as &mut dyn Any).downcast_mut::<Option<rustls::ClientConfig>>()
1749 {
1750 let tls = conn.take().expect("is definitely Some");
1751 let tls = crate::tls::TlsBackend::BuiltRustls(tls);
1752 self.config.tls = tls;
1753 return self;
1754 }
1755 }
1756
1757 self.config.tls = crate::tls::TlsBackend::UnknownPreconfigured;
1759 self
1760 }
1761
1762 #[cfg(feature = "__tls")]
1769 #[cfg_attr(
1770 docsrs,
1771 doc(cfg(any(
1772 feature = "default-tls",
1773 feature = "native-tls",
1774 feature = "rustls-tls"
1775 )))
1776 )]
1777 pub fn tls_info(mut self, tls_info: bool) -> ClientBuilder {
1778 self.config.tls_info = tls_info;
1779 self
1780 }
1781
1782 pub fn https_only(mut self, enabled: bool) -> ClientBuilder {
1786 self.config.https_only = enabled;
1787 self
1788 }
1789
1790 #[doc(hidden)]
1791 #[cfg(feature = "hickory-dns")]
1792 #[cfg_attr(docsrs, doc(cfg(feature = "hickory-dns")))]
1793 #[deprecated(note = "use `hickory_dns` instead")]
1794 pub fn trust_dns(mut self, enable: bool) -> ClientBuilder {
1795 self.config.hickory_dns = enable;
1796 self
1797 }
1798
1799 #[cfg(feature = "hickory-dns")]
1813 #[cfg_attr(docsrs, doc(cfg(feature = "hickory-dns")))]
1814 pub fn hickory_dns(mut self, enable: bool) -> ClientBuilder {
1815 self.config.hickory_dns = enable;
1816 self
1817 }
1818
1819 #[doc(hidden)]
1820 #[deprecated(note = "use `no_hickory_dns` instead")]
1821 pub fn no_trust_dns(self) -> ClientBuilder {
1822 self.no_hickory_dns()
1823 }
1824
1825 pub fn no_hickory_dns(self) -> ClientBuilder {
1831 #[cfg(feature = "hickory-dns")]
1832 {
1833 self.hickory_dns(false)
1834 }
1835
1836 #[cfg(not(feature = "hickory-dns"))]
1837 {
1838 self
1839 }
1840 }
1841
1842 pub fn resolve(self, domain: &str, addr: SocketAddr) -> ClientBuilder {
1851 self.resolve_to_addrs(domain, &[addr])
1852 }
1853
1854 pub fn resolve_to_addrs(mut self, domain: &str, addrs: &[SocketAddr]) -> ClientBuilder {
1863 self.config
1864 .dns_overrides
1865 .insert(domain.to_ascii_lowercase(), addrs.to_vec());
1866 self
1867 }
1868
1869 pub fn dns_resolver<R: Resolve + 'static>(mut self, resolver: Arc<R>) -> ClientBuilder {
1875 self.config.dns_resolver = Some(resolver as _);
1876 self
1877 }
1878
1879 #[cfg(feature = "http3")]
1884 #[cfg_attr(docsrs, doc(cfg(all(reqwest_unstable, feature = "http3",))))]
1885 pub fn tls_early_data(mut self, enabled: bool) -> ClientBuilder {
1886 self.config.tls_enable_early_data = enabled;
1887 self
1888 }
1889
1890 #[cfg(feature = "http3")]
1896 #[cfg_attr(docsrs, doc(cfg(all(reqwest_unstable, feature = "http3",))))]
1897 pub fn http3_max_idle_timeout(mut self, value: Duration) -> ClientBuilder {
1898 self.config.quic_max_idle_timeout = Some(value);
1899 self
1900 }
1901
1902 #[cfg(feature = "http3")]
1913 #[cfg_attr(docsrs, doc(cfg(all(reqwest_unstable, feature = "http3",))))]
1914 pub fn http3_stream_receive_window(mut self, value: u64) -> ClientBuilder {
1915 self.config.quic_stream_receive_window = Some(value.try_into().unwrap());
1916 self
1917 }
1918
1919 #[cfg(feature = "http3")]
1930 #[cfg_attr(docsrs, doc(cfg(all(reqwest_unstable, feature = "http3",))))]
1931 pub fn http3_conn_receive_window(mut self, value: u64) -> ClientBuilder {
1932 self.config.quic_receive_window = Some(value.try_into().unwrap());
1933 self
1934 }
1935
1936 #[cfg(feature = "http3")]
1942 #[cfg_attr(docsrs, doc(cfg(all(reqwest_unstable, feature = "http3",))))]
1943 pub fn http3_send_window(mut self, value: u64) -> ClientBuilder {
1944 self.config.quic_send_window = Some(value);
1945 self
1946 }
1947}
1948
1949type HyperClient = hyper_util::client::legacy::Client<Connector, super::Body>;
1950
1951impl Default for Client {
1952 fn default() -> Self {
1953 Self::new()
1954 }
1955}
1956
1957impl Client {
1958 pub fn new() -> Client {
1968 ClientBuilder::new().build().expect("Client::new()")
1969 }
1970
1971 pub fn builder() -> ClientBuilder {
1975 ClientBuilder::new()
1976 }
1977
1978 pub fn get<U: IntoUrl>(&self, url: U) -> RequestBuilder {
1984 self.request(Method::GET, url)
1985 }
1986
1987 pub fn post<U: IntoUrl>(&self, url: U) -> RequestBuilder {
1993 self.request(Method::POST, url)
1994 }
1995
1996 pub fn put<U: IntoUrl>(&self, url: U) -> RequestBuilder {
2002 self.request(Method::PUT, url)
2003 }
2004
2005 pub fn patch<U: IntoUrl>(&self, url: U) -> RequestBuilder {
2011 self.request(Method::PATCH, url)
2012 }
2013
2014 pub fn delete<U: IntoUrl>(&self, url: U) -> RequestBuilder {
2020 self.request(Method::DELETE, url)
2021 }
2022
2023 pub fn head<U: IntoUrl>(&self, url: U) -> RequestBuilder {
2029 self.request(Method::HEAD, url)
2030 }
2031
2032 pub fn request<U: IntoUrl>(&self, method: Method, url: U) -> RequestBuilder {
2041 let req = url.into_url().map(move |url| Request::new(method, url));
2042 RequestBuilder::new(self.clone(), req)
2043 }
2044
2045 pub fn execute(
2058 &self,
2059 request: Request,
2060 ) -> impl Future<Output = Result<Response, crate::Error>> {
2061 self.execute_request(request)
2062 }
2063
2064 pub(super) fn execute_request(&self, req: Request) -> Pending {
2065 let (method, url, mut headers, body, timeout, version) = req.pieces();
2066 if url.scheme() != "http" && url.scheme() != "https" {
2067 return Pending::new_err(error::url_bad_scheme(url));
2068 }
2069
2070 if self.inner.https_only && url.scheme() != "https" {
2072 return Pending::new_err(error::url_bad_scheme(url));
2073 }
2074
2075 for (key, value) in &self.inner.headers {
2078 if let Entry::Vacant(entry) = headers.entry(key) {
2079 entry.insert(value.clone());
2080 }
2081 }
2082
2083 #[cfg(feature = "cookies")]
2085 {
2086 if let Some(cookie_store) = self.inner.cookie_store.as_ref() {
2087 if headers.get(crate::header::COOKIE).is_none() {
2088 add_cookie_header(&mut headers, &**cookie_store, &url);
2089 }
2090 }
2091 }
2092
2093 let accept_encoding = self.inner.accepts.as_str();
2094
2095 if let Some(accept_encoding) = accept_encoding {
2096 if !headers.contains_key(ACCEPT_ENCODING) && !headers.contains_key(RANGE) {
2097 headers.insert(ACCEPT_ENCODING, HeaderValue::from_static(accept_encoding));
2098 }
2099 }
2100
2101 let uri = match try_uri(&url) {
2102 Ok(uri) => uri,
2103 _ => return Pending::new_err(error::url_invalid_uri(url)),
2104 };
2105
2106 let (reusable, body) = match body {
2107 Some(body) => {
2108 let (reusable, body) = body.try_reuse();
2109 (Some(reusable), body)
2110 }
2111 None => (None, Body::empty()),
2112 };
2113
2114 self.proxy_auth(&uri, &mut headers);
2115
2116 let builder = hyper::Request::builder()
2117 .method(method.clone())
2118 .uri(uri)
2119 .version(version);
2120
2121 let in_flight = match version {
2122 #[cfg(feature = "http3")]
2123 http::Version::HTTP_3 if self.inner.h3_client.is_some() => {
2124 let mut req = builder.body(body).expect("valid request parts");
2125 *req.headers_mut() = headers.clone();
2126 ResponseFuture::H3(self.inner.h3_client.as_ref().unwrap().request(req))
2127 }
2128 _ => {
2129 let mut req = builder.body(body).expect("valid request parts");
2130 *req.headers_mut() = headers.clone();
2131 ResponseFuture::Default(self.inner.hyper.request(req))
2132 }
2133 };
2134
2135 let total_timeout = timeout
2136 .or(self.inner.request_timeout)
2137 .map(tokio::time::sleep)
2138 .map(Box::pin);
2139
2140 let read_timeout_fut = self
2141 .inner
2142 .read_timeout
2143 .map(tokio::time::sleep)
2144 .map(Box::pin);
2145
2146 Pending {
2147 inner: PendingInner::Request(PendingRequest {
2148 method,
2149 url,
2150 headers,
2151 body: reusable,
2152
2153 urls: Vec::new(),
2154
2155 retry_count: 0,
2156
2157 client: self.inner.clone(),
2158
2159 in_flight,
2160 total_timeout,
2161 read_timeout_fut,
2162 read_timeout: self.inner.read_timeout,
2163 }),
2164 }
2165 }
2166
2167 fn proxy_auth(&self, dst: &Uri, headers: &mut HeaderMap) {
2168 if !self.inner.proxies_maybe_http_auth {
2169 return;
2170 }
2171
2172 if dst.scheme() != Some(&Scheme::HTTP) {
2176 return;
2177 }
2178
2179 if headers.contains_key(PROXY_AUTHORIZATION) {
2180 return;
2181 }
2182
2183 for proxy in self.inner.proxies.iter() {
2184 if proxy.is_match(dst) {
2185 if let Some(header) = proxy.http_basic_auth(dst) {
2186 headers.insert(PROXY_AUTHORIZATION, header);
2187 }
2188
2189 break;
2190 }
2191 }
2192 }
2193}
2194
2195impl fmt::Debug for Client {
2196 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2197 let mut builder = f.debug_struct("Client");
2198 self.inner.fmt_fields(&mut builder);
2199 builder.finish()
2200 }
2201}
2202
2203impl tower_service::Service<Request> for Client {
2204 type Response = Response;
2205 type Error = crate::Error;
2206 type Future = Pending;
2207
2208 fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
2209 Poll::Ready(Ok(()))
2210 }
2211
2212 fn call(&mut self, req: Request) -> Self::Future {
2213 self.execute_request(req)
2214 }
2215}
2216
2217impl tower_service::Service<Request> for &'_ Client {
2218 type Response = Response;
2219 type Error = crate::Error;
2220 type Future = Pending;
2221
2222 fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
2223 Poll::Ready(Ok(()))
2224 }
2225
2226 fn call(&mut self, req: Request) -> Self::Future {
2227 self.execute_request(req)
2228 }
2229}
2230
2231impl fmt::Debug for ClientBuilder {
2232 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2233 let mut builder = f.debug_struct("ClientBuilder");
2234 self.config.fmt_fields(&mut builder);
2235 builder.finish()
2236 }
2237}
2238
2239impl Config {
2240 fn fmt_fields(&self, f: &mut fmt::DebugStruct<'_, '_>) {
2241 #[cfg(feature = "cookies")]
2245 {
2246 if let Some(_) = self.cookie_store {
2247 f.field("cookie_store", &true);
2248 }
2249 }
2250
2251 f.field("accepts", &self.accepts);
2252
2253 if !self.proxies.is_empty() {
2254 f.field("proxies", &self.proxies);
2255 }
2256
2257 if !self.redirect_policy.is_default() {
2258 f.field("redirect_policy", &self.redirect_policy);
2259 }
2260
2261 if self.referer {
2262 f.field("referer", &true);
2263 }
2264
2265 f.field("default_headers", &self.headers);
2266
2267 if self.http1_title_case_headers {
2268 f.field("http1_title_case_headers", &true);
2269 }
2270
2271 if self.http1_allow_obsolete_multiline_headers_in_responses {
2272 f.field("http1_allow_obsolete_multiline_headers_in_responses", &true);
2273 }
2274
2275 if self.http1_ignore_invalid_headers_in_responses {
2276 f.field("http1_ignore_invalid_headers_in_responses", &true);
2277 }
2278
2279 if self.http1_allow_spaces_after_header_name_in_responses {
2280 f.field("http1_allow_spaces_after_header_name_in_responses", &true);
2281 }
2282
2283 if matches!(self.http_version_pref, HttpVersionPref::Http1) {
2284 f.field("http1_only", &true);
2285 }
2286
2287 #[cfg(feature = "http2")]
2288 if matches!(self.http_version_pref, HttpVersionPref::Http2) {
2289 f.field("http2_prior_knowledge", &true);
2290 }
2291
2292 if let Some(ref d) = self.connect_timeout {
2293 f.field("connect_timeout", d);
2294 }
2295
2296 if let Some(ref d) = self.timeout {
2297 f.field("timeout", d);
2298 }
2299
2300 if let Some(ref v) = self.local_address {
2301 f.field("local_address", v);
2302 }
2303
2304 #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
2305 if let Some(ref v) = self.interface {
2306 f.field("interface", v);
2307 }
2308
2309 if self.nodelay {
2310 f.field("tcp_nodelay", &true);
2311 }
2312
2313 #[cfg(feature = "__tls")]
2314 {
2315 if !self.hostname_verification {
2316 f.field("danger_accept_invalid_hostnames", &true);
2317 }
2318 }
2319
2320 #[cfg(feature = "__tls")]
2321 {
2322 if !self.certs_verification {
2323 f.field("danger_accept_invalid_certs", &true);
2324 }
2325
2326 if let Some(ref min_tls_version) = self.min_tls_version {
2327 f.field("min_tls_version", min_tls_version);
2328 }
2329
2330 if let Some(ref max_tls_version) = self.max_tls_version {
2331 f.field("max_tls_version", max_tls_version);
2332 }
2333
2334 f.field("tls_sni", &self.tls_sni);
2335
2336 f.field("tls_info", &self.tls_info);
2337 }
2338
2339 #[cfg(all(feature = "default-tls", feature = "__rustls"))]
2340 {
2341 f.field("tls_backend", &self.tls);
2342 }
2343
2344 if !self.dns_overrides.is_empty() {
2345 f.field("dns_overrides", &self.dns_overrides);
2346 }
2347
2348 #[cfg(feature = "http3")]
2349 {
2350 if self.tls_enable_early_data {
2351 f.field("tls_enable_early_data", &true);
2352 }
2353 }
2354 }
2355}
2356
2357struct ClientRef {
2358 accepts: Accepts,
2359 #[cfg(feature = "cookies")]
2360 cookie_store: Option<Arc<dyn cookie::CookieStore>>,
2361 headers: HeaderMap,
2362 hyper: HyperClient,
2363 #[cfg(feature = "http3")]
2364 h3_client: Option<H3Client>,
2365 redirect_policy: redirect::Policy,
2366 referer: bool,
2367 request_timeout: Option<Duration>,
2368 read_timeout: Option<Duration>,
2369 proxies: Arc<Vec<Proxy>>,
2370 proxies_maybe_http_auth: bool,
2371 https_only: bool,
2372}
2373
2374impl ClientRef {
2375 fn fmt_fields(&self, f: &mut fmt::DebugStruct<'_, '_>) {
2376 #[cfg(feature = "cookies")]
2380 {
2381 if let Some(_) = self.cookie_store {
2382 f.field("cookie_store", &true);
2383 }
2384 }
2385
2386 f.field("accepts", &self.accepts);
2387
2388 if !self.proxies.is_empty() {
2389 f.field("proxies", &self.proxies);
2390 }
2391
2392 if !self.redirect_policy.is_default() {
2393 f.field("redirect_policy", &self.redirect_policy);
2394 }
2395
2396 if self.referer {
2397 f.field("referer", &true);
2398 }
2399
2400 f.field("default_headers", &self.headers);
2401
2402 if let Some(ref d) = self.request_timeout {
2403 f.field("timeout", d);
2404 }
2405
2406 if let Some(ref d) = self.read_timeout {
2407 f.field("read_timeout", d);
2408 }
2409 }
2410}
2411
2412pin_project! {
2413 pub struct Pending {
2414 #[pin]
2415 inner: PendingInner,
2416 }
2417}
2418
2419enum PendingInner {
2420 Request(PendingRequest),
2421 Error(Option<crate::Error>),
2422}
2423
2424pin_project! {
2425 struct PendingRequest {
2426 method: Method,
2427 url: Url,
2428 headers: HeaderMap,
2429 body: Option<Option<Bytes>>,
2430
2431 urls: Vec<Url>,
2432
2433 retry_count: usize,
2434
2435 client: Arc<ClientRef>,
2436
2437 #[pin]
2438 in_flight: ResponseFuture,
2439 #[pin]
2440 total_timeout: Option<Pin<Box<Sleep>>>,
2441 #[pin]
2442 read_timeout_fut: Option<Pin<Box<Sleep>>>,
2443 read_timeout: Option<Duration>,
2444 }
2445}
2446
2447enum ResponseFuture {
2448 Default(HyperResponseFuture),
2449 #[cfg(feature = "http3")]
2450 H3(H3ResponseFuture),
2451}
2452
2453impl PendingRequest {
2454 fn in_flight(self: Pin<&mut Self>) -> Pin<&mut ResponseFuture> {
2455 self.project().in_flight
2456 }
2457
2458 fn total_timeout(self: Pin<&mut Self>) -> Pin<&mut Option<Pin<Box<Sleep>>>> {
2459 self.project().total_timeout
2460 }
2461
2462 fn read_timeout(self: Pin<&mut Self>) -> Pin<&mut Option<Pin<Box<Sleep>>>> {
2463 self.project().read_timeout_fut
2464 }
2465
2466 fn urls(self: Pin<&mut Self>) -> &mut Vec<Url> {
2467 self.project().urls
2468 }
2469
2470 fn headers(self: Pin<&mut Self>) -> &mut HeaderMap {
2471 self.project().headers
2472 }
2473
2474 #[cfg(any(feature = "http2", feature = "http3"))]
2475 fn retry_error(mut self: Pin<&mut Self>, err: &(dyn std::error::Error + 'static)) -> bool {
2476 use log::trace;
2477
2478 if !is_retryable_error(err) {
2479 return false;
2480 }
2481
2482 trace!("can retry {err:?}");
2483
2484 let body = match self.body {
2485 Some(Some(ref body)) => Body::reusable(body.clone()),
2486 Some(None) => {
2487 debug!("error was retryable, but body not reusable");
2488 return false;
2489 }
2490 None => Body::empty(),
2491 };
2492
2493 if self.retry_count >= 2 {
2494 trace!("retry count too high");
2495 return false;
2496 }
2497 self.retry_count += 1;
2498
2499 let uri = try_uri(&self.url).expect("URL was already validated as URI");
2501
2502 *self.as_mut().in_flight().get_mut() = match *self.as_mut().in_flight().as_ref() {
2503 #[cfg(feature = "http3")]
2504 ResponseFuture::H3(_) => {
2505 let mut req = hyper::Request::builder()
2506 .method(self.method.clone())
2507 .uri(uri)
2508 .body(body)
2509 .expect("valid request parts");
2510 *req.headers_mut() = self.headers.clone();
2511 ResponseFuture::H3(
2512 self.client
2513 .h3_client
2514 .as_ref()
2515 .expect("H3 client must exists, otherwise we can't have a h3 request here")
2516 .request(req),
2517 )
2518 }
2519 _ => {
2520 let mut req = hyper::Request::builder()
2521 .method(self.method.clone())
2522 .uri(uri)
2523 .body(body)
2524 .expect("valid request parts");
2525 *req.headers_mut() = self.headers.clone();
2526 ResponseFuture::Default(self.client.hyper.request(req))
2527 }
2528 };
2529
2530 true
2531 }
2532}
2533
2534#[cfg(any(feature = "http2", feature = "http3"))]
2535fn is_retryable_error(err: &(dyn std::error::Error + 'static)) -> bool {
2536 let err = if let Some(err) = err.source() {
2538 err
2539 } else {
2540 return false;
2541 };
2542
2543 #[cfg(feature = "http3")]
2544 if let Some(cause) = err.source() {
2545 if let Some(err) = cause.downcast_ref::<h3::Error>() {
2546 debug!("determining if HTTP/3 error {err} can be retried");
2547 return err.to_string().as_str() == "timeout";
2549 }
2550 }
2551
2552 #[cfg(feature = "http2")]
2553 if let Some(cause) = err.source() {
2554 if let Some(err) = cause.downcast_ref::<h2::Error>() {
2555 if err.is_go_away() && err.is_remote() && err.reason() == Some(h2::Reason::NO_ERROR) {
2557 return true;
2558 }
2559
2560 if err.is_reset() && err.is_remote() && err.reason() == Some(h2::Reason::REFUSED_STREAM)
2563 {
2564 return true;
2565 }
2566 }
2567 }
2568 false
2569}
2570
2571impl Pending {
2572 pub(super) fn new_err(err: crate::Error) -> Pending {
2573 Pending {
2574 inner: PendingInner::Error(Some(err)),
2575 }
2576 }
2577
2578 fn inner(self: Pin<&mut Self>) -> Pin<&mut PendingInner> {
2579 self.project().inner
2580 }
2581}
2582
2583impl Future for Pending {
2584 type Output = Result<Response, crate::Error>;
2585
2586 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
2587 let inner = self.inner();
2588 match inner.get_mut() {
2589 PendingInner::Request(ref mut req) => Pin::new(req).poll(cx),
2590 PendingInner::Error(ref mut err) => Poll::Ready(Err(err
2591 .take()
2592 .expect("Pending error polled more than once"))),
2593 }
2594 }
2595}
2596
2597impl Future for PendingRequest {
2598 type Output = Result<Response, crate::Error>;
2599
2600 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
2601 if let Some(delay) = self.as_mut().total_timeout().as_mut().as_pin_mut() {
2602 if let Poll::Ready(()) = delay.poll(cx) {
2603 return Poll::Ready(Err(
2604 crate::error::request(crate::error::TimedOut).with_url(self.url.clone())
2605 ));
2606 }
2607 }
2608
2609 if let Some(delay) = self.as_mut().read_timeout().as_mut().as_pin_mut() {
2610 if let Poll::Ready(()) = delay.poll(cx) {
2611 return Poll::Ready(Err(
2612 crate::error::request(crate::error::TimedOut).with_url(self.url.clone())
2613 ));
2614 }
2615 }
2616
2617 loop {
2618 let res = match self.as_mut().in_flight().get_mut() {
2619 ResponseFuture::Default(r) => match Pin::new(r).poll(cx) {
2620 Poll::Ready(Err(e)) => {
2621 #[cfg(feature = "http2")]
2622 if self.as_mut().retry_error(&e) {
2623 continue;
2624 }
2625 return Poll::Ready(Err(
2626 crate::error::request(e).with_url(self.url.clone())
2627 ));
2628 }
2629 Poll::Ready(Ok(res)) => res.map(super::body::boxed),
2630 Poll::Pending => return Poll::Pending,
2631 },
2632 #[cfg(feature = "http3")]
2633 ResponseFuture::H3(r) => match Pin::new(r).poll(cx) {
2634 Poll::Ready(Err(e)) => {
2635 if self.as_mut().retry_error(&e) {
2636 continue;
2637 }
2638 return Poll::Ready(Err(
2639 crate::error::request(e).with_url(self.url.clone())
2640 ));
2641 }
2642 Poll::Ready(Ok(res)) => res,
2643 Poll::Pending => return Poll::Pending,
2644 },
2645 };
2646
2647 #[cfg(feature = "cookies")]
2648 {
2649 if let Some(ref cookie_store) = self.client.cookie_store {
2650 let mut cookies =
2651 cookie::extract_response_cookie_headers(&res.headers()).peekable();
2652 if cookies.peek().is_some() {
2653 cookie_store.set_cookies(&mut cookies, &self.url);
2654 }
2655 }
2656 }
2657 let should_redirect = match res.status() {
2658 StatusCode::MOVED_PERMANENTLY | StatusCode::FOUND | StatusCode::SEE_OTHER => {
2659 self.body = None;
2660 for header in &[
2661 TRANSFER_ENCODING,
2662 CONTENT_ENCODING,
2663 CONTENT_TYPE,
2664 CONTENT_LENGTH,
2665 ] {
2666 self.headers.remove(header);
2667 }
2668
2669 match self.method {
2670 Method::GET | Method::HEAD => {}
2671 _ => {
2672 self.method = Method::GET;
2673 }
2674 }
2675 true
2676 }
2677 StatusCode::TEMPORARY_REDIRECT | StatusCode::PERMANENT_REDIRECT => {
2678 match self.body {
2679 Some(Some(_)) | None => true,
2680 Some(None) => false,
2681 }
2682 }
2683 _ => false,
2684 };
2685 if should_redirect {
2686 let loc = res.headers().get(LOCATION).and_then(|val| {
2687 let loc = (|| -> Option<Url> {
2688 self.url.join(str::from_utf8(val.as_bytes()).ok()?).ok()
2692 })();
2693
2694 let loc = loc.and_then(|url| {
2698 if try_uri(&url).is_ok() {
2699 Some(url)
2700 } else {
2701 None
2702 }
2703 });
2704
2705 if loc.is_none() {
2706 debug!("Location header had invalid URI: {val:?}");
2707 }
2708 loc
2709 });
2710 if let Some(loc) = loc {
2711 if self.client.referer {
2712 if let Some(referer) = make_referer(&loc, &self.url) {
2713 self.headers.insert(REFERER, referer);
2714 }
2715 }
2716 let url = self.url.clone();
2717 self.as_mut().urls().push(url);
2718 let action = self
2719 .client
2720 .redirect_policy
2721 .check(res.status(), &loc, &self.urls);
2722
2723 match action {
2724 redirect::ActionKind::Follow => {
2725 debug!("redirecting '{}' to '{}'", self.url, loc);
2726
2727 if loc.scheme() != "http" && loc.scheme() != "https" {
2728 return Poll::Ready(Err(error::url_bad_scheme(loc)));
2729 }
2730
2731 if self.client.https_only && loc.scheme() != "https" {
2732 return Poll::Ready(Err(error::redirect(
2733 error::url_bad_scheme(loc.clone()),
2734 loc,
2735 )));
2736 }
2737
2738 self.url = loc;
2739 let mut headers =
2740 std::mem::replace(self.as_mut().headers(), HeaderMap::new());
2741
2742 remove_sensitive_headers(&mut headers, &self.url, &self.urls);
2743 let uri = try_uri(&self.url)?;
2744 let body = match self.body {
2745 Some(Some(ref body)) => Body::reusable(body.clone()),
2746 _ => Body::empty(),
2747 };
2748
2749 #[cfg(feature = "cookies")]
2751 {
2752 if let Some(ref cookie_store) = self.client.cookie_store {
2753 add_cookie_header(&mut headers, &**cookie_store, &self.url);
2754 }
2755 }
2756
2757 *self.as_mut().in_flight().get_mut() =
2758 match *self.as_mut().in_flight().as_ref() {
2759 #[cfg(feature = "http3")]
2760 ResponseFuture::H3(_) => {
2761 let mut req = hyper::Request::builder()
2762 .method(self.method.clone())
2763 .uri(uri.clone())
2764 .body(body)
2765 .expect("valid request parts");
2766 *req.headers_mut() = headers.clone();
2767 std::mem::swap(self.as_mut().headers(), &mut headers);
2768 ResponseFuture::H3(self.client.h3_client
2769 .as_ref()
2770 .expect("H3 client must exists, otherwise we can't have a h3 request here")
2771 .request(req))
2772 }
2773 _ => {
2774 let mut req = hyper::Request::builder()
2775 .method(self.method.clone())
2776 .uri(uri.clone())
2777 .body(body)
2778 .expect("valid request parts");
2779 *req.headers_mut() = headers.clone();
2780 std::mem::swap(self.as_mut().headers(), &mut headers);
2781 ResponseFuture::Default(self.client.hyper.request(req))
2782 }
2783 };
2784
2785 continue;
2786 }
2787 redirect::ActionKind::Stop => {
2788 debug!("redirect policy disallowed redirection to '{loc}'");
2789 }
2790 redirect::ActionKind::Error(err) => {
2791 return Poll::Ready(Err(crate::error::redirect(err, self.url.clone())));
2792 }
2793 }
2794 }
2795 }
2796
2797 let res = Response::new(
2798 res,
2799 self.url.clone(),
2800 self.client.accepts,
2801 self.total_timeout.take(),
2802 self.read_timeout,
2803 );
2804 return Poll::Ready(Ok(res));
2805 }
2806 }
2807}
2808
2809impl fmt::Debug for Pending {
2810 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2811 match self.inner {
2812 PendingInner::Request(ref req) => f
2813 .debug_struct("Pending")
2814 .field("method", &req.method)
2815 .field("url", &req.url)
2816 .finish(),
2817 PendingInner::Error(ref err) => f.debug_struct("Pending").field("error", err).finish(),
2818 }
2819 }
2820}
2821
2822fn make_referer(next: &Url, previous: &Url) -> Option<HeaderValue> {
2823 if next.scheme() == "http" && previous.scheme() == "https" {
2824 return None;
2825 }
2826
2827 let mut referer = previous.clone();
2828 let _ = referer.set_username("");
2829 let _ = referer.set_password(None);
2830 referer.set_fragment(None);
2831 referer.as_str().parse().ok()
2832}
2833
2834#[cfg(feature = "cookies")]
2835fn add_cookie_header(headers: &mut HeaderMap, cookie_store: &dyn cookie::CookieStore, url: &Url) {
2836 if let Some(header) = cookie_store.cookies(url) {
2837 headers.insert(crate::header::COOKIE, header);
2838 }
2839}
2840
2841#[cfg(test)]
2842mod tests {
2843 #![cfg(not(feature = "rustls-tls-manual-roots-no-provider"))]
2844
2845 #[tokio::test]
2846 async fn execute_request_rejects_invalid_urls() {
2847 let url_str = "hxxps://www.rust-lang.org/";
2848 let url = url::Url::parse(url_str).unwrap();
2849 let result = crate::get(url.clone()).await;
2850
2851 assert!(result.is_err());
2852 let err = result.err().unwrap();
2853 assert!(err.is_builder());
2854 assert_eq!(url_str, err.url().unwrap().as_str());
2855 }
2856
2857 #[tokio::test]
2859 async fn execute_request_rejects_invalid_hostname() {
2860 let url_str = "https://{{hostname}}/";
2861 let url = url::Url::parse(url_str).unwrap();
2862 let result = crate::get(url.clone()).await;
2863
2864 assert!(result.is_err());
2865 let err = result.err().unwrap();
2866 assert!(err.is_builder());
2867 assert_eq!(url_str, err.url().unwrap().as_str());
2868 }
2869}