From 66b32f66eee9bb711741829d8fa6e86622e29510 Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Sat, 19 Jul 2025 17:18:02 +0800 Subject: [PATCH 1/5] chore(connect): relocate connect module to http --- src/client/http/aliases.rs | 20 +++- src/{ => client/http}/connect.rs | 167 ++++++++++++++----------------- src/client/http/mod.rs | 38 ++++--- src/client/http/service.rs | 61 ++++++++--- src/lib.rs | 1 - 5 files changed, 155 insertions(+), 132 deletions(-) rename src/{ => client/http}/connect.rs (89%) diff --git a/src/client/http/aliases.rs b/src/client/http/aliases.rs index 9830c7f85..9f36bb65b 100644 --- a/src/client/http/aliases.rs +++ b/src/client/http/aliases.rs @@ -4,14 +4,19 @@ use tower::{ util::{BoxCloneSyncService, BoxCloneSyncServiceLayer, MapErr}, }; -use super::{Body, service::ClientService}; +use super::{ + Body, + connect::{Conn, Unnameable}, + service::ClientService, +}; use crate::{ client::layer::{ redirect::FollowRedirect, retry::Http2RetryPolicy, timeout::{ResponseBodyTimeout, Timeout, TimeoutBody}, }, - core::body::Incoming, + core::{body::Incoming, client::connect}, + dns::DynResolver, error::BoxError, redirect::FollowRedirectPolicy, }; @@ -54,13 +59,13 @@ pub type ResponseBody = TimeoutBody; -pub type RedirectLayer = FollowRedirect< +pub type FollowRedirectLayer = FollowRedirect< CookieLayer>>, FollowRedirectPolicy, >; pub type GenericClientService = - MapErr>, fn(BoxError) -> BoxError>; + MapErr>, fn(BoxError) -> BoxError>; pub type BoxedClientService = BoxCloneSyncService, HttpResponse, BoxError>; @@ -71,3 +76,10 @@ pub type BoxedClientLayer = BoxCloneSyncServiceLayer< HttpResponse, BoxError, >; + +pub type HttpConnector = connect::HttpConnector; + +pub type BoxedConnectorService = BoxCloneSyncService; + +pub type BoxedConnectorLayer = + BoxCloneSyncServiceLayer; diff --git a/src/connect.rs b/src/client/http/connect.rs similarity index 89% rename from src/connect.rs rename to src/client/http/connect.rs index 263e2a835..277574c8c 100644 --- a/src/connect.rs +++ b/src/client/http/connect.rs @@ -15,15 +15,16 @@ use tokio_boring2::SslStream; use tower::{ Service, ServiceBuilder, timeout::TimeoutLayer, - util::{BoxCloneSyncService, BoxCloneSyncServiceLayer, MapRequestLayer}, + util::{BoxCloneSyncService, MapRequestLayer}, }; -pub(crate) use self::conn::{Conn, Unnameable}; +pub(super) use self::conn::{Conn, Unnameable}; +use super::aliases::{BoxedConnectorLayer, BoxedConnectorService, HttpConnector}; use crate::{ core::{ client::{ ConnExtra, ConnRequest, - connect::{self, Connected, Connection, proxy}, + connect::{Connected, Connection, proxy}, }, rt::{Read, ReadBufCursor, TokioIo, Write}, }, @@ -36,38 +37,51 @@ use crate::{ }, }; -type BoxConn = Box; - type Connecting = Pin> + Send>>; -pub(crate) type HttpConnector = connect::HttpConnector; - -pub(crate) type BoxedConnectorService = BoxCloneSyncService; - -pub(crate) type BoxedConnectorLayer = - BoxCloneSyncServiceLayer; - -pub(crate) struct ConnectorBuilder { - http: HttpConnector, +#[derive(Clone)] +struct Config { proxies: Arc>, verbose: verbose::Wrapper, + tcp_nodelay: bool, + tls_info: bool, /// When there is a single timeout layer and no other layers, /// we embed it directly inside our base Service::call(). /// This lets us avoid an extra `Box::pin` indirection layer /// since `tokio::time::Timeout` is `Unpin` timeout: Option, - tcp_nodelay: bool, +} + +pub struct ConnectorBuilder { + config: Config, #[cfg(feature = "socks")] resolver: DynResolver, - - tls_info: bool, + http: HttpConnector, tls_builder: TlsConnectorBuilder, } +#[derive(Clone)] +pub enum Connector { + Simple(ConnectorService), + WithLayers(BoxedConnectorService), +} + +#[derive(Clone)] +pub struct ConnectorService { + config: Config, + #[cfg(feature = "socks")] + resolver: DynResolver, + http: HttpConnector, + tls: TlsConnector, + tls_builder: Arc, +} + +// ===== impl ConnectorBuilder ===== + impl ConnectorBuilder { /// Set the HTTP connector to use. #[inline] - pub(crate) fn with_http(mut self, call: F) -> ConnectorBuilder + pub fn with_http(mut self, call: F) -> ConnectorBuilder where F: FnOnce(&mut HttpConnector), { @@ -77,7 +91,7 @@ impl ConnectorBuilder { /// Set the TLS connector builder to use. #[inline] - pub(crate) fn with_tls(mut self, call: F) -> ConnectorBuilder + pub fn with_tls(mut self, call: F) -> ConnectorBuilder where F: FnOnce(TlsConnectorBuilder) -> TlsConnectorBuilder, { @@ -90,43 +104,42 @@ impl ConnectorBuilder { /// If a domain resolves to multiple IP addresses, the timeout will be /// evenly divided across them. #[inline] - pub(crate) fn timeout(mut self, timeout: Option) -> ConnectorBuilder { - self.timeout = timeout; + pub fn timeout(mut self, timeout: Option) -> ConnectorBuilder { + self.config.timeout = timeout; self } /// Set connecting verbose mode. #[inline] - pub(crate) fn verbose(mut self, enabled: bool) -> ConnectorBuilder { - self.verbose.0 = enabled; + pub fn verbose(mut self, enabled: bool) -> ConnectorBuilder { + self.config.verbose.0 = enabled; self } /// Sets the TLS info flag. #[inline] - pub(crate) fn tls_info(mut self, enabled: bool) -> ConnectorBuilder { - self.tls_info = enabled; + pub fn tls_info(mut self, enabled: bool) -> ConnectorBuilder { + self.config.tls_info = enabled; self } /// Builds the connector with the provided TLS options configuration and optional layers. - pub(crate) fn build( + pub fn build( self, opts: TlsOptions, layers: Option>, ) -> crate::Result { let mut service = ConnectorService { + config: Config { + // The timeout is initially set to None and will be reassigned later + // based on the presence or absence of user-provided layers. + timeout: None, + ..self.config + }, + #[cfg(feature = "socks")] + resolver: self.resolver.clone(), http: self.http, tls: self.tls_builder.build(opts)?, - proxies: self.proxies, - verbose: self.verbose, - // The timeout is initially set to None and will be reassigned later - // based on the presence or absence of user-provided layers. - timeout: None, - tcp_nodelay: self.tcp_nodelay, - #[cfg(feature = "socks")] - resolver: self.resolver, - tls_info: self.tls_info, tls_builder: Arc::new(self.tls_builder), }; @@ -147,7 +160,7 @@ impl ConnectorBuilder { // now we handle the concrete stuff - any `connect_timeout`, // plus a final map_err layer we can use to cast default tower layer // errors to internal errors - match self.timeout { + match self.config.timeout { Some(timeout) => { let service = ServiceBuilder::new() .layer(TimeoutLayer::new(timeout)) @@ -171,20 +184,13 @@ impl ConnectorBuilder { } } else { // we have no user-provided layers, only use concrete types - service.timeout = self.timeout; + service.config.timeout = self.config.timeout; Ok(Connector::Simple(service)) } } } -#[derive(Clone)] -pub(crate) enum Connector { - // base service, with or without an embedded timeout - Simple(ConnectorService), - // at least one custom layer along with maybe an outer timeout layer - // from `builder.connect_timeout()` - WithLayers(BoxedConnectorService), -} +// ===== impl Connector ===== impl Connector { pub(crate) fn builder( @@ -192,18 +198,16 @@ impl Connector { resolver: DynResolver, ) -> ConnectorBuilder { ConnectorBuilder { + config: Config { + proxies, + verbose: verbose::OFF, + tcp_nodelay: false, + tls_info: false, + timeout: None, + }, #[cfg(feature = "socks")] resolver: resolver.clone(), - http: { - let mut http = HttpConnector::new_with_resolver(resolver); - http.enforce_http(false); - http - }, - proxies, - verbose: verbose::OFF, - timeout: None, - tcp_nodelay: false, - tls_info: false, + http: HttpConnector::new_with_resolver(resolver), tls_builder: TlsConnector::builder(), } } @@ -214,7 +218,7 @@ impl Service for Connector { type Error = BoxError; type Future = Connecting; - #[inline(always)] + #[inline] fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { match self { Connector::Simple(service) => service.poll_ready(cx), @@ -222,7 +226,7 @@ impl Service for Connector { } } - #[inline(always)] + #[inline] fn call(&mut self, req: ConnRequest) -> Self::Future { match self { Connector::Simple(service) => service.call(req), @@ -231,27 +235,7 @@ impl Service for Connector { } } -#[derive(Clone)] -pub(crate) struct ConnectorService { - http: HttpConnector, - tls: TlsConnector, - proxies: Arc>, - verbose: verbose::Wrapper, - /// When there is a single timeout layer and no other layers, - /// we embed it directly inside our base Service::call(). - /// This lets us avoid an extra `Box::pin` indirection layer - /// since `tokio::time::Timeout` is `Unpin` - timeout: Option, - tcp_nodelay: bool, - #[cfg(feature = "socks")] - resolver: DynResolver, - - // TLS options configuration - // Note: these are not used in the `TlsConnectorBuilder` but rather - // in the `TlsConnector` that is built from it. - tls_info: bool, - tls_builder: Arc, -} +// ===== impl ConnectorService ===== impl ConnectorService { /// Constructs an HTTPS connector by wrapping an `HttpConnector` @@ -279,7 +263,7 @@ impl ConnectorService { // Disable Nagle's algorithm for TLS handshake // // https://www.openssl.org/docs/man1.1.1/man3/SSL_connect.html#NOTES - if !self.tcp_nodelay && (uri.scheme() == Some(&Scheme::HTTPS)) { + if !self.config.tcp_nodelay && (uri.scheme() == Some(&Scheme::HTTPS)) { http.set_nodelay(true); } @@ -289,20 +273,20 @@ impl ConnectorService { // If the connection is HTTPS, wrap the TLS stream in a TlsConn for unified handling. // For plain HTTP, use the stream directly without additional wrapping. let inner = if let MaybeHttpsStream::Https(stream) = io { - if !self.tcp_nodelay { + if !self.config.tcp_nodelay { stream.get_ref().set_nodelay(false)?; } - self.verbose.wrap(TlsConn { + self.config.verbose.wrap(TlsConn { inner: TokioIo::new(stream), }) } else { - self.verbose.wrap(io) + self.config.verbose.wrap(io) }; Ok(Conn { inner, is_proxy, - tls_info: self.tls_info, + tls_info: self.config.tls_info, }) } @@ -352,15 +336,15 @@ impl ConnectorService { let io = connector.call(established_conn).await?; Ok(Conn { - inner: self.verbose.wrap(TlsConn { + inner: self.config.verbose.wrap(TlsConn { inner: TokioIo::new(io), }), is_proxy: false, - tls_info: self.tls_info, + tls_info: self.config.tls_info, }) } else { Ok(Conn { - inner: self.verbose.wrap(conn), + inner: self.config.verbose.wrap(conn), is_proxy: false, tls_info: false, }) @@ -397,11 +381,11 @@ impl ConnectorService { let io = connector.call(established_conn).await?; return Ok(Conn { - inner: self.verbose.wrap(TlsConn { + inner: self.config.verbose.wrap(TlsConn { inner: TokioIo::new(io), }), is_proxy: false, - tls_info: self.tls_info, + tls_info: self.config.tls_info, }); } @@ -420,12 +404,13 @@ impl ConnectorService { .proxy_matcher() .and_then(|scheme| scheme.intercept(req.uri())) .or_else(|| { - self.proxies + self.config + .proxies .iter() .find_map(|prox| prox.intercept(req.uri())) }); - let timeout = self.timeout; + let timeout = self.config.timeout; let fut = async { if let Some(intercepted) = intercepted { self.connect_with_proxy(req, intercepted).await @@ -518,6 +503,8 @@ trait AsyncConnWithInfo: AsyncConn + TlsInfoFactory {} impl AsyncConnWithInfo for T {} +type BoxConn = Box; + mod conn { use super::*; diff --git a/src/client/http/mod.rs b/src/client/http/mod.rs index 1a5674454..ccdba189a 100644 --- a/src/client/http/mod.rs +++ b/src/client/http/mod.rs @@ -1,4 +1,5 @@ mod aliases; +mod connect; mod future; mod service; @@ -12,13 +13,16 @@ use std::{ time::Duration, }; -use aliases::{BoxedClientLayer, BoxedClientService, GenericClientService, ResponseBody}; +use aliases::{ + BoxedClientLayer, BoxedClientService, BoxedConnectorLayer, BoxedConnectorService, + GenericClientService, ResponseBody, +}; pub use future::Pending; use http::{ Request as HttpRequest, Response as HttpResponse, header::{HeaderMap, HeaderValue, USER_AGENT}, }; -use service::{ClientConfig, ClientService}; +use service::ClientService; use tower::{ Layer, Service, ServiceBuilder, ServiceExt, retry::RetryLayer, @@ -50,12 +54,12 @@ use super::{ use crate::dns::hickory::{HickoryDnsResolver, LookupIpStrategy}; use crate::{ IntoUrl, Method, OriginalHeaders, Proxy, - connect::{ - BoxedConnectorLayer, BoxedConnectorService, Conn, Connector, HttpConnector, Unnameable, + client::http::{ + aliases::HttpConnector, + connect::{Conn, Connector, Unnameable}, }, core::{ client::{HttpClient, connect::TcpConnectOptions, options::TransportOptions}, - ext::RequestConfig, rt::{TokioExecutor, tokio::TokioTimer}, }, dns::{DnsResolverWithOverrides, DynResolver, Resolve, gai::GaiResolver}, @@ -245,10 +249,6 @@ impl ClientBuilder { proxies.push(ProxyMatcher::system()); } let proxies = Arc::new(proxies); - let proxies_maybe_http_auth = proxies.iter().any(ProxyMatcher::maybe_has_http_auth); - let proxies_maybe_http_custom_headers = proxies - .iter() - .any(ProxyMatcher::maybe_has_http_custom_headers); let (tls_opts, http1_opts, http2_opts) = config.transport_options.into_parts(); @@ -275,6 +275,7 @@ impl ClientBuilder { // Apply http connector options let http = |http: &mut HttpConnector| { + http.enforce_http(false); http.set_keepalive(config.tcp_keepalive); http.set_keepalive_interval(config.tcp_keepalive_interval); http.set_keepalive_retries(config.tcp_keepalive_retries); @@ -308,8 +309,8 @@ impl ClientBuilder { .timeout(config.connect_timeout) .tls_info(config.tls_info) .verbose(config.connection_verbose) - .with_http(http) .with_tls(tls) + .with_http(http) .build(tls_opts.unwrap_or_default(), config.connector_layers)? }; @@ -331,18 +332,13 @@ impl ClientBuilder { // Create the client with the configured service layers let client = { - let service = ClientService { + let service = ClientService::new( client, - config: Arc::new(ClientConfig { - default_headers: config.headers, - original_headers: RequestConfig::new(config.original_headers), - skip_default_headers: RequestConfig::default(), - https_only: config.https_only, - proxies, - proxies_maybe_http_auth, - proxies_maybe_http_custom_headers, - }), - }; + config.headers, + config.original_headers, + config.https_only, + proxies, + ); #[cfg(any( feature = "gzip", diff --git a/src/client/http/service.rs b/src/client/http/service.rs index 5a4c55a2a..354f75e48 100644 --- a/src/client/http/service.rs +++ b/src/client/http/service.rs @@ -6,10 +6,10 @@ use std::{ use http::{HeaderMap, Request, Response, header::PROXY_AUTHORIZATION, uri::Scheme}; use tower::Service; -use super::{Body, future::CorePending}; +use super::{Body, connect::Connector, future::CorePending}; use crate::{ + OriginalHeaders, client::layer::config::RequestSkipDefaultHeaders, - connect::Connector, core::{ body::Incoming, client::HttpClient, @@ -20,23 +20,52 @@ use crate::{ proxy::Matcher as ProxyMatcher, }; -#[derive(Clone)] -pub struct ClientService { - pub(super) client: HttpClient, - pub(super) config: Arc, +/// HTTP client service configuration. +struct Config { + headers: HeaderMap, + skip_default_headers: RequestConfig, + original_headers: RequestConfig, + https_only: bool, + proxies: Arc>, + proxies_maybe_http_auth: bool, + proxies_maybe_http_custom_headers: bool, } -pub(super) struct ClientConfig { - pub(super) default_headers: HeaderMap, - pub(super) skip_default_headers: RequestConfig, - pub(super) original_headers: RequestConfig, - pub(super) https_only: bool, - pub(super) proxies: Arc>, - pub(super) proxies_maybe_http_auth: bool, - pub(super) proxies_maybe_http_custom_headers: bool, +/// Tower service wrapper around the HTTP client. +#[derive(Clone)] +pub struct ClientService { + client: HttpClient, + config: Arc, } impl ClientService { + /// Creates a new `ClientService` with the provided HTTP client and configuration. + pub(super) fn new( + client: HttpClient, + headers: HeaderMap, + original_headers: Option, + https_only: bool, + proxies: Arc>, + ) -> Self { + let proxies_maybe_http_auth = proxies.iter().any(ProxyMatcher::maybe_has_http_auth); + let proxies_maybe_http_custom_headers = proxies + .iter() + .any(ProxyMatcher::maybe_has_http_custom_headers); + + ClientService { + client, + config: Arc::new(Config { + headers, + original_headers: RequestConfig::new(original_headers), + skip_default_headers: RequestConfig::default(), + https_only, + proxies, + proxies_maybe_http_auth, + proxies_maybe_http_custom_headers, + }), + } + } + #[inline] fn apply_proxy_headers(&self, req: &mut Request) { // Skip if the destination is not plain HTTP. @@ -121,9 +150,9 @@ impl Service> for ClientService { if !skip { let headers = req.headers_mut(); // Insert default headers if they are not already present in the request. - for name in self.config.default_headers.keys() { + for name in self.config.headers.keys() { if !headers.contains_key(name) { - for value in self.config.default_headers.get_all(name) { + for value in self.config.headers.get_all(name) { headers.append(name, value.clone()); } } diff --git a/src/lib.rs b/src/lib.rs index fcd65e697..289b12328 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -277,7 +277,6 @@ #[macro_use] mod trace; mod client; -mod connect; mod core; mod error; mod into_url; From f114762ade3531e25042284c1512a9f99a2309d5 Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Sat, 19 Jul 2025 17:22:39 +0800 Subject: [PATCH 2/5] fmt --- src/client/http/connect.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/client/http/connect.rs b/src/client/http/connect.rs index 277574c8c..c12c99b50 100644 --- a/src/client/http/connect.rs +++ b/src/client/http/connect.rs @@ -39,6 +39,7 @@ use crate::{ type Connecting = Pin> + Send>>; +/// Configuration for the connector service. #[derive(Clone)] struct Config { proxies: Arc>, @@ -52,6 +53,7 @@ struct Config { timeout: Option, } +/// Builder for `Connector`. pub struct ConnectorBuilder { config: Config, #[cfg(feature = "socks")] @@ -60,12 +62,14 @@ pub struct ConnectorBuilder { tls_builder: TlsConnectorBuilder, } +/// Connector service that establishes connections. #[derive(Clone)] pub enum Connector { Simple(ConnectorService), WithLayers(BoxedConnectorService), } +/// Service that establishes connections to HTTP servers. #[derive(Clone)] pub struct ConnectorService { config: Config, From 10055f9588a922b7c4b9f2c96d4429d26e1bc162 Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Sat, 19 Jul 2025 17:42:17 +0800 Subject: [PATCH 3/5] fmt --- src/client/http/connect.rs | 86 +++++++++++++++++++------------------- src/client/http/mod.rs | 66 +++++++++++++---------------- src/tls/x509/store.rs | 13 +++--- 3 files changed, 79 insertions(+), 86 deletions(-) diff --git a/src/client/http/connect.rs b/src/client/http/connect.rs index c12c99b50..cb4e3e7f5 100644 --- a/src/client/http/connect.rs +++ b/src/client/http/connect.rs @@ -131,7 +131,7 @@ impl ConnectorBuilder { pub fn build( self, opts: TlsOptions, - layers: Option>, + layers: Vec, ) -> crate::Result { let mut service = ConnectorService { config: Config { @@ -147,49 +147,49 @@ impl ConnectorBuilder { tls_builder: Arc::new(self.tls_builder), }; - if let Some(layers) = layers { - // otherwise we have user provided layers - // so we need type erasure all the way through - // as well as mapping the unnameable type of the layers back to ConnectRequest for the - // inner service - let service = layers.into_iter().fold( - BoxCloneSyncService::new( - ServiceBuilder::new() - .layer(MapRequestLayer::new(|request: Unnameable| request.0)) - .service(service), - ), - |service, layer| ServiceBuilder::new().layer(layer).service(service), - ); - - // now we handle the concrete stuff - any `connect_timeout`, - // plus a final map_err layer we can use to cast default tower layer - // errors to internal errors - match self.config.timeout { - Some(timeout) => { - let service = ServiceBuilder::new() - .layer(TimeoutLayer::new(timeout)) - .service(service); - let service = ServiceBuilder::new() - .map_err(map_timeout_to_connector_error) - .service(service); - let service = BoxCloneSyncService::new(service); - Ok(Connector::WithLayers(service)) - } - None => { - // no timeout, but still map err - // no named timeout layer but we still map errors since - // we might have user-provided timeout layer - let service = ServiceBuilder::new() - .map_err(map_timeout_to_connector_error) - .service(service); - let service = BoxCloneSyncService::new(service); - Ok(Connector::WithLayers(service)) - } - } - } else { - // we have no user-provided layers, only use concrete types + // we have no user-provided layers, only use concrete types + if layers.is_empty() { service.config.timeout = self.config.timeout; - Ok(Connector::Simple(service)) + return Ok(Connector::Simple(service)); + } + + // otherwise we have user provided layers + // so we need type erasure all the way through + // as well as mapping the unnameable type of the layers back to ConnectRequest for the + // inner service + let service = layers.into_iter().fold( + BoxCloneSyncService::new( + ServiceBuilder::new() + .layer(MapRequestLayer::new(|request: Unnameable| request.0)) + .service(service), + ), + |service, layer| ServiceBuilder::new().layer(layer).service(service), + ); + + // now we handle the concrete stuff - any `connect_timeout`, + // plus a final map_err layer we can use to cast default tower layer + // errors to internal errors + match self.config.timeout { + Some(timeout) => { + let service = ServiceBuilder::new() + .layer(TimeoutLayer::new(timeout)) + .service(service); + let service = ServiceBuilder::new() + .map_err(map_timeout_to_connector_error) + .service(service); + let service = BoxCloneSyncService::new(service); + Ok(Connector::WithLayers(service)) + } + None => { + // no timeout, but still map err + // no named timeout layer but we still map errors since + // we might have user-provided timeout layer + let service = ServiceBuilder::new() + .map_err(map_timeout_to_connector_error) + .service(service); + let service = BoxCloneSyncService::new(service); + Ok(Connector::WithLayers(service)) + } } } } diff --git a/src/client/http/mod.rs b/src/client/http/mod.rs index ccdba189a..c80617f6f 100644 --- a/src/client/http/mod.rs +++ b/src/client/http/mod.rs @@ -147,8 +147,8 @@ struct Config { http_version_pref: HttpVersionPref, https_only: bool, http2_max_retry: usize, - layers: Option>, - connector_layers: Option>, + layers: Vec, + connector_layers: Vec, keylog_policy: Option, tls_info: bool, tls_sni: bool, @@ -214,8 +214,8 @@ impl ClientBuilder { http_version_pref: HttpVersionPref::All, https_only: false, http2_max_retry: 2, - layers: None, - connector_layers: None, + layers: Vec::new(), + connector_layers: Vec::new(), keylog_policy: None, tls_info: false, tls_sni: true, @@ -378,36 +378,33 @@ impl ClientBuilder { ))) .service(service); - match config.layers { - Some(layers) => { - let service = layers.into_iter().fold( - BoxCloneSyncService::new(service), - |client_service, layer| { - ServiceBuilder::new().layer(layer).service(client_service) - }, - ); - - let service = ServiceBuilder::new() - .layer(TimeoutLayer::new(config.timeout, config.read_timeout)) - .service(service); - - let service = ServiceBuilder::new() - .map_err(error::map_timeout_to_request_error) - .service(service); + if config.layers.is_empty() { + let service = ServiceBuilder::new() + .layer(TimeoutLayer::new(config.timeout, config.read_timeout)) + .service(service); + + let service = ServiceBuilder::new() + .map_err(error::map_timeout_to_request_error as _) + .service(service); + + ClientRef::Generic(service) + } else { + let service = config.layers.into_iter().fold( + BoxCloneSyncService::new(service), + |client_service, layer| { + ServiceBuilder::new().layer(layer).service(client_service) + }, + ); - ClientRef::Boxed(BoxCloneSyncService::new(service)) - } - None => { - let service = ServiceBuilder::new() - .layer(TimeoutLayer::new(config.timeout, config.read_timeout)) - .service(service); + let service = ServiceBuilder::new() + .layer(TimeoutLayer::new(config.timeout, config.read_timeout)) + .service(service); - let service = ServiceBuilder::new() - .map_err(error::map_timeout_to_request_error as _) - .service(service); + let service = ServiceBuilder::new() + .map_err(error::map_timeout_to_request_error) + .service(service); - ClientRef::Generic(service) - } + ClientRef::Boxed(BoxCloneSyncService::new(service)) } }; @@ -1239,7 +1236,7 @@ impl ClientBuilder { >>::Future: Send + 'static, { let layer = BoxCloneSyncServiceLayer::new(layer); - self.config.layers.get_or_insert_default().push(layer); + self.config.layers.push(layer); self } @@ -1273,10 +1270,7 @@ impl ClientBuilder { >::Future: Send + 'static, { let layer = BoxCloneSyncServiceLayer::new(layer); - self.config - .connector_layers - .get_or_insert_default() - .push(layer); + self.config.connector_layers.push(layer); self } diff --git a/src/tls/x509/store.rs b/src/tls/x509/store.rs index 483dde3f8..006941cdd 100644 --- a/src/tls/x509/store.rs +++ b/src/tls/x509/store.rs @@ -195,13 +195,12 @@ impl CertStore { impl Default for CertStore { fn default() -> Self { #[cfg(feature = "webpki-roots")] - pub(super) static LOAD_CERTS: std::sync::LazyLock = - std::sync::LazyLock::new(|| { - CertStore::builder() - .add_der_certs(webpki_root_certs::TLS_SERVER_ROOT_CERTS) - .build() - .expect("failed to load default cert store") - }); + static LOAD_CERTS: std::sync::LazyLock = std::sync::LazyLock::new(|| { + CertStore::builder() + .add_der_certs(webpki_root_certs::TLS_SERVER_ROOT_CERTS) + .build() + .expect("failed to load default cert store") + }); #[cfg(not(feature = "webpki-roots"))] { From 33619a6f8b26ca261c631cf79087106a07ae5fb1 Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Sat, 19 Jul 2025 17:48:26 +0800 Subject: [PATCH 4/5] fmt --- src/client/http/mod.rs | 28 +++++++++++++--------------- src/client/layer/timeout/layer.rs | 16 +++++----------- src/client/layer/timeout/mod.rs | 2 +- 3 files changed, 19 insertions(+), 27 deletions(-) diff --git a/src/client/http/mod.rs b/src/client/http/mod.rs index c80617f6f..c8b332d9a 100644 --- a/src/client/http/mod.rs +++ b/src/client/http/mod.rs @@ -54,9 +54,12 @@ use super::{ use crate::dns::hickory::{HickoryDnsResolver, LookupIpStrategy}; use crate::{ IntoUrl, Method, OriginalHeaders, Proxy, - client::http::{ - aliases::HttpConnector, - connect::{Conn, Connector, Unnameable}, + client::{ + http::{ + aliases::HttpConnector, + connect::{Conn, Connector, Unnameable}, + }, + layer::timeout::TimeoutOptions, }, core::{ client::{HttpClient, connect::TcpConnectOptions, options::TransportOptions}, @@ -136,8 +139,7 @@ struct Config { auto_sys_proxy: bool, redirect_policy: RedirectPolicy, referer: bool, - timeout: Option, - read_timeout: Option, + timeout_options: TimeoutOptions, #[cfg(feature = "cookies")] cookie_store: Option>, #[cfg(feature = "hickory-dns")] @@ -203,8 +205,7 @@ impl ClientBuilder { auto_sys_proxy: true, redirect_policy: RedirectPolicy::none(), referer: true, - timeout: None, - read_timeout: None, + timeout_options: TimeoutOptions::default(), #[cfg(feature = "hickory-dns")] hickory_dns: cfg!(feature = "hickory-dns"), #[cfg(feature = "cookies")] @@ -351,10 +352,7 @@ impl ClientBuilder { .service(service); let service = ServiceBuilder::new() - .layer(ResponseBodyTimeoutLayer::new( - config.timeout, - config.read_timeout, - )) + .layer(ResponseBodyTimeoutLayer::new(config.timeout_options)) .service(service); #[cfg(feature = "cookies")] @@ -380,7 +378,7 @@ impl ClientBuilder { if config.layers.is_empty() { let service = ServiceBuilder::new() - .layer(TimeoutLayer::new(config.timeout, config.read_timeout)) + .layer(TimeoutLayer::new(config.timeout_options)) .service(service); let service = ServiceBuilder::new() @@ -397,7 +395,7 @@ impl ClientBuilder { ); let service = ServiceBuilder::new() - .layer(TimeoutLayer::new(config.timeout, config.read_timeout)) + .layer(TimeoutLayer::new(config.timeout_options)) .service(service); let service = ServiceBuilder::new() @@ -769,7 +767,7 @@ impl ClientBuilder { /// Default is no timeout. #[inline] pub fn timeout(mut self, timeout: Duration) -> ClientBuilder { - self.config.timeout = Some(timeout); + self.config.timeout_options.total_timeout(timeout); self } @@ -778,7 +776,7 @@ impl ClientBuilder { /// Default is `None`. #[inline] pub fn read_timeout(mut self, timeout: Duration) -> ClientBuilder { - self.config.read_timeout = Some(timeout); + self.config.timeout_options.read_timeout(timeout); self } diff --git a/src/client/layer/timeout/layer.rs b/src/client/layer/timeout/layer.rs index 46dc26456..3c5396100 100644 --- a/src/client/layer/timeout/layer.rs +++ b/src/client/layer/timeout/layer.rs @@ -24,13 +24,10 @@ pub struct TimeoutLayer { } impl TimeoutLayer { - /// Create a timeout from a duration - pub const fn new(total: Option, read: Option) -> Self { + /// Create a new [`TimeoutLayer`]. + pub const fn new(options: TimeoutOptions) -> Self { TimeoutLayer { - timeout: RequestConfig::new(Some(TimeoutOptions { - total_timeout: total, - read_timeout: read, - })), + timeout: RequestConfig::new(Some(options)), } } } @@ -92,12 +89,9 @@ pub struct ResponseBodyTimeoutLayer { impl ResponseBodyTimeoutLayer { /// Creates a new [`ResponseBodyTimeoutLayer`]. - pub const fn new(total: Option, read: Option) -> Self { + pub const fn new(options: TimeoutOptions) -> Self { Self { - timeout: RequestConfig::new(Some(TimeoutOptions { - total_timeout: total, - read_timeout: read, - })), + timeout: RequestConfig::new(Some(options)), } } } diff --git a/src/client/layer/timeout/mod.rs b/src/client/layer/timeout/mod.rs index 812c26a44..71d58edfe 100644 --- a/src/client/layer/timeout/mod.rs +++ b/src/client/layer/timeout/mod.rs @@ -11,7 +11,7 @@ pub use self::{ layer::{ResponseBodyTimeout, ResponseBodyTimeoutLayer, Timeout, TimeoutLayer}, }; -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Copy, Default)] pub struct TimeoutOptions { total_timeout: Option, read_timeout: Option, From 867586bcaf2c4fda02396e9c3c3ad5301faa0862 Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Sat, 19 Jul 2025 17:48:57 +0800 Subject: [PATCH 5/5] fmt --- src/client/layer/timeout/layer.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/layer/timeout/layer.rs b/src/client/layer/timeout/layer.rs index 3c5396100..880799f4b 100644 --- a/src/client/layer/timeout/layer.rs +++ b/src/client/layer/timeout/layer.rs @@ -38,7 +38,7 @@ impl Layer for TimeoutLayer { fn layer(&self, service: S) -> Self::Service { Timeout { inner: service, - timeout: self.timeout.clone(), + timeout: self.timeout, } } } @@ -102,7 +102,7 @@ impl Layer for ResponseBodyTimeoutLayer { fn layer(&self, inner: S) -> Self::Service { ResponseBodyTimeout { inner, - timeout: self.timeout.clone(), + timeout: self.timeout, } } }