Skip to content

Commit

Permalink
feat(client): Apply configuration sequentially (#391)
Browse files Browse the repository at this point in the history
  • Loading branch information
0x676e67 authored Jan 30, 2025
1 parent 2022b25 commit 775db82
Showing 1 changed file with 61 additions and 67 deletions.
128 changes: 61 additions & 67 deletions src/client/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ use crate::into_url::try_uri;
use crate::util::{
self,
client::{
connect::HttpConnector, Builder, Http1Builder, Http2Builder, InnerRequest, NetworkScheme,
NetworkSchemeBuilder,
connect::HttpConnector, Builder, Client as HyperClient, Http1Builder, Http2Builder,
InnerRequest, NetworkScheme, NetworkSchemeBuilder,
},
rt::{tokio::TokioTimer, TokioExecutor},
};
use crate::{cfg_bindable_device, error, impl_debug, Http1Config, Http2Config};
use crate::{cfg_bindable_device, error, impl_debug, Http1Config, Http2Config, TlsConfig};
use crate::{
redirect,
tls::{AlpnProtos, BoringTlsConnector, RootCertStore, TlsVersion},
Expand All @@ -38,7 +38,7 @@ use crate::{IntoUrl, Method, Proxy, StatusCode, Url};
use super::decoder::Accepts;
use super::request::{Request, RequestBuilder};
use super::response::Response;
use super::{Body, HttpContext, HttpContextProvider};
use super::{Body, HttpContextProvider};

use bytes::Bytes;
use http::{
Expand Down Expand Up @@ -91,6 +91,8 @@ pub struct ClientBuilder {

struct Config {
// NOTE: When adding a new field, update `fmt::Debug for ClientBuilder`
headers: HeaderMap,
headers_order: Option<Cow<'static, [HeaderName]>>,
accepts: Accepts,
connect_timeout: Option<Duration>,
connection_verbose: bool,
Expand Down Expand Up @@ -121,7 +123,7 @@ struct Config {
tls_info: bool,
connector_layers: Vec<BoxedConnectorLayer>,
builder: Builder,
http_context: HttpContext,
tls_config: TlsConfig,
}

impl Default for ClientBuilder {
Expand All @@ -138,6 +140,8 @@ impl ClientBuilder {
ClientBuilder {
config: Config {
error: None,
headers: HeaderMap::new(),
headers_order: None,
accepts: Accepts::default(),
connect_timeout: None,
connection_verbose: false,
Expand All @@ -164,12 +168,12 @@ impl ClientBuilder {
dns_overrides: HashMap::new(),
dns_resolver: None,
base_url: None,
builder: util::client::Client::builder(TokioExecutor::new()),
builder: HyperClient::builder(TokioExecutor::new()),
https_only: false,
http2_max_retry_count: 2,
tls_info: false,
connector_layers: Vec::new(),
http_context: HttpContext::default(),
tls_config: TlsConfig::default(),
},
}
}
Expand All @@ -193,10 +197,7 @@ impl ClientBuilder {
}
let proxies_maybe_http_auth = proxies.iter().any(|p| p.maybe_has_http_auth());

let http2_only = matches!(
config.http_context.tls_config.alpn_protos,
AlpnProtos::Http2
);
let http2_only = matches!(config.tls_config.alpn_protos, AlpnProtos::Http2);

config
.builder
Expand All @@ -207,17 +208,7 @@ impl ClientBuilder {
.pool_max_idle_per_host(config.pool_max_idle_per_host)
.pool_max_size(config.pool_max_size);

if let Some(http1_config) = config.http_context.http1_config {
let builder = config.builder.http1();
apply_http1_config(builder, http1_config);
}

if let Some(http2_config) = config.http_context.http2_config {
let builder = config.builder.http2();
apply_http2_config(builder, http2_config)
}

let mut connector_builder = {
let connector = {
let mut resolver: Arc<dyn Resolve> = if let Some(dns_resolver) = config.dns_resolver {
dns_resolver
} else if config.hickory_dns {
Expand All @@ -241,26 +232,22 @@ impl ClientBuilder {
let mut http = HttpConnector::new_with_resolver(DynResolver::new(resolver));
http.set_connect_timeout(config.connect_timeout);

let tls = BoringTlsConnector::new(config.http_context.tls_config)?;
ConnectorBuilder::new(http, tls, config.nodelay, config.tls_info)
let tls = BoringTlsConnector::new(config.tls_config)?;
let mut builder = ConnectorBuilder::new(http, tls, config.nodelay, config.tls_info);
builder.set_timeout(config.connect_timeout);
builder.set_verbose(config.connection_verbose);
builder.set_keepalive(config.tcp_keepalive);
builder.build(config.connector_layers)
};

connector_builder.set_timeout(config.connect_timeout);
connector_builder.set_verbose(config.connection_verbose);
connector_builder.set_keepalive(config.tcp_keepalive);

let hyper = config
.builder
.build(connector_builder.build(config.connector_layers));

Ok(Client {
inner: Arc::new(ClientRef {
accepts: config.accepts,
#[cfg(feature = "cookies")]
cookie_store: config.cookie_store,
hyper,
headers: config.http_context.default_headers.unwrap_or_default(),
headers_order: config.http_context.headers_order,
hyper: config.builder.build(connector),
headers: config.headers,
headers_order: config.headers_order,
redirect: config.redirect_policy,
redirect_with_proxy_auth: config.redirect_with_proxy_auth,
referer: config.referer,
Expand Down Expand Up @@ -337,11 +324,7 @@ impl ClientBuilder {
{
match value.try_into() {
Ok(value) => {
self.config
.http_context
.default_headers
.get_or_insert_with(Default::default)
.insert(USER_AGENT, value);
self.config.headers.insert(USER_AGENT, value);
}
Err(e) => {
self.config.error = Some(crate::error::builder(e.into()));
Expand Down Expand Up @@ -394,11 +377,8 @@ impl ClientBuilder {
/// # Ok(())
/// # }
/// ```
pub fn default_headers(mut self, headers: HeaderMap) -> ClientBuilder {
std::mem::swap(
&mut self.config.http_context.default_headers,
&mut Some(headers),
);
pub fn default_headers(mut self, mut headers: HeaderMap) -> ClientBuilder {
std::mem::swap(&mut self.config.headers, &mut headers);
self
}

Expand All @@ -409,7 +389,7 @@ impl ClientBuilder {
/// The host header needs to be manually inserted if you want to modify its order.
/// Otherwise it will be inserted by hyper after sorting.
pub fn headers_order(mut self, order: impl Into<Cow<'static, [HeaderName]>>) -> ClientBuilder {
self.config.http_context.headers_order = Some(order.into());
std::mem::swap(&mut self.config.headers_order, &mut Some(order.into()));
self
}

Expand Down Expand Up @@ -772,13 +752,13 @@ impl ClientBuilder {

/// Only use HTTP/1.
pub fn http1_only(mut self) -> ClientBuilder {
self.config.http_context.tls_config.alpn_protos = AlpnProtos::Http1;
self.config.tls_config.alpn_protos = AlpnProtos::Http1;
self
}

/// Only use HTTP/2.
pub fn http2_only(mut self) -> ClientBuilder {
self.config.http_context.tls_config.alpn_protos = AlpnProtos::Http2;
self.config.tls_config.alpn_protos = AlpnProtos::Http2;
self
}

Expand Down Expand Up @@ -927,9 +907,6 @@ impl ClientBuilder {
/// to use the specified HTTP context. It allows the client to mimic the behavior of different
/// versions or setups, which can be useful for testing or ensuring compatibility with various environments.
///
/// The configuration set by this method will have the highest priority, overriding any other
/// config that may have been previously set.
///
/// # Arguments
///
/// * `provider` - The HTTP context provider, which can be any type that implements the `HttpContextProvider` trait.
Expand All @@ -948,30 +925,48 @@ impl ClientBuilder {
/// .build()
/// .unwrap();
/// ```
#[inline]
pub fn impersonate<P>(mut self, provider: P) -> ClientBuilder
where
P: HttpContextProvider,
{
std::mem::swap(&mut self.config.http_context, &mut provider.context());
let mut http_context = provider.context();

if let Some(mut headers) = http_context.default_headers {
std::mem::swap(&mut self.config.headers, &mut headers);
}

if let Some(headers_order) = http_context.headers_order {
std::mem::swap(&mut self.config.headers_order, &mut Some(headers_order));
}

if let Some(http1_config) = http_context.http1_config.take() {
let builder = self.config.builder.http1();
apply_http1_config(builder, http1_config);
}
if let Some(http2_config) = http_context.http2_config.take() {
let builder = self.config.builder.http2();
apply_http2_config(builder, http2_config)
}

std::mem::swap(&mut self.config.tls_config, &mut http_context.tls_config);
self
}

/// Enable Encrypted Client Hello (Secure SNI)
pub fn enable_ech_grease(mut self, enabled: bool) -> ClientBuilder {
self.config.http_context.tls_config.enable_ech_grease = enabled;
self.config.tls_config.enable_ech_grease = enabled;
self
}

/// Enable TLS permute_extensions
pub fn permute_extensions(mut self, enabled: bool) -> ClientBuilder {
self.config.http_context.tls_config.permute_extensions = Some(enabled);
self.config.tls_config.permute_extensions = Some(enabled);
self
}

/// Enable TLS pre_shared_key
pub fn pre_shared_key(mut self, enabled: bool) -> ClientBuilder {
self.config.http_context.tls_config.pre_shared_key = enabled;
self.config.tls_config.pre_shared_key = enabled;
self
}

Expand All @@ -991,15 +986,15 @@ impl ClientBuilder {
///
/// feature to be enabled.
pub fn danger_accept_invalid_certs(mut self, accept_invalid_certs: bool) -> ClientBuilder {
self.config.http_context.tls_config.certs_verification = !accept_invalid_certs;
self.config.tls_config.certs_verification = !accept_invalid_certs;
self
}

/// Configures the use of Server Name Indication (SNI) when connecting.
///
/// Defaults to `true`.
pub fn tls_sni(mut self, tls_sni: bool) -> ClientBuilder {
self.config.http_context.tls_config.tls_sni = tls_sni;
self.config.tls_config.tls_sni = tls_sni;
self
}

Expand All @@ -1013,7 +1008,7 @@ impl ClientBuilder {
/// 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.
pub fn verify_hostname(mut self, verify_hostname: bool) -> ClientBuilder {
self.config.http_context.tls_config.verify_hostname = verify_hostname;
self.config.tls_config.verify_hostname = verify_hostname;
self
}

Expand All @@ -1032,7 +1027,7 @@ impl ClientBuilder {
///
/// feature to be enabled.
pub fn min_tls_version(mut self, version: TlsVersion) -> ClientBuilder {
self.config.http_context.tls_config.min_tls_version = Some(version);
self.config.tls_config.min_tls_version = Some(version);
self
}

Expand All @@ -1051,7 +1046,7 @@ impl ClientBuilder {
///
/// feature to be enabled.
pub fn max_tls_version(mut self, version: TlsVersion) -> ClientBuilder {
self.config.http_context.tls_config.max_tls_version = Some(version);
self.config.tls_config.max_tls_version = Some(version);
self
}

Expand All @@ -1078,7 +1073,7 @@ impl ClientBuilder {
where
S: Into<RootCertStore>,
{
self.config.http_context.tls_config.root_certs_store = store.into();
self.config.tls_config.root_certs_store = store.into();
self
}

Expand Down Expand Up @@ -1182,8 +1177,6 @@ impl ClientBuilder {
}
}

type HyperClient = util::client::Client<Connector, super::Body>;

impl Default for Client {
fn default() -> Self {
Self::new()
Expand Down Expand Up @@ -1610,6 +1603,8 @@ impl_debug!(
Config,
{
accepts,
headers,
headers_order,
proxies,
redirect_policy,
accepts,
Expand All @@ -1622,7 +1617,7 @@ impl_debug!(
dns_overrides,
base_url,
builder,
http_context
tls_config
}
);

Expand All @@ -1633,7 +1628,7 @@ struct ClientRef {
cookie_store: Option<Arc<dyn cookie::CookieStore>>,
headers: HeaderMap,
headers_order: Option<Cow<'static, [HeaderName]>>,
hyper: HyperClient,
hyper: HyperClient<Connector, super::Body>,
redirect: redirect::Policy,
redirect_with_proxy_auth: bool,
referer: bool,
Expand Down Expand Up @@ -1849,7 +1844,6 @@ impl<'c> ClientMut<'c> {
/// let mut client = Client::builder().build().unwrap();
/// client.impersonate(Impersonate::Firefox128);
/// ```
#[inline]
pub fn impersonate<P>(&mut self, provider: P) -> &mut ClientMut<'c>
where
P: HttpContextProvider,
Expand Down

0 comments on commit 775db82

Please sign in to comment.