diff --git a/src/async_impl/client.rs b/src/async_impl/client.rs index 56aa2a6ea..3b206aea2 100644 --- a/src/async_impl/client.rs +++ b/src/async_impl/client.rs @@ -17,7 +17,7 @@ use super::Body; use crate::async_impl::h3_client::connect::{H3ClientConfig, H3Connector}; #[cfg(feature = "http3")] use crate::async_impl::h3_client::{H3Client, H3ResponseFuture}; -use crate::config::{RequestConfig, RequestTimeout}; +use crate::config::{self, RequestConfig}; use crate::connect::{ sealed::{Conn, Unnameable}, BoxedConnectorLayer, BoxedConnectorService, Connector, ConnectorBuilder, @@ -45,7 +45,7 @@ use http::header::{ CONTENT_TYPE, LOCATION, PROXY_AUTHORIZATION, RANGE, REFERER, TRANSFER_ENCODING, USER_AGENT, }; use http::uri::Scheme; -use http::Uri; +use http::{Extensions, Uri}; use hyper_util::client::legacy::connect::HttpConnector; use log::debug; #[cfg(feature = "default-tls")] @@ -889,7 +889,7 @@ impl ClientBuilder { Ok(Client { inner: Arc::new(ClientRef { - accepts: config.accepts, + accepts: RequestConfig::new(Some(config.accepts)), #[cfg(feature = "cookies")] cookie_store: config.cookie_store, // Use match instead of map since config is partially moved, @@ -2314,7 +2314,12 @@ impl Client { } } - let accept_encoding = self.inner.accepts.as_str(); + let accept_encoding = self + .inner + .accepts + .fetch(&extensions) + .expect("accepts must be set in the client ref") + .as_str(); if let Some(accept_encoding) = accept_encoding { if !headers.contains_key(ACCEPT_ENCODING) && !headers.contains_key(RANGE) { @@ -2376,6 +2381,7 @@ impl Client { url, headers, body: reusable, + extensions, urls: Vec::new(), @@ -2593,7 +2599,7 @@ impl Config { } struct ClientRef { - accepts: Accepts, + accepts: RequestConfig, #[cfg(feature = "cookies")] cookie_store: Option>, headers: HeaderMap, @@ -2602,7 +2608,7 @@ struct ClientRef { h3_client: Option, redirect_policy: redirect::Policy, referer: bool, - request_timeout: RequestConfig, + request_timeout: RequestConfig, read_timeout: Option, proxies: Arc>, proxies_maybe_http_auth: bool, @@ -2621,7 +2627,7 @@ impl ClientRef { } } - f.field("accepts", &self.accepts); + self.accepts.fmt_as_field(f); if !self.proxies.is_empty() { f.field("proxies", &self.proxies); @@ -2663,6 +2669,7 @@ pin_project! { url: Url, headers: HeaderMap, body: Option>, + extensions: Extensions, urls: Vec, @@ -3033,7 +3040,10 @@ impl Future for PendingRequest { let res = Response::new( res, self.url.clone(), - self.client.accepts, + self.client + .accepts + .fetch_owned(&self.extensions) + .expect("accepts must has been set"), self.total_timeout.take(), self.read_timeout, ); diff --git a/src/async_impl/decoder.rs b/src/async_impl/decoder.rs index d21962483..65a924ac8 100644 --- a/src/async_impl/decoder.rs +++ b/src/async_impl/decoder.rs @@ -61,7 +61,7 @@ use tokio_util::io::StreamReader; use super::body::ResponseBody; #[derive(Clone, Copy, Debug)] -pub(super) struct Accepts { +pub(crate) struct Accepts { #[cfg(feature = "gzip")] pub(super) gzip: bool, #[cfg(feature = "brotli")] diff --git a/src/async_impl/request.rs b/src/async_impl/request.rs index 3795bde9d..7185d99e5 100644 --- a/src/async_impl/request.rs +++ b/src/async_impl/request.rs @@ -12,10 +12,11 @@ use super::client::{Client, Pending}; #[cfg(feature = "multipart")] use super::multipart; use super::response::Response; -use crate::config::{RequestConfig, RequestTimeout}; +use crate::config::{self, RequestConfig}; #[cfg(feature = "multipart")] use crate::header::CONTENT_LENGTH; use crate::header::{HeaderMap, HeaderName, HeaderValue, CONTENT_TYPE}; + use crate::{Method, Url}; use http::{request::Parts, Extensions, Request as HttpRequest, Version}; @@ -115,13 +116,13 @@ impl Request { /// Get the timeout. #[inline] pub fn timeout(&self) -> Option<&Duration> { - RequestConfig::::get(&self.extensions) + RequestConfig::::get(&self.extensions) } /// Get a mutable reference to the timeout. #[inline] pub fn timeout_mut(&mut self) -> &mut Option { - RequestConfig::::get_mut(&mut self.extensions) + RequestConfig::::get_mut(&mut self.extensions) } /// Get the http version. @@ -136,6 +137,126 @@ impl Request { &mut self.version } + /// Get the `gzip` option on this request. + #[inline] + pub fn gzip(&self) -> Option<&bool> { + #[cfg(feature = "gzip")] + { + RequestConfig::::get(&self.extensions).map(|v| &v.gzip) + } + #[cfg(not(feature = "gzip"))] + { + None + } + } + + /// Set auto gzip decompression by checking the `Content-Encoding` response header. + /// + /// Refer to [`crate::ClientBuilder::gzip`] for more details. + #[inline] + pub fn gzip_mut(&mut self) -> Option<&mut bool> { + #[cfg(feature = "gzip")] + { + RequestConfig::::get_mut(&mut self.extensions) + .as_mut() + .map(|v| &mut v.gzip) + } + #[cfg(not(feature = "gzip"))] + { + None + } + } + + /// Get the `brotli` option on this request. + #[inline] + pub fn brotli(&self) -> Option<&bool> { + #[cfg(feature = "brotli")] + { + RequestConfig::::get(&self.extensions).map(|v| &v.brotli) + } + #[cfg(not(feature = "brotli"))] + { + None + } + } + + /// Set auto brotli decompression by checking the `Content-Encoding` response header. + /// + /// Refer to [`crate::ClientBuilder::brotli`] for more details. + #[inline] + pub fn brotli_mut(&mut self) -> Option<&mut bool> { + #[cfg(feature = "brotli")] + { + RequestConfig::::get_mut(&mut self.extensions) + .as_mut() + .map(|v| &mut v.brotli) + } + #[cfg(not(feature = "brotli"))] + { + None + } + } + + /// Get the `zstd` option on this request. + #[inline] + pub fn zstd(&self) -> Option<&bool> { + #[cfg(feature = "zstd")] + { + RequestConfig::::get(&self.extensions).map(|v| &v.zstd) + } + #[cfg(not(feature = "zstd"))] + { + None + } + } + + /// Set auto zstd decompression by checking the `Content-Encoding` response header. + /// + /// Refer to [`crate::ClientBuilder::zstd`] for more details. + #[inline] + pub fn zstd_mut(&mut self) -> Option<&mut bool> { + #[cfg(feature = "zstd")] + { + RequestConfig::::get_mut(&mut self.extensions) + .as_mut() + .map(|v| &mut v.zstd) + } + #[cfg(not(feature = "zstd"))] + { + None + } + } + + /// Get the `deflate` option on this request. + #[inline] + pub fn deflate(&self) -> Option<&bool> { + #[cfg(feature = "deflate")] + { + RequestConfig::::get(&self.extensions).map(|v| &v.deflate) + } + #[cfg(not(feature = "deflate"))] + { + None + } + } + + /// Enable auto deflate decompression by checking the `Content-Encoding` response header. + /// + /// Refer to [`crate::ClientBuilder::deflate`] for more details. + #[inline] + pub fn deflate_mut(&mut self) -> Option<&mut bool> { + #[cfg(feature = "deflate")] + { + RequestConfig::::get_mut(&mut self.extensions) + .as_mut() + .map(|v| &mut v.deflate) + } + #[cfg(not(feature = "deflate"))] + { + None + } + } + /// Attempt to clone the request. /// /// `None` is returned if the request can not be cloned, i.e. if the body is a stream. diff --git a/src/config.rs b/src/config.rs index 5b377b2c1..fd2629ae6 100644 --- a/src/config.rs +++ b/src/config.rs @@ -78,6 +78,17 @@ where .or(self.0.as_ref()) } + /// Retrieve the owned value from the request-scoped configuration. + /// + /// If the request specifies a value, use that value; otherwise, attempt to retrieve it from the current instance (typically a client instance). + /// + /// This owned version of `fetch` can consume the config value directly to avoid extra clone. + pub(crate) fn fetch_owned<'request>(self, ext: &Extensions) -> Option { + ext.get::>() + .and_then(|v| v.0.clone()) + .or(self.0) + } + /// Retrieve the value from the request's Extensions. pub(crate) fn get(ext: &Extensions) -> Option<&T::Value> { ext.get::>().and_then(|v| v.0.as_ref()) @@ -108,3 +119,10 @@ pub(crate) struct RequestTimeout; impl RequestConfigValue for RequestTimeout { type Value = Duration; } + +#[derive(Clone, Copy)] +pub(crate) struct Accepts; + +impl RequestConfigValue for Accepts { + type Value = crate::async_impl::decoder::Accepts; +}