summaryrefslogtreecommitdiffstats
path: root/vendor/reqwest/src/async_impl/client.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/reqwest/src/async_impl/client.rs')
-rw-r--r--vendor/reqwest/src/async_impl/client.rs2352
1 files changed, 2352 insertions, 0 deletions
diff --git a/vendor/reqwest/src/async_impl/client.rs b/vendor/reqwest/src/async_impl/client.rs
new file mode 100644
index 000000000..559e5f365
--- /dev/null
+++ b/vendor/reqwest/src/async_impl/client.rs
@@ -0,0 +1,2352 @@
+#[cfg(any(feature = "native-tls", feature = "__rustls",))]
+use std::any::Any;
+use std::net::IpAddr;
+use std::sync::Arc;
+use std::time::Duration;
+use std::{collections::HashMap, convert::TryInto, net::SocketAddr};
+use std::{fmt, str};
+
+use bytes::Bytes;
+use http::header::{
+ Entry, HeaderMap, HeaderValue, ACCEPT, ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_LENGTH,
+ CONTENT_TYPE, LOCATION, PROXY_AUTHORIZATION, RANGE, REFERER, TRANSFER_ENCODING, USER_AGENT,
+};
+use http::uri::Scheme;
+use http::Uri;
+use hyper::client::{HttpConnector, ResponseFuture as HyperResponseFuture};
+#[cfg(feature = "native-tls-crate")]
+use native_tls_crate::TlsConnector;
+use pin_project_lite::pin_project;
+use std::future::Future;
+use std::pin::Pin;
+use std::task::{Context, Poll};
+use tokio::time::Sleep;
+
+use super::decoder::Accepts;
+use super::request::{Request, RequestBuilder};
+use super::response::Response;
+use super::Body;
+#[cfg(feature = "http3")]
+use crate::async_impl::h3_client::connect::H3Connector;
+#[cfg(feature = "http3")]
+use crate::async_impl::h3_client::{H3Client, H3ResponseFuture};
+use crate::connect::Connector;
+#[cfg(feature = "cookies")]
+use crate::cookie;
+#[cfg(feature = "trust-dns")]
+use crate::dns::trust_dns::TrustDnsResolver;
+use crate::dns::{gai::GaiResolver, DnsResolverWithOverrides, DynResolver, Resolve};
+use crate::error;
+use crate::into_url::{expect_uri, try_uri};
+use crate::redirect::{self, remove_sensitive_headers};
+#[cfg(feature = "__tls")]
+use crate::tls::{self, TlsBackend};
+#[cfg(feature = "__tls")]
+use crate::Certificate;
+#[cfg(any(feature = "native-tls", feature = "__rustls"))]
+use crate::Identity;
+use crate::{IntoUrl, Method, Proxy, StatusCode, Url};
+use log::{debug, trace};
+#[cfg(feature = "http3")]
+use quinn::TransportConfig;
+#[cfg(feature = "http3")]
+use quinn::VarInt;
+
+/// An asynchronous `Client` to make Requests with.
+///
+/// The Client has various configuration values to tweak, but the defaults
+/// are set to what is usually the most commonly desired value. To configure a
+/// `Client`, use `Client::builder()`.
+///
+/// The `Client` holds a connection pool internally, so it is advised that
+/// you create one and **reuse** it.
+///
+/// You do **not** have to wrap the `Client` in an [`Rc`] or [`Arc`] to **reuse** it,
+/// because it already uses an [`Arc`] internally.
+///
+/// [`Rc`]: std::rc::Rc
+#[derive(Clone)]
+pub struct Client {
+ inner: Arc<ClientRef>,
+}
+
+/// A `ClientBuilder` can be used to create a `Client` with custom configuration.
+#[must_use]
+pub struct ClientBuilder {
+ config: Config,
+}
+
+enum HttpVersionPref {
+ Http1,
+ Http2,
+ #[cfg(feature = "http3")]
+ Http3,
+ All,
+}
+
+struct Config {
+ // NOTE: When adding a new field, update `fmt::Debug for ClientBuilder`
+ accepts: Accepts,
+ headers: HeaderMap,
+ #[cfg(feature = "native-tls")]
+ hostname_verification: bool,
+ #[cfg(feature = "__tls")]
+ certs_verification: bool,
+ #[cfg(feature = "__tls")]
+ tls_sni: bool,
+ connect_timeout: Option<Duration>,
+ connection_verbose: bool,
+ pool_idle_timeout: Option<Duration>,
+ pool_max_idle_per_host: usize,
+ tcp_keepalive: Option<Duration>,
+ #[cfg(any(feature = "native-tls", feature = "__rustls"))]
+ identity: Option<Identity>,
+ proxies: Vec<Proxy>,
+ auto_sys_proxy: bool,
+ redirect_policy: redirect::Policy,
+ referer: bool,
+ timeout: Option<Duration>,
+ #[cfg(feature = "__tls")]
+ root_certs: Vec<Certificate>,
+ #[cfg(feature = "__tls")]
+ tls_built_in_root_certs: bool,
+ #[cfg(feature = "__tls")]
+ min_tls_version: Option<tls::Version>,
+ #[cfg(feature = "__tls")]
+ max_tls_version: Option<tls::Version>,
+ #[cfg(feature = "__tls")]
+ tls: TlsBackend,
+ http_version_pref: HttpVersionPref,
+ http09_responses: bool,
+ http1_title_case_headers: bool,
+ http1_allow_obsolete_multiline_headers_in_responses: bool,
+ http2_initial_stream_window_size: Option<u32>,
+ http2_initial_connection_window_size: Option<u32>,
+ http2_adaptive_window: bool,
+ http2_max_frame_size: Option<u32>,
+ http2_keep_alive_interval: Option<Duration>,
+ http2_keep_alive_timeout: Option<Duration>,
+ http2_keep_alive_while_idle: bool,
+ local_address: Option<IpAddr>,
+ nodelay: bool,
+ #[cfg(feature = "cookies")]
+ cookie_store: Option<Arc<dyn cookie::CookieStore>>,
+ trust_dns: bool,
+ error: Option<crate::Error>,
+ https_only: bool,
+ #[cfg(feature = "http3")]
+ tls_enable_early_data: bool,
+ #[cfg(feature = "http3")]
+ quic_max_idle_timeout: Option<Duration>,
+ #[cfg(feature = "http3")]
+ quic_stream_receive_window: Option<VarInt>,
+ #[cfg(feature = "http3")]
+ quic_receive_window: Option<VarInt>,
+ #[cfg(feature = "http3")]
+ quic_send_window: Option<u64>,
+ dns_overrides: HashMap<String, Vec<SocketAddr>>,
+ dns_resolver: Option<Arc<dyn Resolve>>,
+}
+
+impl Default for ClientBuilder {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl ClientBuilder {
+ /// Constructs a new `ClientBuilder`.
+ ///
+ /// This is the same as `Client::builder()`.
+ pub fn new() -> ClientBuilder {
+ let mut headers: HeaderMap<HeaderValue> = HeaderMap::with_capacity(2);
+ headers.insert(ACCEPT, HeaderValue::from_static("*/*"));
+
+ ClientBuilder {
+ config: Config {
+ error: None,
+ accepts: Accepts::default(),
+ headers,
+ #[cfg(feature = "native-tls")]
+ hostname_verification: true,
+ #[cfg(feature = "__tls")]
+ certs_verification: true,
+ #[cfg(feature = "__tls")]
+ tls_sni: true,
+ connect_timeout: None,
+ connection_verbose: false,
+ pool_idle_timeout: Some(Duration::from_secs(90)),
+ pool_max_idle_per_host: std::usize::MAX,
+ // TODO: Re-enable default duration once hyper's HttpConnector is fixed
+ // to no longer error when an option fails.
+ tcp_keepalive: None, //Some(Duration::from_secs(60)),
+ proxies: Vec::new(),
+ auto_sys_proxy: true,
+ redirect_policy: redirect::Policy::default(),
+ referer: true,
+ timeout: None,
+ #[cfg(feature = "__tls")]
+ root_certs: Vec::new(),
+ #[cfg(feature = "__tls")]
+ tls_built_in_root_certs: true,
+ #[cfg(any(feature = "native-tls", feature = "__rustls"))]
+ identity: None,
+ #[cfg(feature = "__tls")]
+ min_tls_version: None,
+ #[cfg(feature = "__tls")]
+ max_tls_version: None,
+ #[cfg(feature = "__tls")]
+ tls: TlsBackend::default(),
+ http_version_pref: HttpVersionPref::All,
+ http09_responses: false,
+ http1_title_case_headers: false,
+ http1_allow_obsolete_multiline_headers_in_responses: false,
+ http2_initial_stream_window_size: None,
+ http2_initial_connection_window_size: None,
+ http2_adaptive_window: false,
+ http2_max_frame_size: None,
+ http2_keep_alive_interval: None,
+ http2_keep_alive_timeout: None,
+ http2_keep_alive_while_idle: false,
+ local_address: None,
+ nodelay: true,
+ trust_dns: cfg!(feature = "trust-dns"),
+ #[cfg(feature = "cookies")]
+ cookie_store: None,
+ https_only: false,
+ dns_overrides: HashMap::new(),
+ #[cfg(feature = "http3")]
+ tls_enable_early_data: false,
+ #[cfg(feature = "http3")]
+ quic_max_idle_timeout: None,
+ #[cfg(feature = "http3")]
+ quic_stream_receive_window: None,
+ #[cfg(feature = "http3")]
+ quic_receive_window: None,
+ #[cfg(feature = "http3")]
+ quic_send_window: None,
+ dns_resolver: None,
+ },
+ }
+ }
+
+ /// Returns a `Client` that uses this `ClientBuilder` configuration.
+ ///
+ /// # Errors
+ ///
+ /// This method fails if a TLS backend cannot be initialized, or the resolver
+ /// cannot load the system configuration.
+ pub fn build(self) -> crate::Result<Client> {
+ let config = self.config;
+
+ if let Some(err) = config.error {
+ return Err(err);
+ }
+
+ let mut proxies = config.proxies;
+ if config.auto_sys_proxy {
+ proxies.push(Proxy::system());
+ }
+ let proxies = Arc::new(proxies);
+
+ #[allow(unused)]
+ #[cfg(feature = "http3")]
+ let mut h3_connector = None;
+
+ let mut connector = {
+ #[cfg(feature = "__tls")]
+ fn user_agent(headers: &HeaderMap) -> Option<HeaderValue> {
+ headers.get(USER_AGENT).cloned()
+ }
+
+ let mut resolver: Arc<dyn Resolve> = match config.trust_dns {
+ false => Arc::new(GaiResolver::new()),
+ #[cfg(feature = "trust-dns")]
+ true => Arc::new(TrustDnsResolver::new().map_err(crate::error::builder)?),
+ #[cfg(not(feature = "trust-dns"))]
+ true => unreachable!("trust-dns shouldn't be enabled unless the feature is"),
+ };
+ if let Some(dns_resolver) = config.dns_resolver {
+ resolver = dns_resolver;
+ }
+ if !config.dns_overrides.is_empty() {
+ resolver = Arc::new(DnsResolverWithOverrides::new(
+ resolver,
+ config.dns_overrides,
+ ));
+ }
+ let http = HttpConnector::new_with_resolver(DynResolver::new(resolver.clone()));
+
+ #[cfg(feature = "__tls")]
+ match config.tls {
+ #[cfg(feature = "default-tls")]
+ TlsBackend::Default => {
+ let mut tls = TlsConnector::builder();
+
+ #[cfg(all(feature = "native-tls-alpn", not(feature = "http3")))]
+ {
+ match config.http_version_pref {
+ HttpVersionPref::Http1 => {
+ tls.request_alpns(&["http/1.1"]);
+ }
+ HttpVersionPref::Http2 => {
+ tls.request_alpns(&["h2"]);
+ }
+ HttpVersionPref::All => {
+ tls.request_alpns(&["h2", "http/1.1"]);
+ }
+ }
+ }
+
+ #[cfg(feature = "native-tls")]
+ {
+ tls.danger_accept_invalid_hostnames(!config.hostname_verification);
+ }
+
+ tls.danger_accept_invalid_certs(!config.certs_verification);
+
+ tls.use_sni(config.tls_sni);
+
+ tls.disable_built_in_roots(!config.tls_built_in_root_certs);
+
+ for cert in config.root_certs {
+ cert.add_to_native_tls(&mut tls);
+ }
+
+ #[cfg(feature = "native-tls")]
+ {
+ if let Some(id) = config.identity {
+ id.add_to_native_tls(&mut tls)?;
+ }
+ }
+
+ if let Some(min_tls_version) = config.min_tls_version {
+ let protocol = min_tls_version.to_native_tls().ok_or_else(|| {
+ // TLS v1.3. This would be entirely reasonable,
+ // native-tls just doesn't support it.
+ // https://github.com/sfackler/rust-native-tls/issues/140
+ crate::error::builder("invalid minimum TLS version for backend")
+ })?;
+ tls.min_protocol_version(Some(protocol));
+ }
+
+ if let Some(max_tls_version) = config.max_tls_version {
+ let protocol = max_tls_version.to_native_tls().ok_or_else(|| {
+ // TLS v1.3.
+ // We could arguably do max_protocol_version(None), given
+ // that 1.4 does not exist yet, but that'd get messy in the
+ // future.
+ crate::error::builder("invalid maximum TLS version for backend")
+ })?;
+ tls.max_protocol_version(Some(protocol));
+ }
+
+ Connector::new_default_tls(
+ http,
+ tls,
+ proxies.clone(),
+ user_agent(&config.headers),
+ config.local_address,
+ config.nodelay,
+ )?
+ }
+ #[cfg(feature = "native-tls")]
+ TlsBackend::BuiltNativeTls(conn) => Connector::from_built_default_tls(
+ http,
+ conn,
+ proxies.clone(),
+ user_agent(&config.headers),
+ config.local_address,
+ config.nodelay,
+ ),
+ #[cfg(feature = "__rustls")]
+ TlsBackend::BuiltRustls(conn) => Connector::new_rustls_tls(
+ http,
+ conn,
+ proxies.clone(),
+ user_agent(&config.headers),
+ config.local_address,
+ config.nodelay,
+ ),
+ #[cfg(feature = "__rustls")]
+ TlsBackend::Rustls => {
+ use crate::tls::NoVerifier;
+
+ // Set root certificates.
+ let mut root_cert_store = rustls::RootCertStore::empty();
+ for cert in config.root_certs {
+ cert.add_to_rustls(&mut root_cert_store)?;
+ }
+
+ #[cfg(feature = "rustls-tls-webpki-roots")]
+ if config.tls_built_in_root_certs {
+ use rustls::OwnedTrustAnchor;
+
+ let trust_anchors =
+ webpki_roots::TLS_SERVER_ROOTS.0.iter().map(|trust_anchor| {
+ OwnedTrustAnchor::from_subject_spki_name_constraints(
+ trust_anchor.subject,
+ trust_anchor.spki,
+ trust_anchor.name_constraints,
+ )
+ });
+
+ root_cert_store.add_server_trust_anchors(trust_anchors);
+ }
+
+ #[cfg(feature = "rustls-tls-native-roots")]
+ if config.tls_built_in_root_certs {
+ let mut valid_count = 0;
+ let mut invalid_count = 0;
+ for cert in rustls_native_certs::load_native_certs()
+ .map_err(crate::error::builder)?
+ {
+ let cert = rustls::Certificate(cert.0);
+ // Continue on parsing errors, as native stores often include ancient or syntactically
+ // invalid certificates, like root certificates without any X509 extensions.
+ // Inspiration: https://github.com/rustls/rustls/blob/633bf4ba9d9521a95f68766d04c22e2b01e68318/rustls/src/anchors.rs#L105-L112
+ match root_cert_store.add(&cert) {
+ Ok(_) => valid_count += 1,
+ Err(err) => {
+ invalid_count += 1;
+ log::warn!(
+ "rustls failed to parse DER certificate {:?} {:?}",
+ &err,
+ &cert
+ );
+ }
+ }
+ }
+ if valid_count == 0 && invalid_count > 0 {
+ return Err(crate::error::builder(
+ "zero valid certificates found in native root store",
+ ));
+ }
+ }
+
+ // Set TLS versions.
+ let mut versions = rustls::ALL_VERSIONS.to_vec();
+
+ if let Some(min_tls_version) = config.min_tls_version {
+ versions.retain(|&supported_version| {
+ match tls::Version::from_rustls(supported_version.version) {
+ Some(version) => version >= min_tls_version,
+ // Assume it's so new we don't know about it, allow it
+ // (as of writing this is unreachable)
+ None => true,
+ }
+ });
+ }
+
+ if let Some(max_tls_version) = config.max_tls_version {
+ versions.retain(|&supported_version| {
+ match tls::Version::from_rustls(supported_version.version) {
+ Some(version) => version <= max_tls_version,
+ None => false,
+ }
+ });
+ }
+
+ // Build TLS config
+ let config_builder = rustls::ClientConfig::builder()
+ .with_safe_default_cipher_suites()
+ .with_safe_default_kx_groups()
+ .with_protocol_versions(&versions)
+ .map_err(crate::error::builder)?
+ .with_root_certificates(root_cert_store);
+
+ // Finalize TLS config
+ let mut tls = if let Some(id) = config.identity {
+ id.add_to_rustls(config_builder)?
+ } else {
+ config_builder.with_no_client_auth()
+ };
+
+ // Certificate verifier
+ if !config.certs_verification {
+ tls.dangerous()
+ .set_certificate_verifier(Arc::new(NoVerifier));
+ }
+
+ tls.enable_sni = config.tls_sni;
+
+ // ALPN protocol
+ match config.http_version_pref {
+ HttpVersionPref::Http1 => {
+ tls.alpn_protocols = vec!["http/1.1".into()];
+ }
+ HttpVersionPref::Http2 => {
+ tls.alpn_protocols = vec!["h2".into()];
+ }
+ #[cfg(feature = "http3")]
+ HttpVersionPref::Http3 => {
+ tls.alpn_protocols = vec!["h3".into()];
+ }
+ HttpVersionPref::All => {
+ tls.alpn_protocols = vec!["h2".into(), "http/1.1".into()];
+ }
+ }
+
+ #[cfg(feature = "http3")]
+ {
+ tls.enable_early_data = config.tls_enable_early_data;
+
+ let mut transport_config = TransportConfig::default();
+
+ if let Some(max_idle_timeout) = config.quic_max_idle_timeout {
+ transport_config.max_idle_timeout(Some(
+ max_idle_timeout.try_into().map_err(error::builder)?,
+ ));
+ }
+
+ if let Some(stream_receive_window) = config.quic_stream_receive_window {
+ transport_config.stream_receive_window(stream_receive_window);
+ }
+
+ if let Some(receive_window) = config.quic_receive_window {
+ transport_config.receive_window(receive_window);
+ }
+
+ if let Some(send_window) = config.quic_send_window {
+ transport_config.send_window(send_window);
+ }
+
+ h3_connector = Some(H3Connector::new(
+ DynResolver::new(resolver),
+ tls.clone(),
+ config.local_address,
+ transport_config,
+ ));
+ }
+
+ Connector::new_rustls_tls(
+ http,
+ tls,
+ proxies.clone(),
+ user_agent(&config.headers),
+ config.local_address,
+ config.nodelay,
+ )
+ }
+ #[cfg(any(feature = "native-tls", feature = "__rustls",))]
+ TlsBackend::UnknownPreconfigured => {
+ return Err(crate::error::builder(
+ "Unknown TLS backend passed to `use_preconfigured_tls`",
+ ));
+ }
+ }
+
+ #[cfg(not(feature = "__tls"))]
+ Connector::new(http, proxies.clone(), config.local_address, config.nodelay)
+ };
+
+ connector.set_timeout(config.connect_timeout);
+ connector.set_verbose(config.connection_verbose);
+
+ let mut builder = hyper::Client::builder();
+ if matches!(config.http_version_pref, HttpVersionPref::Http2) {
+ builder.http2_only(true);
+ }
+
+ if let Some(http2_initial_stream_window_size) = config.http2_initial_stream_window_size {
+ builder.http2_initial_stream_window_size(http2_initial_stream_window_size);
+ }
+ if let Some(http2_initial_connection_window_size) =
+ config.http2_initial_connection_window_size
+ {
+ builder.http2_initial_connection_window_size(http2_initial_connection_window_size);
+ }
+ if config.http2_adaptive_window {
+ builder.http2_adaptive_window(true);
+ }
+ if let Some(http2_max_frame_size) = config.http2_max_frame_size {
+ builder.http2_max_frame_size(http2_max_frame_size);
+ }
+ if let Some(http2_keep_alive_interval) = config.http2_keep_alive_interval {
+ builder.http2_keep_alive_interval(http2_keep_alive_interval);
+ }
+ if let Some(http2_keep_alive_timeout) = config.http2_keep_alive_timeout {
+ builder.http2_keep_alive_timeout(http2_keep_alive_timeout);
+ }
+ if config.http2_keep_alive_while_idle {
+ builder.http2_keep_alive_while_idle(true);
+ }
+
+ builder.pool_idle_timeout(config.pool_idle_timeout);
+ builder.pool_max_idle_per_host(config.pool_max_idle_per_host);
+ connector.set_keepalive(config.tcp_keepalive);
+
+ if config.http09_responses {
+ builder.http09_responses(true);
+ }
+
+ if config.http1_title_case_headers {
+ builder.http1_title_case_headers(true);
+ }
+
+ if config.http1_allow_obsolete_multiline_headers_in_responses {
+ builder.http1_allow_obsolete_multiline_headers_in_responses(true);
+ }
+
+ let proxies_maybe_http_auth = proxies.iter().any(|p| p.maybe_has_http_auth());
+
+ Ok(Client {
+ inner: Arc::new(ClientRef {
+ accepts: config.accepts,
+ #[cfg(feature = "cookies")]
+ cookie_store: config.cookie_store,
+ #[cfg(feature = "http3")]
+ h3_client: H3Client::new(
+ h3_connector.expect("missing HTTP/3 connector"),
+ config.pool_idle_timeout,
+ ),
+ hyper: builder.build(connector),
+ headers: config.headers,
+ redirect_policy: config.redirect_policy,
+ referer: config.referer,
+ request_timeout: config.timeout,
+ proxies,
+ proxies_maybe_http_auth,
+ https_only: config.https_only,
+ }),
+ })
+ }
+
+ // Higher-level options
+
+ /// Sets the `User-Agent` header to be used by this client.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// # async fn doc() -> Result<(), reqwest::Error> {
+ /// // Name your user agent after your app?
+ /// static APP_USER_AGENT: &str = concat!(
+ /// env!("CARGO_PKG_NAME"),
+ /// "/",
+ /// env!("CARGO_PKG_VERSION"),
+ /// );
+ ///
+ /// let client = reqwest::Client::builder()
+ /// .user_agent(APP_USER_AGENT)
+ /// .build()?;
+ /// let res = client.get("https://www.rust-lang.org").send().await?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn user_agent<V>(mut self, value: V) -> ClientBuilder
+ where
+ V: TryInto<HeaderValue>,
+ V::Error: Into<http::Error>,
+ {
+ match value.try_into() {
+ Ok(value) => {
+ self.config.headers.insert(USER_AGENT, value);
+ }
+ Err(e) => {
+ self.config.error = Some(crate::error::builder(e.into()));
+ }
+ };
+ self
+ }
+ /// Sets the default headers for every request.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use reqwest::header;
+ /// # async fn doc() -> Result<(), reqwest::Error> {
+ /// let mut headers = header::HeaderMap::new();
+ /// headers.insert("X-MY-HEADER", header::HeaderValue::from_static("value"));
+ ///
+ /// // Consider marking security-sensitive headers with `set_sensitive`.
+ /// let mut auth_value = header::HeaderValue::from_static("secret");
+ /// auth_value.set_sensitive(true);
+ /// headers.insert(header::AUTHORIZATION, auth_value);
+ ///
+ /// // get a client builder
+ /// let client = reqwest::Client::builder()
+ /// .default_headers(headers)
+ /// .build()?;
+ /// let res = client.get("https://www.rust-lang.org").send().await?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// Override the default headers:
+ ///
+ /// ```rust
+ /// use reqwest::header;
+ /// # async fn doc() -> Result<(), reqwest::Error> {
+ /// let mut headers = header::HeaderMap::new();
+ /// headers.insert("X-MY-HEADER", header::HeaderValue::from_static("value"));
+ ///
+ /// // get a client builder
+ /// let client = reqwest::Client::builder()
+ /// .default_headers(headers)
+ /// .build()?;
+ /// let res = client
+ /// .get("https://www.rust-lang.org")
+ /// .header("X-MY-HEADER", "new_value")
+ /// .send()
+ /// .await?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn default_headers(mut self, headers: HeaderMap) -> ClientBuilder {
+ for (key, value) in headers.iter() {
+ self.config.headers.insert(key, value.clone());
+ }
+ self
+ }
+
+ /// Enable a persistent cookie store for the client.
+ ///
+ /// Cookies received in responses will be preserved and included in
+ /// additional requests.
+ ///
+ /// By default, no cookie store is used.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `cookies` feature to be enabled.
+ #[cfg(feature = "cookies")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "cookies")))]
+ pub fn cookie_store(mut self, enable: bool) -> ClientBuilder {
+ if enable {
+ self.cookie_provider(Arc::new(cookie::Jar::default()))
+ } else {
+ self.config.cookie_store = None;
+ self
+ }
+ }
+
+ /// Set the persistent cookie store for the client.
+ ///
+ /// Cookies received in responses will be passed to this store, and
+ /// additional requests will query this store for cookies.
+ ///
+ /// By default, no cookie store is used.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `cookies` feature to be enabled.
+ #[cfg(feature = "cookies")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "cookies")))]
+ pub fn cookie_provider<C: cookie::CookieStore + 'static>(
+ mut self,
+ cookie_store: Arc<C>,
+ ) -> ClientBuilder {
+ self.config.cookie_store = Some(cookie_store as _);
+ self
+ }
+
+ /// Enable auto gzip decompression by checking the `Content-Encoding` response header.
+ ///
+ /// If auto gzip decompression is turned on:
+ ///
+ /// - When sending a request and if the request's headers do not already contain
+ /// an `Accept-Encoding` **and** `Range` values, the `Accept-Encoding` header is set to `gzip`.
+ /// The request body is **not** automatically compressed.
+ /// - When receiving a response, if its headers contain a `Content-Encoding` value of
+ /// `gzip`, both `Content-Encoding` and `Content-Length` are removed from the
+ /// headers' set. The response body is automatically decompressed.
+ ///
+ /// If the `gzip` feature is turned on, the default option is enabled.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `gzip` feature to be enabled
+ #[cfg(feature = "gzip")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "gzip")))]
+ pub fn gzip(mut self, enable: bool) -> ClientBuilder {
+ self.config.accepts.gzip = enable;
+ self
+ }
+
+ /// Enable auto brotli decompression by checking the `Content-Encoding` response header.
+ ///
+ /// If auto brotli decompression is turned on:
+ ///
+ /// - When sending a request and if the request's headers do not already contain
+ /// an `Accept-Encoding` **and** `Range` values, the `Accept-Encoding` header is set to `br`.
+ /// The request body is **not** automatically compressed.
+ /// - When receiving a response, if its headers contain a `Content-Encoding` value of
+ /// `br`, both `Content-Encoding` and `Content-Length` are removed from the
+ /// headers' set. The response body is automatically decompressed.
+ ///
+ /// If the `brotli` feature is turned on, the default option is enabled.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `brotli` feature to be enabled
+ #[cfg(feature = "brotli")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "brotli")))]
+ pub fn brotli(mut self, enable: bool) -> ClientBuilder {
+ self.config.accepts.brotli = enable;
+ self
+ }
+
+ /// Enable auto deflate decompression by checking the `Content-Encoding` response header.
+ ///
+ /// If auto deflate decompression is turned on:
+ ///
+ /// - When sending a request and if the request's headers do not already contain
+ /// an `Accept-Encoding` **and** `Range` values, the `Accept-Encoding` header is set to `deflate`.
+ /// The request body is **not** automatically compressed.
+ /// - When receiving a response, if it's headers contain a `Content-Encoding` value that
+ /// equals to `deflate`, both values `Content-Encoding` and `Content-Length` are removed from the
+ /// headers' set. The response body is automatically decompressed.
+ ///
+ /// If the `deflate` feature is turned on, the default option is enabled.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `deflate` feature to be enabled
+ #[cfg(feature = "deflate")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "deflate")))]
+ pub fn deflate(mut self, enable: bool) -> ClientBuilder {
+ self.config.accepts.deflate = enable;
+ self
+ }
+
+ /// Disable auto response body gzip decompression.
+ ///
+ /// This method exists even if the optional `gzip` feature is not enabled.
+ /// This can be used to ensure a `Client` doesn't use gzip decompression
+ /// even if another dependency were to enable the optional `gzip` feature.
+ pub fn no_gzip(self) -> ClientBuilder {
+ #[cfg(feature = "gzip")]
+ {
+ self.gzip(false)
+ }
+
+ #[cfg(not(feature = "gzip"))]
+ {
+ self
+ }
+ }
+
+ /// Disable auto response body brotli decompression.
+ ///
+ /// This method exists even if the optional `brotli` feature is not enabled.
+ /// This can be used to ensure a `Client` doesn't use brotli decompression
+ /// even if another dependency were to enable the optional `brotli` feature.
+ pub fn no_brotli(self) -> ClientBuilder {
+ #[cfg(feature = "brotli")]
+ {
+ self.brotli(false)
+ }
+
+ #[cfg(not(feature = "brotli"))]
+ {
+ self
+ }
+ }
+
+ /// Disable auto response body deflate decompression.
+ ///
+ /// This method exists even if the optional `deflate` feature is not enabled.
+ /// This can be used to ensure a `Client` doesn't use deflate decompression
+ /// even if another dependency were to enable the optional `deflate` feature.
+ pub fn no_deflate(self) -> ClientBuilder {
+ #[cfg(feature = "deflate")]
+ {
+ self.deflate(false)
+ }
+
+ #[cfg(not(feature = "deflate"))]
+ {
+ self
+ }
+ }
+
+ // Redirect options
+
+ /// Set a `RedirectPolicy` for this client.
+ ///
+ /// Default will follow redirects up to a maximum of 10.
+ pub fn redirect(mut self, policy: redirect::Policy) -> ClientBuilder {
+ self.config.redirect_policy = policy;
+ self
+ }
+
+ /// Enable or disable automatic setting of the `Referer` header.
+ ///
+ /// Default is `true`.
+ pub fn referer(mut self, enable: bool) -> ClientBuilder {
+ self.config.referer = enable;
+ self
+ }
+
+ // Proxy options
+
+ /// Add a `Proxy` to the list of proxies the `Client` will use.
+ ///
+ /// # Note
+ ///
+ /// Adding a proxy will disable the automatic usage of the "system" proxy.
+ pub fn proxy(mut self, proxy: Proxy) -> ClientBuilder {
+ self.config.proxies.push(proxy);
+ self.config.auto_sys_proxy = false;
+ self
+ }
+
+ /// Clear all `Proxies`, so `Client` will use no proxy anymore.
+ ///
+ /// # Note
+ /// To add a proxy exclusion list, use [crate::proxy::Proxy::no_proxy()]
+ /// on all desired proxies instead.
+ ///
+ /// This also disables the automatic usage of the "system" proxy.
+ pub fn no_proxy(mut self) -> ClientBuilder {
+ self.config.proxies.clear();
+ self.config.auto_sys_proxy = false;
+ self
+ }
+
+ // Timeout options
+
+ /// Enables a request timeout.
+ ///
+ /// The timeout is applied from when the request starts connecting until the
+ /// response body has finished.
+ ///
+ /// Default is no timeout.
+ pub fn timeout(mut self, timeout: Duration) -> ClientBuilder {
+ self.config.timeout = Some(timeout);
+ self
+ }
+
+ /// Set a timeout for only the connect phase of a `Client`.
+ ///
+ /// Default is `None`.
+ ///
+ /// # Note
+ ///
+ /// This **requires** the futures be executed in a tokio runtime with
+ /// a tokio timer enabled.
+ pub fn connect_timeout(mut self, timeout: Duration) -> ClientBuilder {
+ self.config.connect_timeout = Some(timeout);
+ self
+ }
+
+ /// Set whether connections should emit verbose logs.
+ ///
+ /// Enabling this option will emit [log][] messages at the `TRACE` level
+ /// for read and write operations on connections.
+ ///
+ /// [log]: https://crates.io/crates/log
+ pub fn connection_verbose(mut self, verbose: bool) -> ClientBuilder {
+ self.config.connection_verbose = verbose;
+ self
+ }
+
+ // HTTP options
+
+ /// Set an optional timeout for idle sockets being kept-alive.
+ ///
+ /// Pass `None` to disable timeout.
+ ///
+ /// Default is 90 seconds.
+ pub fn pool_idle_timeout<D>(mut self, val: D) -> ClientBuilder
+ where
+ D: Into<Option<Duration>>,
+ {
+ self.config.pool_idle_timeout = val.into();
+ self
+ }
+
+ /// Sets the maximum idle connection per host allowed in the pool.
+ pub fn pool_max_idle_per_host(mut self, max: usize) -> ClientBuilder {
+ self.config.pool_max_idle_per_host = max;
+ self
+ }
+
+ /// Send headers as title case instead of lowercase.
+ pub fn http1_title_case_headers(mut self) -> ClientBuilder {
+ self.config.http1_title_case_headers = true;
+ self
+ }
+
+ /// Set whether HTTP/1 connections will accept obsolete line folding for
+ /// header values.
+ ///
+ /// Newline codepoints (`\r` and `\n`) will be transformed to spaces when
+ /// parsing.
+ pub fn http1_allow_obsolete_multiline_headers_in_responses(
+ mut self,
+ value: bool,
+ ) -> ClientBuilder {
+ self.config
+ .http1_allow_obsolete_multiline_headers_in_responses = value;
+ self
+ }
+
+ /// Only use HTTP/1.
+ pub fn http1_only(mut self) -> ClientBuilder {
+ self.config.http_version_pref = HttpVersionPref::Http1;
+ self
+ }
+
+ /// Allow HTTP/0.9 responses
+ pub fn http09_responses(mut self) -> ClientBuilder {
+ self.config.http09_responses = true;
+ self
+ }
+
+ /// Only use HTTP/2.
+ pub fn http2_prior_knowledge(mut self) -> ClientBuilder {
+ self.config.http_version_pref = HttpVersionPref::Http2;
+ self
+ }
+
+ /// Only use HTTP/3.
+ #[cfg(feature = "http3")]
+ pub fn http3_prior_knowledge(mut self) -> ClientBuilder {
+ self.config.http_version_pref = HttpVersionPref::Http3;
+ self
+ }
+
+ /// Sets the `SETTINGS_INITIAL_WINDOW_SIZE` option for HTTP2 stream-level flow control.
+ ///
+ /// Default is currently 65,535 but may change internally to optimize for common uses.
+ pub fn http2_initial_stream_window_size(mut self, sz: impl Into<Option<u32>>) -> ClientBuilder {
+ self.config.http2_initial_stream_window_size = sz.into();
+ self
+ }
+
+ /// Sets the max connection-level flow control for HTTP2
+ ///
+ /// Default is currently 65,535 but may change internally to optimize for common uses.
+ pub fn http2_initial_connection_window_size(
+ mut self,
+ sz: impl Into<Option<u32>>,
+ ) -> ClientBuilder {
+ self.config.http2_initial_connection_window_size = sz.into();
+ self
+ }
+
+ /// Sets whether to use an adaptive flow control.
+ ///
+ /// Enabling this will override the limits set in `http2_initial_stream_window_size` and
+ /// `http2_initial_connection_window_size`.
+ pub fn http2_adaptive_window(mut self, enabled: bool) -> ClientBuilder {
+ self.config.http2_adaptive_window = enabled;
+ self
+ }
+
+ /// Sets the maximum frame size to use for HTTP2.
+ ///
+ /// Default is currently 16,384 but may change internally to optimize for common uses.
+ pub fn http2_max_frame_size(mut self, sz: impl Into<Option<u32>>) -> ClientBuilder {
+ self.config.http2_max_frame_size = sz.into();
+ self
+ }
+
+ /// Sets an interval for HTTP2 Ping frames should be sent to keep a connection alive.
+ ///
+ /// Pass `None` to disable HTTP2 keep-alive.
+ /// Default is currently disabled.
+ pub fn http2_keep_alive_interval(
+ mut self,
+ interval: impl Into<Option<Duration>>,
+ ) -> ClientBuilder {
+ self.config.http2_keep_alive_interval = interval.into();
+ self
+ }
+
+ /// Sets a timeout for receiving an acknowledgement of the keep-alive ping.
+ ///
+ /// If the ping is not acknowledged within the timeout, the connection will be closed.
+ /// Does nothing if `http2_keep_alive_interval` is disabled.
+ /// Default is currently disabled.
+ pub fn http2_keep_alive_timeout(mut self, timeout: Duration) -> ClientBuilder {
+ self.config.http2_keep_alive_timeout = Some(timeout);
+ self
+ }
+
+ /// Sets whether HTTP2 keep-alive should apply while the connection is idle.
+ ///
+ /// If disabled, keep-alive pings are only sent while there are open request/responses streams.
+ /// If enabled, pings are also sent when no streams are active.
+ /// Does nothing if `http2_keep_alive_interval` is disabled.
+ /// Default is `false`.
+ pub fn http2_keep_alive_while_idle(mut self, enabled: bool) -> ClientBuilder {
+ self.config.http2_keep_alive_while_idle = enabled;
+ self
+ }
+
+ // TCP options
+
+ /// Set whether sockets have `TCP_NODELAY` enabled.
+ ///
+ /// Default is `true`.
+ pub fn tcp_nodelay(mut self, enabled: bool) -> ClientBuilder {
+ self.config.nodelay = enabled;
+ self
+ }
+
+ /// Bind to a local IP Address.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use std::net::IpAddr;
+ /// let local_addr = IpAddr::from([12, 4, 1, 8]);
+ /// let client = reqwest::Client::builder()
+ /// .local_address(local_addr)
+ /// .build().unwrap();
+ /// ```
+ pub fn local_address<T>(mut self, addr: T) -> ClientBuilder
+ where
+ T: Into<Option<IpAddr>>,
+ {
+ self.config.local_address = addr.into();
+ self
+ }
+
+ /// Set that all sockets have `SO_KEEPALIVE` set with the supplied duration.
+ ///
+ /// If `None`, the option will not be set.
+ pub fn tcp_keepalive<D>(mut self, val: D) -> ClientBuilder
+ where
+ D: Into<Option<Duration>>,
+ {
+ self.config.tcp_keepalive = val.into();
+ self
+ }
+
+ // TLS options
+
+ /// Add a custom root certificate.
+ ///
+ /// This can be used to connect to a server that has a self-signed
+ /// certificate for example.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `default-tls`, `native-tls`, or `rustls-tls(-...)`
+ /// feature to be enabled.
+ #[cfg(feature = "__tls")]
+ #[cfg_attr(
+ docsrs,
+ doc(cfg(any(
+ feature = "default-tls",
+ feature = "native-tls",
+ feature = "rustls-tls"
+ )))
+ )]
+ pub fn add_root_certificate(mut self, cert: Certificate) -> ClientBuilder {
+ self.config.root_certs.push(cert);
+ self
+ }
+
+ /// Controls the use of built-in/preloaded certificates during certificate validation.
+ ///
+ /// Defaults to `true` -- built-in system certs will be used.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `default-tls`, `native-tls`, or `rustls-tls(-...)`
+ /// feature to be enabled.
+ #[cfg(feature = "__tls")]
+ #[cfg_attr(
+ docsrs,
+ doc(cfg(any(
+ feature = "default-tls",
+ feature = "native-tls",
+ feature = "rustls-tls"
+ )))
+ )]
+ pub fn tls_built_in_root_certs(mut self, tls_built_in_root_certs: bool) -> ClientBuilder {
+ self.config.tls_built_in_root_certs = tls_built_in_root_certs;
+ self
+ }
+
+ /// Sets the identity to be used for client certificate authentication.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `native-tls` or `rustls-tls(-...)` feature to be
+ /// enabled.
+ #[cfg(any(feature = "native-tls", feature = "__rustls"))]
+ #[cfg_attr(docsrs, doc(cfg(any(feature = "native-tls", feature = "rustls-tls"))))]
+ pub fn identity(mut self, identity: Identity) -> ClientBuilder {
+ self.config.identity = Some(identity);
+ self
+ }
+
+ /// Controls the use of hostname verification.
+ ///
+ /// Defaults to `false`.
+ ///
+ /// # Warning
+ ///
+ /// You should think very carefully before you use this method. If
+ /// hostname verification is not used, any valid certificate for any
+ /// site will be trusted for use from any other. This introduces a
+ /// significant vulnerability to man-in-the-middle attacks.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `native-tls` feature to be enabled.
+ #[cfg(feature = "native-tls")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))]
+ pub fn danger_accept_invalid_hostnames(
+ mut self,
+ accept_invalid_hostname: bool,
+ ) -> ClientBuilder {
+ self.config.hostname_verification = !accept_invalid_hostname;
+ self
+ }
+
+ /// Controls the use of certificate validation.
+ ///
+ /// Defaults to `false`.
+ ///
+ /// # Warning
+ ///
+ /// You should think very carefully before using this method. If
+ /// invalid certificates are trusted, *any* certificate for *any* site
+ /// will be trusted for use. This includes expired certificates. This
+ /// introduces significant vulnerabilities, and should only be used
+ /// as a last resort.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `default-tls`, `native-tls`, or `rustls-tls(-...)`
+ /// feature to be enabled.
+ #[cfg(feature = "__tls")]
+ #[cfg_attr(
+ docsrs,
+ doc(cfg(any(
+ feature = "default-tls",
+ feature = "native-tls",
+ feature = "rustls-tls"
+ )))
+ )]
+ pub fn danger_accept_invalid_certs(mut self, accept_invalid_certs: bool) -> ClientBuilder {
+ self.config.certs_verification = !accept_invalid_certs;
+ self
+ }
+
+ /// Controls the use of TLS server name indication.
+ ///
+ /// Defaults to `true`.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `default-tls`, `native-tls`, or `rustls-tls(-...)`
+ /// feature to be enabled.
+ #[cfg(feature = "__tls")]
+ #[cfg_attr(
+ docsrs,
+ doc(cfg(any(
+ feature = "default-tls",
+ feature = "native-tls",
+ feature = "rustls-tls"
+ )))
+ )]
+ pub fn tls_sni(mut self, tls_sni: bool) -> ClientBuilder {
+ self.config.tls_sni = tls_sni;
+ self
+ }
+
+ /// Set the minimum required TLS version for connections.
+ ///
+ /// By default the TLS backend's own default is used.
+ ///
+ /// # Errors
+ ///
+ /// A value of `tls::Version::TLS_1_3` will cause an error with the
+ /// `native-tls`/`default-tls` backend. This does not mean the version
+ /// isn't supported, just that it can't be set as a minimum due to
+ /// technical limitations.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `default-tls`, `native-tls`, or `rustls-tls(-...)`
+ /// feature to be enabled.
+ #[cfg(feature = "__tls")]
+ #[cfg_attr(
+ docsrs,
+ doc(cfg(any(
+ feature = "default-tls",
+ feature = "native-tls",
+ feature = "rustls-tls"
+ )))
+ )]
+ pub fn min_tls_version(mut self, version: tls::Version) -> ClientBuilder {
+ self.config.min_tls_version = Some(version);
+ self
+ }
+
+ /// Set the maximum allowed TLS version for connections.
+ ///
+ /// By default there's no maximum.
+ ///
+ /// # Errors
+ ///
+ /// A value of `tls::Version::TLS_1_3` will cause an error with the
+ /// `native-tls`/`default-tls` backend. This does not mean the version
+ /// isn't supported, just that it can't be set as a maximum due to
+ /// technical limitations.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `default-tls`, `native-tls`, or `rustls-tls(-...)`
+ /// feature to be enabled.
+ #[cfg(feature = "__tls")]
+ #[cfg_attr(
+ docsrs,
+ doc(cfg(any(
+ feature = "default-tls",
+ feature = "native-tls",
+ feature = "rustls-tls"
+ )))
+ )]
+ pub fn max_tls_version(mut self, version: tls::Version) -> ClientBuilder {
+ self.config.max_tls_version = Some(version);
+ self
+ }
+
+ /// Force using the native TLS backend.
+ ///
+ /// Since multiple TLS backends can be optionally enabled, this option will
+ /// force the `native-tls` backend to be used for this `Client`.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `native-tls` feature to be enabled.
+ #[cfg(feature = "native-tls")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))]
+ pub fn use_native_tls(mut self) -> ClientBuilder {
+ self.config.tls = TlsBackend::Default;
+ self
+ }
+
+ /// Force using the Rustls TLS backend.
+ ///
+ /// Since multiple TLS backends can be optionally enabled, this option will
+ /// force the `rustls` backend to be used for this `Client`.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `rustls-tls(-...)` feature to be enabled.
+ #[cfg(feature = "__rustls")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "rustls-tls")))]
+ pub fn use_rustls_tls(mut self) -> ClientBuilder {
+ self.config.tls = TlsBackend::Rustls;
+ self
+ }
+
+ /// Use a preconfigured TLS backend.
+ ///
+ /// If the passed `Any` argument is not a TLS backend that reqwest
+ /// understands, the `ClientBuilder` will error when calling `build`.
+ ///
+ /// # Advanced
+ ///
+ /// This is an advanced option, and can be somewhat brittle. Usage requires
+ /// keeping the preconfigured TLS argument version in sync with reqwest,
+ /// since version mismatches will result in an "unknown" TLS backend.
+ ///
+ /// If possible, it's preferable to use the methods on `ClientBuilder`
+ /// to configure reqwest's TLS.
+ ///
+ /// # Optional
+ ///
+ /// This requires one of the optional features `native-tls` or
+ /// `rustls-tls(-...)` to be enabled.
+ #[cfg(any(feature = "native-tls", feature = "__rustls",))]
+ #[cfg_attr(docsrs, doc(cfg(any(feature = "native-tls", feature = "rustls-tls"))))]
+ pub fn use_preconfigured_tls(mut self, tls: impl Any) -> ClientBuilder {
+ let mut tls = Some(tls);
+ #[cfg(feature = "native-tls")]
+ {
+ if let Some(conn) =
+ (&mut tls as &mut dyn Any).downcast_mut::<Option<native_tls_crate::TlsConnector>>()
+ {
+ let tls = conn.take().expect("is definitely Some");
+ let tls = crate::tls::TlsBackend::BuiltNativeTls(tls);
+ self.config.tls = tls;
+ return self;
+ }
+ }
+ #[cfg(feature = "__rustls")]
+ {
+ if let Some(conn) =
+ (&mut tls as &mut dyn Any).downcast_mut::<Option<rustls::ClientConfig>>()
+ {
+ let tls = conn.take().expect("is definitely Some");
+ let tls = crate::tls::TlsBackend::BuiltRustls(tls);
+ self.config.tls = tls;
+ return self;
+ }
+ }
+
+ // Otherwise, we don't recognize the TLS backend!
+ self.config.tls = crate::tls::TlsBackend::UnknownPreconfigured;
+ self
+ }
+
+ /// Enables the [trust-dns](trust_dns_resolver) async resolver instead of a default threadpool using `getaddrinfo`.
+ ///
+ /// If the `trust-dns` feature is turned on, the default option is enabled.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `trust-dns` feature to be enabled
+ #[cfg(feature = "trust-dns")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "trust-dns")))]
+ pub fn trust_dns(mut self, enable: bool) -> ClientBuilder {
+ self.config.trust_dns = enable;
+ self
+ }
+
+ /// Disables the trust-dns async resolver.
+ ///
+ /// This method exists even if the optional `trust-dns` feature is not enabled.
+ /// This can be used to ensure a `Client` doesn't use the trust-dns async resolver
+ /// even if another dependency were to enable the optional `trust-dns` feature.
+ pub fn no_trust_dns(self) -> ClientBuilder {
+ #[cfg(feature = "trust-dns")]
+ {
+ self.trust_dns(false)
+ }
+
+ #[cfg(not(feature = "trust-dns"))]
+ {
+ self
+ }
+ }
+
+ /// Restrict the Client to be used with HTTPS only requests.
+ ///
+ /// Defaults to false.
+ pub fn https_only(mut self, enabled: bool) -> ClientBuilder {
+ self.config.https_only = enabled;
+ self
+ }
+
+ /// Override DNS resolution for specific domains to a particular IP address.
+ ///
+ /// Warning
+ ///
+ /// Since the DNS protocol has no notion of ports, if you wish to send
+ /// traffic to a particular port you must include this port in the URL
+ /// itself, any port in the overridden addr will be ignored and traffic sent
+ /// to the conventional port for the given scheme (e.g. 80 for http).
+ pub fn resolve(self, domain: &str, addr: SocketAddr) -> ClientBuilder {
+ self.resolve_to_addrs(domain, &[addr])
+ }
+
+ /// Override DNS resolution for specific domains to particular IP addresses.
+ ///
+ /// Warning
+ ///
+ /// Since the DNS protocol has no notion of ports, if you wish to send
+ /// traffic to a particular port you must include this port in the URL
+ /// itself, any port in the overridden addresses will be ignored and traffic sent
+ /// to the conventional port for the given scheme (e.g. 80 for http).
+ pub fn resolve_to_addrs(mut self, domain: &str, addrs: &[SocketAddr]) -> ClientBuilder {
+ self.config
+ .dns_overrides
+ .insert(domain.to_string(), addrs.to_vec());
+ self
+ }
+
+ /// Override the DNS resolver implementation.
+ ///
+ /// Pass an `Arc` wrapping a trait object implementing `Resolve`.
+ /// Overrides for specific names passed to `resolve` and `resolve_to_addrs` will
+ /// still be applied on top of this resolver.
+ pub fn dns_resolver<R: Resolve + 'static>(mut self, resolver: Arc<R>) -> ClientBuilder {
+ self.config.dns_resolver = Some(resolver as _);
+ self
+ }
+
+ /// Whether to send data on the first flight ("early data") in TLS 1.3 handshakes
+ /// for HTTP/3 connections.
+ ///
+ /// The default is false.
+ #[cfg(feature = "http3")]
+ pub fn set_tls_enable_early_data(mut self, enabled: bool) -> ClientBuilder {
+ self.config.tls_enable_early_data = enabled;
+ self
+ }
+
+ /// Maximum duration of inactivity to accept before timing out the QUIC connection.
+ ///
+ /// Please see docs in [`TransportConfig`] in [`quinn`].
+ ///
+ /// [`TransportConfig`]: https://docs.rs/quinn/latest/quinn/struct.TransportConfig.html
+ #[cfg(feature = "http3")]
+ pub fn set_quic_max_idle_timeout(mut self, value: Duration) -> ClientBuilder {
+ self.config.quic_max_idle_timeout = Some(value);
+ self
+ }
+
+ /// Maximum number of bytes the peer may transmit without acknowledgement on any one stream
+ /// before becoming blocked.
+ ///
+ /// Please see docs in [`TransportConfig`] in [`quinn`].
+ ///
+ /// [`TransportConfig`]: https://docs.rs/quinn/latest/quinn/struct.TransportConfig.html
+ #[cfg(feature = "http3")]
+ pub fn set_quic_stream_receive_window(mut self, value: VarInt) -> ClientBuilder {
+ self.config.quic_stream_receive_window = Some(value);
+ self
+ }
+
+ /// Maximum number of bytes the peer may transmit across all streams of a connection before
+ /// becoming blocked.
+ ///
+ /// Please see docs in [`TransportConfig`] in [`quinn`].
+ ///
+ /// [`TransportConfig`]: https://docs.rs/quinn/latest/quinn/struct.TransportConfig.html
+ #[cfg(feature = "http3")]
+ pub fn set_quic_receive_window(mut self, value: VarInt) -> ClientBuilder {
+ self.config.quic_receive_window = Some(value);
+ self
+ }
+
+ /// Maximum number of bytes to transmit to a peer without acknowledgment
+ ///
+ /// Please see docs in [`TransportConfig`] in [`quinn`].
+ ///
+ /// [`TransportConfig`]: https://docs.rs/quinn/latest/quinn/struct.TransportConfig.html
+ #[cfg(feature = "http3")]
+ pub fn set_quic_send_window(mut self, value: u64) -> ClientBuilder {
+ self.config.quic_send_window = Some(value);
+ self
+ }
+}
+
+type HyperClient = hyper::Client<Connector, super::body::ImplStream>;
+
+impl Default for Client {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl Client {
+ /// Constructs a new `Client`.
+ ///
+ /// # Panics
+ ///
+ /// This method panics if a TLS backend cannot be initialized, or the resolver
+ /// cannot load the system configuration.
+ ///
+ /// Use `Client::builder()` if you wish to handle the failure as an `Error`
+ /// instead of panicking.
+ pub fn new() -> Client {
+ ClientBuilder::new().build().expect("Client::new()")
+ }
+
+ /// Creates a `ClientBuilder` to configure a `Client`.
+ ///
+ /// This is the same as `ClientBuilder::new()`.
+ pub fn builder() -> ClientBuilder {
+ ClientBuilder::new()
+ }
+
+ /// Convenience method to make a `GET` request to a URL.
+ ///
+ /// # Errors
+ ///
+ /// This method fails whenever the supplied `Url` cannot be parsed.
+ pub fn get<U: IntoUrl>(&self, url: U) -> RequestBuilder {
+ self.request(Method::GET, url)
+ }
+
+ /// Convenience method to make a `POST` request to a URL.
+ ///
+ /// # Errors
+ ///
+ /// This method fails whenever the supplied `Url` cannot be parsed.
+ pub fn post<U: IntoUrl>(&self, url: U) -> RequestBuilder {
+ self.request(Method::POST, url)
+ }
+
+ /// Convenience method to make a `PUT` request to a URL.
+ ///
+ /// # Errors
+ ///
+ /// This method fails whenever the supplied `Url` cannot be parsed.
+ pub fn put<U: IntoUrl>(&self, url: U) -> RequestBuilder {
+ self.request(Method::PUT, url)
+ }
+
+ /// Convenience method to make a `PATCH` request to a URL.
+ ///
+ /// # Errors
+ ///
+ /// This method fails whenever the supplied `Url` cannot be parsed.
+ pub fn patch<U: IntoUrl>(&self, url: U) -> RequestBuilder {
+ self.request(Method::PATCH, url)
+ }
+
+ /// Convenience method to make a `DELETE` request to a URL.
+ ///
+ /// # Errors
+ ///
+ /// This method fails whenever the supplied `Url` cannot be parsed.
+ pub fn delete<U: IntoUrl>(&self, url: U) -> RequestBuilder {
+ self.request(Method::DELETE, url)
+ }
+
+ /// Convenience method to make a `HEAD` request to a URL.
+ ///
+ /// # Errors
+ ///
+ /// This method fails whenever the supplied `Url` cannot be parsed.
+ pub fn head<U: IntoUrl>(&self, url: U) -> RequestBuilder {
+ self.request(Method::HEAD, url)
+ }
+
+ /// Start building a `Request` with the `Method` and `Url`.
+ ///
+ /// Returns a `RequestBuilder`, which will allow setting headers and
+ /// the request body before sending.
+ ///
+ /// # Errors
+ ///
+ /// This method fails whenever the supplied `Url` cannot be parsed.
+ pub fn request<U: IntoUrl>(&self, method: Method, url: U) -> RequestBuilder {
+ let req = url.into_url().map(move |url| Request::new(method, url));
+ RequestBuilder::new(self.clone(), req)
+ }
+
+ /// Executes a `Request`.
+ ///
+ /// A `Request` can be built manually with `Request::new()` or obtained
+ /// from a RequestBuilder with `RequestBuilder::build()`.
+ ///
+ /// You should prefer to use the `RequestBuilder` and
+ /// `RequestBuilder::send()`.
+ ///
+ /// # Errors
+ ///
+ /// This method fails if there was an error while sending request,
+ /// redirect loop was detected or redirect limit was exhausted.
+ pub fn execute(
+ &self,
+ request: Request,
+ ) -> impl Future<Output = Result<Response, crate::Error>> {
+ self.execute_request(request)
+ }
+
+ pub(super) fn execute_request(&self, req: Request) -> Pending {
+ let (method, url, mut headers, body, timeout, version) = req.pieces();
+ if url.scheme() != "http" && url.scheme() != "https" {
+ return Pending::new_err(error::url_bad_scheme(url));
+ }
+
+ // check if we're in https_only mode and check the scheme of the current URL
+ if self.inner.https_only && url.scheme() != "https" {
+ return Pending::new_err(error::url_bad_scheme(url));
+ }
+
+ // insert default headers in the request headers
+ // without overwriting already appended headers.
+ for (key, value) in &self.inner.headers {
+ if let Entry::Vacant(entry) = headers.entry(key) {
+ entry.insert(value.clone());
+ }
+ }
+
+ // Add cookies from the cookie store.
+ #[cfg(feature = "cookies")]
+ {
+ if let Some(cookie_store) = self.inner.cookie_store.as_ref() {
+ if headers.get(crate::header::COOKIE).is_none() {
+ add_cookie_header(&mut headers, &**cookie_store, &url);
+ }
+ }
+ }
+
+ let accept_encoding = self.inner.accepts.as_str();
+
+ if let Some(accept_encoding) = accept_encoding {
+ if !headers.contains_key(ACCEPT_ENCODING) && !headers.contains_key(RANGE) {
+ headers.insert(ACCEPT_ENCODING, HeaderValue::from_static(accept_encoding));
+ }
+ }
+
+ let uri = expect_uri(&url);
+
+ let (reusable, body) = match body {
+ Some(body) => {
+ let (reusable, body) = body.try_reuse();
+ (Some(reusable), body)
+ }
+ None => (None, Body::empty()),
+ };
+
+ self.proxy_auth(&uri, &mut headers);
+
+ let builder = hyper::Request::builder()
+ .method(method.clone())
+ .uri(uri)
+ .version(version);
+
+ let in_flight = match version {
+ #[cfg(feature = "http3")]
+ http::Version::HTTP_3 => {
+ let mut req = builder.body(body).expect("valid request parts");
+ *req.headers_mut() = headers.clone();
+ ResponseFuture::H3(self.inner.h3_client.request(req))
+ }
+ _ => {
+ let mut req = builder
+ .body(body.into_stream())
+ .expect("valid request parts");
+ *req.headers_mut() = headers.clone();
+ ResponseFuture::Default(self.inner.hyper.request(req))
+ }
+ };
+
+ let timeout = timeout
+ .or(self.inner.request_timeout)
+ .map(tokio::time::sleep)
+ .map(Box::pin);
+
+ Pending {
+ inner: PendingInner::Request(PendingRequest {
+ method,
+ url,
+ headers,
+ body: reusable,
+
+ urls: Vec::new(),
+
+ retry_count: 0,
+
+ client: self.inner.clone(),
+
+ in_flight,
+ timeout,
+ }),
+ }
+ }
+
+ fn proxy_auth(&self, dst: &Uri, headers: &mut HeaderMap) {
+ if !self.inner.proxies_maybe_http_auth {
+ return;
+ }
+
+ // Only set the header here if the destination scheme is 'http',
+ // since otherwise, the header will be included in the CONNECT tunnel
+ // request instead.
+ if dst.scheme() != Some(&Scheme::HTTP) {
+ return;
+ }
+
+ if headers.contains_key(PROXY_AUTHORIZATION) {
+ return;
+ }
+
+ for proxy in self.inner.proxies.iter() {
+ if proxy.is_match(dst) {
+ if let Some(header) = proxy.http_basic_auth(dst) {
+ headers.insert(PROXY_AUTHORIZATION, header);
+ }
+
+ break;
+ }
+ }
+ }
+}
+
+impl fmt::Debug for Client {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let mut builder = f.debug_struct("Client");
+ self.inner.fmt_fields(&mut builder);
+ builder.finish()
+ }
+}
+
+impl tower_service::Service<Request> for Client {
+ type Response = Response;
+ type Error = crate::Error;
+ type Future = Pending;
+
+ fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
+ Poll::Ready(Ok(()))
+ }
+
+ fn call(&mut self, req: Request) -> Self::Future {
+ self.execute_request(req)
+ }
+}
+
+impl tower_service::Service<Request> for &'_ Client {
+ type Response = Response;
+ type Error = crate::Error;
+ type Future = Pending;
+
+ fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
+ Poll::Ready(Ok(()))
+ }
+
+ fn call(&mut self, req: Request) -> Self::Future {
+ self.execute_request(req)
+ }
+}
+
+impl fmt::Debug for ClientBuilder {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let mut builder = f.debug_struct("ClientBuilder");
+ self.config.fmt_fields(&mut builder);
+ builder.finish()
+ }
+}
+
+impl Config {
+ fn fmt_fields(&self, f: &mut fmt::DebugStruct<'_, '_>) {
+ // Instead of deriving Debug, only print fields when their output
+ // would provide relevant or interesting data.
+
+ #[cfg(feature = "cookies")]
+ {
+ if let Some(_) = self.cookie_store {
+ f.field("cookie_store", &true);
+ }
+ }
+
+ f.field("accepts", &self.accepts);
+
+ if !self.proxies.is_empty() {
+ f.field("proxies", &self.proxies);
+ }
+
+ if !self.redirect_policy.is_default() {
+ f.field("redirect_policy", &self.redirect_policy);
+ }
+
+ if self.referer {
+ f.field("referer", &true);
+ }
+
+ f.field("default_headers", &self.headers);
+
+ if self.http1_title_case_headers {
+ f.field("http1_title_case_headers", &true);
+ }
+
+ if self.http1_allow_obsolete_multiline_headers_in_responses {
+ f.field("http1_allow_obsolete_multiline_headers_in_responses", &true);
+ }
+
+ if matches!(self.http_version_pref, HttpVersionPref::Http1) {
+ f.field("http1_only", &true);
+ }
+
+ if matches!(self.http_version_pref, HttpVersionPref::Http2) {
+ f.field("http2_prior_knowledge", &true);
+ }
+
+ if let Some(ref d) = self.connect_timeout {
+ f.field("connect_timeout", d);
+ }
+
+ if let Some(ref d) = self.timeout {
+ f.field("timeout", d);
+ }
+
+ if let Some(ref v) = self.local_address {
+ f.field("local_address", v);
+ }
+
+ if self.nodelay {
+ f.field("tcp_nodelay", &true);
+ }
+
+ #[cfg(feature = "native-tls")]
+ {
+ if !self.hostname_verification {
+ f.field("danger_accept_invalid_hostnames", &true);
+ }
+ }
+
+ #[cfg(feature = "__tls")]
+ {
+ if !self.certs_verification {
+ f.field("danger_accept_invalid_certs", &true);
+ }
+
+ if let Some(ref min_tls_version) = self.min_tls_version {
+ f.field("min_tls_version", min_tls_version);
+ }
+
+ if let Some(ref max_tls_version) = self.max_tls_version {
+ f.field("max_tls_version", max_tls_version);
+ }
+
+ f.field("tls_sni", &self.tls_sni);
+ }
+
+ #[cfg(all(feature = "native-tls-crate", feature = "__rustls"))]
+ {
+ f.field("tls_backend", &self.tls);
+ }
+
+ if !self.dns_overrides.is_empty() {
+ f.field("dns_overrides", &self.dns_overrides);
+ }
+
+ #[cfg(feature = "http3")]
+ {
+ if self.tls_enable_early_data {
+ f.field("tls_enable_early_data", &true);
+ }
+ }
+ }
+}
+
+struct ClientRef {
+ accepts: Accepts,
+ #[cfg(feature = "cookies")]
+ cookie_store: Option<Arc<dyn cookie::CookieStore>>,
+ headers: HeaderMap,
+ hyper: HyperClient,
+ #[cfg(feature = "http3")]
+ h3_client: H3Client,
+ redirect_policy: redirect::Policy,
+ referer: bool,
+ request_timeout: Option<Duration>,
+ proxies: Arc<Vec<Proxy>>,
+ proxies_maybe_http_auth: bool,
+ https_only: bool,
+}
+
+impl ClientRef {
+ fn fmt_fields(&self, f: &mut fmt::DebugStruct<'_, '_>) {
+ // Instead of deriving Debug, only print fields when their output
+ // would provide relevant or interesting data.
+
+ #[cfg(feature = "cookies")]
+ {
+ if let Some(_) = self.cookie_store {
+ f.field("cookie_store", &true);
+ }
+ }
+
+ f.field("accepts", &self.accepts);
+
+ if !self.proxies.is_empty() {
+ f.field("proxies", &self.proxies);
+ }
+
+ if !self.redirect_policy.is_default() {
+ f.field("redirect_policy", &self.redirect_policy);
+ }
+
+ if self.referer {
+ f.field("referer", &true);
+ }
+
+ f.field("default_headers", &self.headers);
+
+ if let Some(ref d) = self.request_timeout {
+ f.field("timeout", d);
+ }
+ }
+}
+
+pin_project! {
+ pub struct Pending {
+ #[pin]
+ inner: PendingInner,
+ }
+}
+
+enum PendingInner {
+ Request(PendingRequest),
+ Error(Option<crate::Error>),
+}
+
+pin_project! {
+ struct PendingRequest {
+ method: Method,
+ url: Url,
+ headers: HeaderMap,
+ body: Option<Option<Bytes>>,
+
+ urls: Vec<Url>,
+
+ retry_count: usize,
+
+ client: Arc<ClientRef>,
+
+ #[pin]
+ in_flight: ResponseFuture,
+ #[pin]
+ timeout: Option<Pin<Box<Sleep>>>,
+ }
+}
+
+enum ResponseFuture {
+ Default(HyperResponseFuture),
+ #[cfg(feature = "http3")]
+ H3(H3ResponseFuture),
+}
+
+impl PendingRequest {
+ fn in_flight(self: Pin<&mut Self>) -> Pin<&mut ResponseFuture> {
+ self.project().in_flight
+ }
+
+ fn timeout(self: Pin<&mut Self>) -> Pin<&mut Option<Pin<Box<Sleep>>>> {
+ self.project().timeout
+ }
+
+ fn urls(self: Pin<&mut Self>) -> &mut Vec<Url> {
+ self.project().urls
+ }
+
+ fn headers(self: Pin<&mut Self>) -> &mut HeaderMap {
+ self.project().headers
+ }
+
+ fn retry_error(mut self: Pin<&mut Self>, err: &(dyn std::error::Error + 'static)) -> bool {
+ if !is_retryable_error(err) {
+ return false;
+ }
+
+ trace!("can retry {:?}", err);
+
+ let body = match self.body {
+ Some(Some(ref body)) => Body::reusable(body.clone()),
+ Some(None) => {
+ debug!("error was retryable, but body not reusable");
+ return false;
+ }
+ None => Body::empty(),
+ };
+
+ if self.retry_count >= 2 {
+ trace!("retry count too high");
+ return false;
+ }
+ self.retry_count += 1;
+
+ let uri = expect_uri(&self.url);
+
+ *self.as_mut().in_flight().get_mut() = match *self.as_mut().in_flight().as_ref() {
+ #[cfg(feature = "http3")]
+ ResponseFuture::H3(_) => {
+ let mut req = hyper::Request::builder()
+ .method(self.method.clone())
+ .uri(uri)
+ .body(body)
+ .expect("valid request parts");
+ *req.headers_mut() = self.headers.clone();
+ ResponseFuture::H3(self.client.h3_client.request(req))
+ }
+ _ => {
+ let mut req = hyper::Request::builder()
+ .method(self.method.clone())
+ .uri(uri)
+ .body(body.into_stream())
+ .expect("valid request parts");
+ *req.headers_mut() = self.headers.clone();
+ ResponseFuture::Default(self.client.hyper.request(req))
+ }
+ };
+
+ true
+ }
+}
+
+fn is_retryable_error(err: &(dyn std::error::Error + 'static)) -> bool {
+ #[cfg(feature = "http3")]
+ if let Some(cause) = err.source() {
+ if let Some(err) = cause.downcast_ref::<h3::Error>() {
+ debug!("determining if HTTP/3 error {} can be retried", err);
+ // TODO: Does h3 provide an API for checking the error?
+ return err.to_string().as_str() == "timeout";
+ }
+ }
+
+ if let Some(cause) = err.source() {
+ if let Some(err) = cause.downcast_ref::<h2::Error>() {
+ // They sent us a graceful shutdown, try with a new connection!
+ return err.is_go_away()
+ && err.is_remote()
+ && err.reason() == Some(h2::Reason::NO_ERROR);
+ }
+ }
+ false
+}
+
+impl Pending {
+ pub(super) fn new_err(err: crate::Error) -> Pending {
+ Pending {
+ inner: PendingInner::Error(Some(err)),
+ }
+ }
+
+ fn inner(self: Pin<&mut Self>) -> Pin<&mut PendingInner> {
+ self.project().inner
+ }
+}
+
+impl Future for Pending {
+ type Output = Result<Response, crate::Error>;
+
+ fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
+ let inner = self.inner();
+ match inner.get_mut() {
+ PendingInner::Request(ref mut req) => Pin::new(req).poll(cx),
+ PendingInner::Error(ref mut err) => Poll::Ready(Err(err
+ .take()
+ .expect("Pending error polled more than once"))),
+ }
+ }
+}
+
+impl Future for PendingRequest {
+ type Output = Result<Response, crate::Error>;
+
+ fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
+ if let Some(delay) = self.as_mut().timeout().as_mut().as_pin_mut() {
+ if let Poll::Ready(()) = delay.poll(cx) {
+ return Poll::Ready(Err(
+ crate::error::request(crate::error::TimedOut).with_url(self.url.clone())
+ ));
+ }
+ }
+
+ loop {
+ let res = match self.as_mut().in_flight().get_mut() {
+ ResponseFuture::Default(r) => match Pin::new(r).poll(cx) {
+ Poll::Ready(Err(e)) => {
+ if self.as_mut().retry_error(&e) {
+ continue;
+ }
+ return Poll::Ready(Err(
+ crate::error::request(e).with_url(self.url.clone())
+ ));
+ }
+ Poll::Ready(Ok(res)) => res,
+ Poll::Pending => return Poll::Pending,
+ },
+ #[cfg(feature = "http3")]
+ ResponseFuture::H3(r) => match Pin::new(r).poll(cx) {
+ Poll::Ready(Err(e)) => {
+ if self.as_mut().retry_error(&e) {
+ continue;
+ }
+ return Poll::Ready(Err(
+ crate::error::request(e).with_url(self.url.clone())
+ ));
+ }
+ Poll::Ready(Ok(res)) => res,
+ Poll::Pending => return Poll::Pending,
+ },
+ };
+
+ #[cfg(feature = "cookies")]
+ {
+ if let Some(ref cookie_store) = self.client.cookie_store {
+ let mut cookies =
+ cookie::extract_response_cookie_headers(&res.headers()).peekable();
+ if cookies.peek().is_some() {
+ cookie_store.set_cookies(&mut cookies, &self.url);
+ }
+ }
+ }
+ let should_redirect = match res.status() {
+ StatusCode::MOVED_PERMANENTLY | StatusCode::FOUND | StatusCode::SEE_OTHER => {
+ self.body = None;
+ for header in &[
+ TRANSFER_ENCODING,
+ CONTENT_ENCODING,
+ CONTENT_TYPE,
+ CONTENT_LENGTH,
+ ] {
+ self.headers.remove(header);
+ }
+
+ match self.method {
+ Method::GET | Method::HEAD => {}
+ _ => {
+ self.method = Method::GET;
+ }
+ }
+ true
+ }
+ StatusCode::TEMPORARY_REDIRECT | StatusCode::PERMANENT_REDIRECT => {
+ match self.body {
+ Some(Some(_)) | None => true,
+ Some(None) => false,
+ }
+ }
+ _ => false,
+ };
+ if should_redirect {
+ let loc = res.headers().get(LOCATION).and_then(|val| {
+ let loc = (|| -> Option<Url> {
+ // Some sites may send a utf-8 Location header,
+ // even though we're supposed to treat those bytes
+ // as opaque, we'll check specifically for utf8.
+ self.url.join(str::from_utf8(val.as_bytes()).ok()?).ok()
+ })();
+
+ // Check that the `url` is also a valid `http::Uri`.
+ //
+ // If not, just log it and skip the redirect.
+ let loc = loc.and_then(|url| {
+ if try_uri(&url).is_some() {
+ Some(url)
+ } else {
+ None
+ }
+ });
+
+ if loc.is_none() {
+ debug!("Location header had invalid URI: {:?}", val);
+ }
+ loc
+ });
+ if let Some(loc) = loc {
+ if self.client.referer {
+ if let Some(referer) = make_referer(&loc, &self.url) {
+ self.headers.insert(REFERER, referer);
+ }
+ }
+ let url = self.url.clone();
+ self.as_mut().urls().push(url);
+ let action = self
+ .client
+ .redirect_policy
+ .check(res.status(), &loc, &self.urls);
+
+ match action {
+ redirect::ActionKind::Follow => {
+ debug!("redirecting '{}' to '{}'", self.url, loc);
+
+ if self.client.https_only && loc.scheme() != "https" {
+ return Poll::Ready(Err(error::redirect(
+ error::url_bad_scheme(loc.clone()),
+ loc,
+ )));
+ }
+
+ self.url = loc;
+ let mut headers =
+ std::mem::replace(self.as_mut().headers(), HeaderMap::new());
+
+ remove_sensitive_headers(&mut headers, &self.url, &self.urls);
+ let uri = expect_uri(&self.url);
+ let body = match self.body {
+ Some(Some(ref body)) => Body::reusable(body.clone()),
+ _ => Body::empty(),
+ };
+
+ // Add cookies from the cookie store.
+ #[cfg(feature = "cookies")]
+ {
+ if let Some(ref cookie_store) = self.client.cookie_store {
+ add_cookie_header(&mut headers, &**cookie_store, &self.url);
+ }
+ }
+
+ *self.as_mut().in_flight().get_mut() =
+ match *self.as_mut().in_flight().as_ref() {
+ #[cfg(feature = "http3")]
+ ResponseFuture::H3(_) => {
+ let mut req = hyper::Request::builder()
+ .method(self.method.clone())
+ .uri(uri.clone())
+ .body(body)
+ .expect("valid request parts");
+ *req.headers_mut() = headers.clone();
+ std::mem::swap(self.as_mut().headers(), &mut headers);
+ ResponseFuture::H3(self.client.h3_client.request(req))
+ }
+ _ => {
+ let mut req = hyper::Request::builder()
+ .method(self.method.clone())
+ .uri(uri.clone())
+ .body(body.into_stream())
+ .expect("valid request parts");
+ *req.headers_mut() = headers.clone();
+ std::mem::swap(self.as_mut().headers(), &mut headers);
+ ResponseFuture::Default(self.client.hyper.request(req))
+ }
+ };
+
+ continue;
+ }
+ redirect::ActionKind::Stop => {
+ debug!("redirect policy disallowed redirection to '{}'", loc);
+ }
+ redirect::ActionKind::Error(err) => {
+ return Poll::Ready(Err(crate::error::redirect(err, self.url.clone())));
+ }
+ }
+ }
+ }
+
+ let res = Response::new(
+ res,
+ self.url.clone(),
+ self.client.accepts,
+ self.timeout.take(),
+ );
+ return Poll::Ready(Ok(res));
+ }
+ }
+}
+
+impl fmt::Debug for Pending {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self.inner {
+ PendingInner::Request(ref req) => f
+ .debug_struct("Pending")
+ .field("method", &req.method)
+ .field("url", &req.url)
+ .finish(),
+ PendingInner::Error(ref err) => f.debug_struct("Pending").field("error", err).finish(),
+ }
+ }
+}
+
+fn make_referer(next: &Url, previous: &Url) -> Option<HeaderValue> {
+ if next.scheme() == "http" && previous.scheme() == "https" {
+ return None;
+ }
+
+ let mut referer = previous.clone();
+ let _ = referer.set_username("");
+ let _ = referer.set_password(None);
+ referer.set_fragment(None);
+ referer.as_str().parse().ok()
+}
+
+#[cfg(feature = "cookies")]
+fn add_cookie_header(headers: &mut HeaderMap, cookie_store: &dyn cookie::CookieStore, url: &Url) {
+ if let Some(header) = cookie_store.cookies(url) {
+ headers.insert(crate::header::COOKIE, header);
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ #[tokio::test]
+ async fn execute_request_rejects_invald_urls() {
+ let url_str = "hxxps://www.rust-lang.org/";
+ let url = url::Url::parse(url_str).unwrap();
+ let result = crate::get(url.clone()).await;
+
+ assert!(result.is_err());
+ let err = result.err().unwrap();
+ assert!(err.is_builder());
+ assert_eq!(url_str, err.url().unwrap().as_str());
+ }
+}