Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(client): Apply configuration sequentially #391

Merged
merged 3 commits into from
Jan 30, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading