blob: b247624ed26a98011aa207512bac59824ed77d6e [file] [log] [blame]
#[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 = "hickory-dns")]
use crate::dns::hickory::HickoryDnsResolver;
use crate::dns::{gai::GaiResolver, DnsResolverWithOverrides, DynResolver, Resolve};
use crate::error;
use crate::into_url::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_info: bool,
#[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,
http1_ignore_invalid_headers_in_responses: bool,
http1_allow_spaces_after_header_name_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>>,
hickory_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_info: false,
#[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,
http1_ignore_invalid_headers_in_responses: false,
http1_allow_spaces_after_header_name_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,
hickory_dns: cfg!(feature = "hickory-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.hickory_dns {
false => Arc::new(GaiResolver::new()),
#[cfg(feature = "hickory-dns")]
true => Arc::new(HickoryDnsResolver::default()),
#[cfg(not(feature = "hickory-dns"))]
true => unreachable!("hickory-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 mut http = HttpConnector::new_with_resolver(DynResolver::new(resolver.clone()));
http.set_connect_timeout(config.connect_timeout);
#[cfg(all(feature = "http3", feature = "__rustls"))]
let build_h3_connector =
|resolver,
tls,
quic_max_idle_timeout: Option<Duration>,
quic_stream_receive_window,
quic_receive_window,
quic_send_window,
local_address,
http_version_pref: &HttpVersionPref| {
let mut transport_config = TransportConfig::default();
if let Some(max_idle_timeout) = 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) = quic_stream_receive_window {
transport_config.stream_receive_window(stream_receive_window);
}
if let Some(receive_window) = quic_receive_window {
transport_config.receive_window(receive_window);
}
if let Some(send_window) = quic_send_window {
transport_config.send_window(send_window);
}
let res = H3Connector::new(
DynResolver::new(resolver),
tls,
local_address,
transport_config,
);
match res {
Ok(connector) => Ok(Some(connector)),
Err(err) => {
if let HttpVersionPref::Http3 = http_version_pref {
Err(error::builder(err))
} else {
Ok(None)
}
}
}
};
#[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)?;
}
}
#[cfg(all(feature = "__rustls", not(feature = "native-tls")))]
{
// Default backend + rustls Identity doesn't work.
if let Some(_id) = config.identity {
return Err(crate::error::builder("incompatible TLS identity type"));
}
}
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,
config.tls_info,
)?
}
#[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,
config.tls_info,
),
#[cfg(feature = "__rustls")]
TlsBackend::BuiltRustls(conn) => {
#[cfg(feature = "http3")]
{
h3_connector = build_h3_connector(
resolver,
conn.clone(),
config.quic_max_idle_timeout,
config.quic_stream_receive_window,
config.quic_receive_window,
config.quic_send_window,
config.local_address,
&config.http_version_pref,
)?;
}
Connector::new_rustls_tls(
http,
conn,
proxies.clone(),
user_agent(&config.headers),
config.local_address,
config.nodelay,
config.tls_info,
)
}
#[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.iter().map(|trust_anchor| {
OwnedTrustAnchor::from_subject_spki_name_constraints(
trust_anchor.subject,
trust_anchor.spki,
trust_anchor.name_constraints,
)
});
root_cert_store.add_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;
h3_connector = build_h3_connector(
resolver,
tls.clone(),
config.quic_max_idle_timeout,
config.quic_stream_receive_window,
config.quic_receive_window,
config.quic_send_window,
config.local_address,
&config.http_version_pref,
)?;
}
Connector::new_rustls_tls(
http,
tls,
proxies.clone(),
user_agent(&config.headers),
config.local_address,
config.nodelay,
config.tls_info,
)
}
#[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);
}
if config.http1_ignore_invalid_headers_in_responses {
builder.http1_ignore_invalid_headers_in_responses(true);
}
if config.http1_allow_spaces_after_header_name_in_responses {
builder.http1_allow_spaces_after_header_name_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,
// Use match instead of map since config is partially moved
// and it cannot be used in closure
#[cfg(feature = "http3")]
h3_client: match h3_connector {
Some(h3_connector) => {
Some(H3Client::new(h3_connector, config.pool_idle_timeout))
}
None => None,
},
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. Enabling the cookie store
/// with `cookie_store(true)` will set the store to a default implementation.
/// It is **not** necessary to call [cookie_store(true)](crate::ClientBuilder::cookie_store) if [cookie_provider(my_cookie_store)](crate::ClientBuilder::cookie_provider)
/// is used; calling [cookie_store(true)](crate::ClientBuilder::cookie_store) _after_ [cookie_provider(my_cookie_store)](crate::ClientBuilder::cookie_provider) will result
/// in the provided `my_cookie_store` being **overridden** with a default implementation.
///
/// # 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. It is **not** necessary to also call
/// [cookie_store(true)](crate::ClientBuilder::cookie_store) if [cookie_provider(my_cookie_store)](crate::ClientBuilder::cookie_provider) is used; calling
/// [cookie_store(true)](crate::ClientBuilder::cookie_store) _after_ [cookie_provider(my_cookie_store)](crate::ClientBuilder::cookie_provider) will result
/// in the provided `my_cookie_store` being **overridden** with a default implementation.
///
/// # 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
}
/// Sets whether invalid header lines should be silently ignored in HTTP/1 responses.
pub fn http1_ignore_invalid_headers_in_responses(mut self, value: bool) -> ClientBuilder {
self.config.http1_ignore_invalid_headers_in_responses = value;
self
}
/// Set whether HTTP/1 connections will accept spaces between header
/// names and the colon that follow them in responses.
///
/// Newline codepoints (`\r` and `\n`) will be transformed to spaces when
/// parsing.
pub fn http1_allow_spaces_after_header_name_in_responses(
mut self,
value: bool,
) -> ClientBuilder {
self.config
.http1_allow_spaces_after_header_name_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")]
#[cfg_attr(docsrs, doc(cfg(all(reqwest_unstable, 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
}
/// Add TLS information as `TlsInfo` extension to responses.
///
/// # 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_info(mut self, tls_info: bool) -> ClientBuilder {
self.config.tls_info = tls_info;
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
}
/// Enables the [hickory-dns](hickory_resolver) async resolver instead of a default threadpool
/// using `getaddrinfo`.
///
/// If the `hickory-dns` feature is turned on, the default option is enabled.
///
/// # Optional
///
/// This requires the optional `hickory-dns` feature to be enabled
#[cfg(feature = "hickory-dns")]
#[cfg_attr(docsrs, doc(cfg(feature = "hickory-dns")))]
#[deprecated(note = "use `hickory_dns` instead")]
pub fn trust_dns(mut self, enable: bool) -> ClientBuilder {
self.config.hickory_dns = enable;
self
}
/// Enables the [hickory-dns](hickory_resolver) async resolver instead of a default threadpool
/// using `getaddrinfo`.
///
/// If the `hickory-dns` feature is turned on, the default option is enabled.
///
/// # Optional
///
/// This requires the optional `hickory-dns` feature to be enabled
#[cfg(feature = "hickory-dns")]
#[cfg_attr(docsrs, doc(cfg(feature = "hickory-dns")))]
pub fn hickory_dns(mut self, enable: bool) -> ClientBuilder {
self.config.hickory_dns = enable;
self
}
/// Disables the hickory-dns async resolver.
///
/// This method exists even if the optional `hickory-dns` feature is not enabled.
/// This can be used to ensure a `Client` doesn't use the hickory-dns async resolver
/// even if another dependency were to enable the optional `hickory-dns` feature.
#[deprecated(note = "use `no_hickory_dns` instead")]
pub fn no_trust_dns(self) -> ClientBuilder {
#[cfg(feature = "hickory-dns")]
{
self.hickory_dns(false)
}
#[cfg(not(feature = "hickory-dns"))]
{
self
}
}
/// Disables the hickory-dns async resolver.
///
/// This method exists even if the optional `hickory-dns` feature is not enabled.
/// This can be used to ensure a `Client` doesn't use the hickory-dns async resolver
/// even if another dependency were to enable the optional `hickory-dns` feature.
pub fn no_hickory_dns(self) -> ClientBuilder {
#[cfg(feature = "hickory-dns")]
{
self.hickory_dns(false)
}
#[cfg(not(feature = "hickory-dns"))]
{
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")]
#[cfg_attr(docsrs, doc(cfg(all(reqwest_unstable, 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")]
#[cfg_attr(docsrs, doc(cfg(all(reqwest_unstable, 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")]
#[cfg_attr(docsrs, doc(cfg(all(reqwest_unstable, 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")]
#[cfg_attr(docsrs, doc(cfg(all(reqwest_unstable, 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")]
#[cfg_attr(docsrs, doc(cfg(all(reqwest_unstable, 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 = match try_uri(&url) {
Ok(uri) => uri,
_ => return Pending::new_err(error::url_invalid_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 if self.inner.h3_client.is_some() => {
let mut req = builder.body(body).expect("valid request parts");
*req.headers_mut() = headers.clone();
ResponseFuture::H3(self.inner.h3_client.as_ref().unwrap().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 self.http1_ignore_invalid_headers_in_responses {
f.field("http1_ignore_invalid_headers_in_responses", &true);
}
if self.http1_allow_spaces_after_header_name_in_responses {
f.field("http1_allow_spaces_after_header_name_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);
f.field("tls_info", &self.tls_info);
}
#[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: Option<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;
// If it parsed once, it should parse again
let uri = try_uri(&self.url).expect("URL was already validated as URI");
*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
.as_ref()
.expect("H3 client must exists, otherwise we can't have a h3 request here")
.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 {err} can be retried");
// 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!
if err.is_go_away() && err.is_remote() && err.reason() == Some(h2::Reason::NO_ERROR) {
return true;
}
// REFUSED_STREAM was sent from the server, which is safe to retry.
// https://www.rfc-editor.org/rfc/rfc9113.html#section-8.7-3.2
if err.is_reset() && err.is_remote() && err.reason() == Some(h2::Reason::REFUSED_STREAM)
{
return true;
}
}
}
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_ok() {
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 loc.scheme() != "http" && loc.scheme() != "https" {
return Poll::Ready(Err(error::url_bad_scheme(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 = try_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
.as_ref()
.expect("H3 client must exists, otherwise we can't have a h3 request here")
.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_invalid_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());
}
/// https://github.com/seanmonstar/reqwest/issues/668
#[tokio::test]
async fn execute_request_rejects_invalid_hostname() {
let url_str = "https://{{hostname}}/";
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());
}
}