From 25bae853fa45925f7bf39ea8eb214691f3fc3446 Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Fri, 11 Jul 2025 17:56:21 +0800 Subject: [PATCH 01/19] init --- examples/emulation_firefox.rs | 22 +-- examples/emulation_twitter.rs | 127 +++--------------- examples/http1_recv_case_sensitive_headers.rs | 6 +- examples/request_with_emulation.rs | 16 +-- src/client/client/macros.rs | 6 +- src/client/client/mod.rs | 69 +++++----- src/client/emulation.rs | 99 ++++++-------- src/client/request.rs | 23 ++-- src/connect.rs | 11 +- src/core/client/config/http1.rs | 31 +++-- src/core/client/config/http2.rs | 24 ++-- src/core/client/config/mod.rs | 53 +++++--- src/core/client/conn/http1.rs | 6 +- src/core/client/conn/http2.rs | 6 +- src/core/client/mod.rs | 45 ++++--- src/core/ext/config.rs | 6 +- src/core/ext/mod.rs | 2 +- src/tls/config.rs | 28 ++-- src/tls/conn/mod.rs | 4 +- src/tls/mod.rs | 2 +- tests/badssl.rs | 18 +-- 21 files changed, 259 insertions(+), 345 deletions(-) diff --git a/examples/emulation_firefox.rs b/examples/emulation_firefox.rs index ccdd57596..bf8620482 100644 --- a/examples/emulation_firefox.rs +++ b/examples/emulation_firefox.rs @@ -1,12 +1,12 @@ use http::{HeaderMap, HeaderValue, header}; use wreq::{ Client, EmulationProvider, OriginalHeaders, - http1::Http1Config, + http1::Http1Options, http2::{ - Http2Config, Priorities, Priority, PseudoId, PseudoOrder, SettingId, SettingsOrder, + Http2Options, Priorities, Priority, PseudoId, PseudoOrder, SettingId, SettingsOrder, StreamDependency, StreamId, }, - tls::{AlpnProtocol, CertificateCompressionAlgorithm, ExtensionType, TlsConfig, TlsVersion}, + tls::{AlpnProtocol, CertificateCompressionAlgorithm, ExtensionType, TlsOptions, TlsVersion}, }; macro_rules! join { @@ -22,7 +22,7 @@ async fn main() -> wreq::Result<()> { .init(); // TLS config - let tls = TlsConfig::builder() + let tls = TlsOptions::builder() .curves_list(join!( ":", "X25519", @@ -108,7 +108,7 @@ async fn main() -> wreq::Result<()> { .build(); // HTTP/1 config - let http1 = Http1Config::builder() + let http1 = Http1Options::builder() .allow_obsolete_multiline_headers_in_responses(true) .max_headers(100) .build(); @@ -169,7 +169,7 @@ async fn main() -> wreq::Result<()> { ]) .build(); - Http2Config::builder() + Http2Options::builder() .initial_stream_id(15) .header_table_size(65536) .initial_stream_window_size(131072) @@ -213,11 +213,11 @@ async fn main() -> wreq::Result<()> { // Create emulation provider with all configurations // This provider encapsulates TLS, HTTP/1, HTTP/2, default headers, and original headers let emulation = EmulationProvider::builder() - .tls_config(tls) - .http1_config(http1) - .http2_config(http2) - .default_headers(headers) - .original_headers(original_headers) + .with_tls(tls) + .with_config(http1) + .with_http2(http2) + .with_headers(headers) + .with_original_headers(original_headers) .build(); // Build a client with emulation config diff --git a/examples/emulation_twitter.rs b/examples/emulation_twitter.rs index 84487f54b..7c3353006 100644 --- a/examples/emulation_twitter.rs +++ b/examples/emulation_twitter.rs @@ -1,120 +1,25 @@ -use http::{HeaderMap, HeaderValue, header}; -use wreq::{ - Client, EmulationProvider, OriginalHeaders, - http2::{Http2Config, PseudoId, PseudoOrder}, - tls::{AlpnProtocol, TlsConfig, TlsVersion}, -}; - -macro_rules! join { - ($sep:expr, $first:expr $(, $rest:expr)*) => { - concat!($first $(, $sep, $rest)*) - }; -} +use wreq::Client; #[tokio::main] -async fn main() -> wreq::Result<()> { - tracing_subscriber::fmt() - .with_max_level(tracing::Level::TRACE) - .init(); - - // TLS config - let tls = TlsConfig::builder() - .enable_ocsp_stapling(true) - .curves_list(join!(":", "X25519", "P-256", "P-384")) - .cipher_list(join!( - ":", - "TLS_AES_128_GCM_SHA256", - "TLS_AES_256_GCM_SHA384", - "TLS_CHACHA20_POLY1305_SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", - "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", - "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", - "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256" - )) - .sigalgs_list(join!( - ":", - "ecdsa_secp256r1_sha256", - "rsa_pss_rsae_sha256", - "rsa_pkcs1_sha256", - "ecdsa_secp384r1_sha384", - "rsa_pss_rsae_sha384", - "rsa_pkcs1_sha384", - "rsa_pss_rsae_sha512", - "rsa_pkcs1_sha512", - "rsa_pkcs1_sha1" - )) - .alpn_protos(&[AlpnProtocol::HTTP2, AlpnProtocol::HTTP1]) - .min_tls_version(TlsVersion::TLS_1_2) - .max_tls_version(TlsVersion::TLS_1_3) - .build(); - - // HTTP/2 config - let http2 = Http2Config::builder() - .initial_stream_id(3) - .initial_stream_window_size(16777216) - .initial_connection_window_size(16711681 + 65535) - .headers_pseudo_order( - PseudoOrder::builder() - .extend([ - PseudoId::Method, - PseudoId::Path, - PseudoId::Authority, - PseudoId::Scheme, - ]) - .build(), - ) - .build(); - - // Default headers - let headers = { - let mut headers = HeaderMap::new(); - headers.insert(header::USER_AGENT, HeaderValue::from_static("TwitterAndroid/10.89.0-release.0 (310890000-r-0) G011A/9 (google;G011A;google;G011A;0;;1;2016)")); - headers.insert(header::ACCEPT_LANGUAGE, HeaderValue::from_static("en-US")); - headers.insert( - header::ACCEPT_ENCODING, - HeaderValue::from_static("br, gzip, deflate"), - ); - headers.insert(header::ACCEPT, HeaderValue::from_static("application/json")); - headers.insert(header::CACHE_CONTROL, HeaderValue::from_static("no-store")); - headers.insert( - header::COOKIE, - HeaderValue::from_static("ct0=YOUR_CT0_VALUE;"), - ); - headers - }; - - // Original headers - // The headers keep the original case and order - let original_headers = { - let mut original_headers = OriginalHeaders::new(); - original_headers.insert("cookie"); - original_headers.insert("content-length"); - original_headers.insert("USER-AGENT"); - original_headers.insert("ACCEPT-LANGUAGE"); - original_headers.insert("ACCEPT-ENCODING"); - original_headers - }; - - // Create emulation provider with all configurations - // This provider encapsulates TLS, HTTP/1, HTTP/2, default headers, and original headers - let emulation = EmulationProvider::builder() - .tls_config(tls) - .http2_config(http2) - .default_headers(headers) - .original_headers(original_headers) - .build(); - - // Build a client with emulation config +async fn main() -> Result<(), wreq::Error> { + // Build a client to emulation Firefox136 let client = Client::builder() - .emulation(emulation) - .cert_verification(false) + .gzip(true) + .brotli(false) + .zstd(false) + .deflate(false) .build()?; // Use the API you're already familiar with - let resp = client.post("https://tls.peet.ws/api/all").send().await?; - println!("{}", resp.text().await?); + let respnose = client + .get("https://httpbin.org/brotli") + .header("Accept-Encoding", "gzip,br,zstd,deflate") + .send() + .await?; + + dbg!(&respnose.headers()); + + println!("{}", respnose.text().await?); Ok(()) } diff --git a/examples/http1_recv_case_sensitive_headers.rs b/examples/http1_recv_case_sensitive_headers.rs index e33fbc40a..249f3172d 100644 --- a/examples/http1_recv_case_sensitive_headers.rs +++ b/examples/http1_recv_case_sensitive_headers.rs @@ -1,10 +1,10 @@ -use wreq::{OriginalHeaders, http1::Http1Config}; +use wreq::{OriginalHeaders, http1::Http1Options}; #[tokio::main] async fn main() -> wreq::Result<()> { let client = wreq::Client::builder() - .configure_http1( - Http1Config::builder() + .http1_options( + Http1Options::builder() .preserve_header_case(true) .http09_responses(true) .build(), diff --git a/examples/request_with_emulation.rs b/examples/request_with_emulation.rs index 4558aa101..b7ec962c0 100644 --- a/examples/request_with_emulation.rs +++ b/examples/request_with_emulation.rs @@ -1,8 +1,8 @@ use http::{HeaderMap, HeaderValue, header}; use wreq::{ Client, EmulationProvider, OriginalHeaders, - http2::{Http2Config, PseudoId, PseudoOrder}, - tls::{AlpnProtocol, TlsConfig, TlsVersion}, + http2::{Http2Options, PseudoId, PseudoOrder}, + tls::{AlpnProtocol, TlsOptions, TlsVersion}, }; macro_rules! join { @@ -18,7 +18,7 @@ async fn main() -> wreq::Result<()> { .init(); // TLS config - let tls = TlsConfig::builder() + let tls = TlsOptions::builder() .enable_ocsp_stapling(true) .curves_list(join!(":", "X25519", "P-256", "P-384")) .cipher_list(join!( @@ -51,7 +51,7 @@ async fn main() -> wreq::Result<()> { .build(); // HTTP/2 config - let http2 = Http2Config::builder() + let http2 = Http2Options::builder() .initial_stream_id(3) .initial_stream_window_size(16777216) .initial_connection_window_size(16711681 + 65535) @@ -100,10 +100,10 @@ async fn main() -> wreq::Result<()> { // Create emulation provider with all configurations // This provider encapsulates TLS, HTTP/1, HTTP/2, default headers, and original headers let emulation = EmulationProvider::builder() - .tls_config(tls) - .http2_config(http2) - .default_headers(headers) - .original_headers(original_headers) + .with_tls(tls) + .with_http2(http2) + .with_headers(headers) + .with_original_headers(original_headers) .build(); // Use the API you're already familiar with diff --git a/src/client/client/macros.rs b/src/client/client/macros.rs index 2edb80736..f173623ee 100644 --- a/src/client/client/macros.rs +++ b/src/client/client/macros.rs @@ -19,10 +19,10 @@ macro_rules! take_err { } macro_rules! apply_option { - ($self:expr, $emulation:expr, $(($field:ident, $method:ident)),*) => { + ($builder:expr, $(($option:expr, $method:ident)),* $(,)?) => { $( - if let Some(value) = $emulation.$field { - $self = $self.$method(value); + if let Some(value) = $option { + $builder = $builder.$method(value); } )* }; diff --git a/src/client/client/mod.rs b/src/client/client/mod.rs index cde2f15ac..1730fafc2 100644 --- a/src/client/client/mod.rs +++ b/src/client/client/mod.rs @@ -54,18 +54,20 @@ use crate::{ IntoUrl, Method, OriginalHeaders, Proxy, connect::{BoxedConnectorLayer, BoxedConnectorService, Conn, Connector, Unnameable}, core::{ - client::{Builder, Client as NativeClient, connect::TcpConnectOptions}, + client::{ + Builder, Client as NativeClient, config::TransportOptions, connect::TcpConnectOptions, + }, ext::RequestConfig, rt::{TokioExecutor, tokio::TokioTimer}, }, dns::{DnsResolverWithOverrides, DynResolver, Resolve, gai::GaiResolver}, error::{self, BoxError, Error}, - http1::Http1Config, - http2::Http2Config, + http1::Http1Options, + http2::Http2Options, proxy::Matcher as ProxyMatcher, redirect::{self, RedirectPolicy}, tls::{ - AlpnProtocol, CertStore, CertificateInput, Identity, KeyLogPolicy, TlsConfig, TlsVersion, + AlpnProtocol, CertStore, CertificateInput, Identity, KeyLogPolicy, TlsOptions, TlsVersion, }, }; @@ -146,8 +148,8 @@ struct Config { dns_resolver: Option>, http_version_pref: HttpVersionPref, https_only: bool, - http1_config: Http1Config, - http2_config: Http2Config, + http1_options: Http1Options, + http2_options: Http2Options, http2_max_retry: usize, request_layers: Option>, connector_layers: Option>, @@ -161,7 +163,7 @@ struct Config { tls_cert_verification: bool, min_tls_version: Option, max_tls_version: Option, - tls_config: TlsConfig, + tls_options: TlsOptions, } impl Default for ClientBuilder { @@ -217,8 +219,8 @@ impl ClientBuilder { http_version_pref: HttpVersionPref::All, builder: NativeClient::builder(TokioExecutor::new()), https_only: false, - http1_config: Http1Config::default(), - http2_config: Http2Config::default(), + http1_options: Http1Options::default(), + http2_options: Http2Options::default(), http2_max_retry: 2, request_layers: None, connector_layers: None, @@ -231,7 +233,7 @@ impl ClientBuilder { tls_cert_verification: true, min_tls_version: None, max_tls_version: None, - tls_config: TlsConfig::default(), + tls_options: TlsOptions::default(), }, } } @@ -261,8 +263,8 @@ impl ClientBuilder { config .builder - .http1_config(config.http1_config) - .http2_config(config.http2_config) + .http1_options(config.http1_options) + .http2_options(config.http2_options) .http2_only(matches!(config.http_version_pref, HttpVersionPref::Http2)) .http2_timer(TokioTimer::new()) .pool_timer(TokioTimer::new()) @@ -292,10 +294,10 @@ impl ClientBuilder { match config.http_version_pref { HttpVersionPref::Http1 => { - config.tls_config.alpn_protos = Some(AlpnProtocol::HTTP1.encode()); + config.tls_options.alpn_protos = Some(AlpnProtocol::HTTP1.encode()); } HttpVersionPref::Http2 => { - config.tls_config.alpn_protos = Some(AlpnProtocol::HTTP2.encode()); + config.tls_options.alpn_protos = Some(AlpnProtocol::HTTP2.encode()); } _ => {} } @@ -322,7 +324,7 @@ impl ClientBuilder { #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] config.tcp_user_timeout, ) - .build(config.tls_config, config.connector_layers)? + .build(config.tls_options, config.connector_layers)? }; let service = { @@ -881,17 +883,17 @@ impl ClientBuilder { self } - /// Sets the HTTP/1 configuration for the client. + /// Sets the HTTP/1 options configuration for the client. #[inline] - pub fn configure_http1(mut self, config: Http1Config) -> ClientBuilder { - self.config.http1_config = config; + pub fn http1_options(mut self, opts: Http1Options) -> ClientBuilder { + self.config.http1_options = opts; self } - /// Sets the HTTP/2 configuration for the client. + /// Sets the HTTP/2 options configuration for the client. #[inline] - pub fn configure_http2(mut self, config: Http2Config) -> ClientBuilder { - self.config.http2_config = config; + pub fn http2_options(mut self, opts: Http2Options) -> ClientBuilder { + self.config.http2_options = opts; self } @@ -1171,10 +1173,10 @@ impl ClientBuilder { self } - /// Sets the TLS configuration for the client. + /// Sets the TLS options configuration for the client. #[inline] - pub fn configure_tls(mut self, config: TlsConfig) -> ClientBuilder { - self.config.tls_config = config; + pub fn tls_options(mut self, opts: TlsOptions) -> ClientBuilder { + self.config.tls_options = opts; self } @@ -1340,15 +1342,22 @@ impl ClientBuilder { where P: EmulationProviderFactory, { - let emulation = factory.emulation(); + let (transport_opts, default_headers, original_headers) = factory.emulation().into_parts(); + if let Some((tls_opts, http1_opts, http2_opts)) = + transport_opts.map(TransportOptions::into_parts) + { + apply_option!( + self, + (tls_opts, tls_options), + (http1_opts, http1_options), + (http2_opts, http2_options) + ); + } + apply_option!( self, - emulation, (default_headers, default_headers), - (original_headers, original_headers), - (http1_config, configure_http1), - (http2_config, configure_http2), - (tls_config, configure_tls) + (original_headers, original_headers) ); self } diff --git a/src/client/emulation.rs b/src/client/emulation.rs index 4919c123d..021b4fd06 100644 --- a/src/client/emulation.rs +++ b/src/client/emulation.rs @@ -1,31 +1,15 @@ use http::HeaderMap; -use crate::{OriginalHeaders, http1::Http1Config, http2::Http2Config, tls::TlsConfig}; +use crate::{ + OriginalHeaders, core::client::config::TransportOptions, http1::Http1Options, + http2::Http2Options, tls::TlsOptions, +}; /// Trait defining the interface for providing an `EmulationProvider`. /// /// The `EmulationProviderFactory` trait is designed to be implemented by types that can provide /// an `EmulationProvider` instance. This trait abstracts the creation and configuration of /// `EmulationProvider`, allowing different types to offer their own specific configurations. -/// -/// # Example -/// -/// ```rust -/// use wreq::{ -/// EmulationProvider, -/// EmulationProviderFactory, -/// }; -/// -/// struct MyEmulationProvider; -/// -/// impl EmulationProviderFactory for MyEmulationProvider { -/// fn emulation(self) -> EmulationProvider { -/// EmulationProvider::default() -/// } -/// } -/// -/// let provider = MyEmulationProvider.emulation(); -/// ``` pub trait EmulationProviderFactory { /// Provides an `EmulationProvider` instance. fn emulation(self) -> EmulationProvider; @@ -43,64 +27,52 @@ pub struct EmulationProviderBuilder { /// The `EmulationProvider` provides a complete environment for HTTP connections, /// including both HTTP-specific settings and the underlying TLS configuration. /// This unified context ensures consistent behavior across connections. -/// -/// # Components -/// -/// - **TLS Configuration**: Manages secure connection settings. -/// - **HTTP Settings**: Controls HTTP/1 and HTTP/2 behaviors. -/// - **Header Management**: Handles default headers and their ordering. -/// -/// # Example -/// -/// ```rust -/// use wreq::{ -/// EmulationProvider, -/// TlsConfig, -/// }; -/// -/// let provider = EmulationProvider::builder() -/// .tls_config(TlsConfig::default()) -/// .build(); -/// ``` #[derive(Default, Debug)] pub struct EmulationProvider { - pub(crate) tls_config: Option, - pub(crate) http1_config: Option, - pub(crate) http2_config: Option, - pub(crate) default_headers: Option, - pub(crate) original_headers: Option, + transport_options: Option, + default_headers: Option, + original_headers: Option, } impl EmulationProviderBuilder { /// Sets the TLS configuration for the `EmulationProvider`. - pub fn tls_config(mut self, config: C) -> Self + pub fn with_tls(mut self, config: C) -> Self where - C: Into>, + C: Into>, { - self.provider.tls_config = config.into(); + self.provider + .transport_options + .get_or_insert_with(TransportOptions::default) + .configure_tls(config); self } /// Sets the HTTP/1 configuration for the `EmulationProvider`. - pub fn http1_config(mut self, config: C) -> Self + pub fn with_config(mut self, config: C) -> Self where - C: Into>, + C: Into>, { - self.provider.http1_config = config.into(); + self.provider + .transport_options + .get_or_insert_with(TransportOptions::default) + .configure_http1(config); self } /// Sets the HTTP/2 configuration for the `EmulationProvider`. - pub fn http2_config(mut self, config: C) -> Self + pub fn with_http2(mut self, config: C) -> Self where - C: Into>, + C: Into>, { - self.provider.http2_config = config.into(); + self.provider + .transport_options + .get_or_insert_with(TransportOptions::default) + .configure_http2(config); self } /// Sets the default headers for the `EmulationProvider`. - pub fn default_headers(mut self, headers: H) -> Self + pub fn with_headers(mut self, headers: H) -> Self where H: Into>, { @@ -109,7 +81,7 @@ impl EmulationProviderBuilder { } /// Sets the original headers for the `EmulationProvider`. - pub fn original_headers(mut self, headers: H) -> Self + pub fn with_original_headers(mut self, headers: H) -> Self where H: Into>, { @@ -129,11 +101,28 @@ impl EmulationProvider { /// # Returns /// /// Returns a new `EmulationProviderBuilder` instance. + #[inline] pub fn builder() -> EmulationProviderBuilder { EmulationProviderBuilder { provider: EmulationProvider::default(), } } + + /// Decomposes the `EmulationProvider` into its components. + #[inline] + pub(crate) fn into_parts( + self, + ) -> ( + Option, + Option, + Option, + ) { + ( + self.transport_options, + self.default_headers, + self.original_headers, + ) + } } /// Implement `EmulationProviderFactory` for `EmulationProvider`. diff --git a/src/client/request.rs b/src/client/request.rs index cfe393ba8..64d5cf516 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -29,10 +29,10 @@ use super::{ use crate::{ EmulationProviderFactory, Error, Method, OriginalHeaders, Proxy, Url, core::{ - client::{config::TransportConfig, connect::TcpConnectOptions}, + client::{config::TransportOptions, connect::TcpConnectOptions}, ext::{ RequestConfig, RequestEnforcedHttpVersion, RequestOriginalHeaders, RequestProxyMatcher, - RequestTcpConnectOptions, RequestTransportConfig, + RequestTcpConnectOptions, RequestTransportOptions, }, }, header::{CONTENT_TYPE, HeaderMap, HeaderName, HeaderValue}, @@ -185,9 +185,10 @@ impl Request { RequestConfig::::get_mut(&mut self.extensions) } + // Get a mutable reference to the transport options. #[inline] - pub(crate) fn transport_config_mut(&mut self) -> &mut Option { - RequestConfig::::get_mut(&mut self.extensions) + pub(crate) fn transport_options_mut(&mut self) -> &mut Option { + RequestConfig::::get_mut(&mut self.extensions) } /// Get the extensions. @@ -654,18 +655,18 @@ impl RequestBuilder { P: EmulationProviderFactory, { if let Ok(ref mut req) = self.request { - let transport_config = req.transport_config_mut().get_or_insert_default(); + let opts = req.transport_options_mut().get_or_insert_default(); let emulation = factory.emulation(); + let (transport_opts, default_headers, original_headers) = emulation.into_parts(); + if let Some(transport_opts) = transport_opts { + *opts = transport_opts; + } - transport_config.set_http1_config(emulation.http1_config); - transport_config.set_http2_config(emulation.http2_config); - transport_config.set_tls_config(emulation.tls_config); - - if let Some(default_headers) = emulation.default_headers { + if let Some(default_headers) = default_headers { self = self.headers(default_headers); } - if let Some(original_headers) = emulation.original_headers { + if let Some(original_headers) = original_headers { self = self.original_headers(original_headers); } } diff --git a/src/connect.rs b/src/connect.rs index 7d02057e0..803ecb5b8 100644 --- a/src/connect.rs +++ b/src/connect.rs @@ -33,7 +33,7 @@ use crate::{ proxy::{Intercepted, Matcher as ProxyMatcher}, tls::{ CertStore, EstablishedConn, HttpsConnector, Identity, KeyLogPolicy, MaybeHttpsStream, - TlsConfig, TlsConnector, TlsConnectorBuilder, TlsInfo, TlsVersion, + TlsConnector, TlsConnectorBuilder, TlsInfo, TlsOptions, TlsVersion, }, }; @@ -222,12 +222,12 @@ impl ConnectorBuilder { /// Builds the connector with the provided TLS configuration and optional layers. pub(crate) fn build( self, - tls_config: TlsConfig, + opts: TlsOptions, layers: Option>, ) -> crate::Result { let mut service = ConnectorService { http: self.http, - tls: self.tls_builder.build(tls_config)?, + tls: self.tls_builder.build(opts)?, proxies: self.proxies, verbose: self.verbose, // The timeout is initially set to None and will be reassigned later @@ -365,15 +365,14 @@ pub(crate) struct ConnectorService { impl ConnectorService { /// Constructs an HTTPS connector by wrapping an `HttpConnector` - /// with the appropriate TLS configuration. fn build_tls_connector( &self, mut http: HttpConnector, - req: &mut ConnRequest, + req: &ConnRequest, ) -> Result, BoxError> { let ex_data = req.ex_data(); http.set_tcp_connect_options(ex_data.tcp_connect_options().cloned()); - let tls = match ex_data.tls_config() { + let tls = match ex_data.tls_options() { Some(cfg) => self.tls_builder.build(cfg.clone())?, None => self.tls.clone(), }; diff --git a/src/core/client/config/http1.rs b/src/core/client/config/http1.rs index 25ae6a08b..3238d5324 100644 --- a/src/core/client/config/http1.rs +++ b/src/core/client/config/http1.rs @@ -4,20 +4,19 @@ use httparse::ParserConfig; use crate::core::proto; -/// Builder for `Http1Config`. +/// Builder for `Http1Options`. #[must_use] #[derive(Debug)] -pub struct Http1ConfigBuilder { - config: Http1Config, +pub struct Http1OptionsBuilder { + config: Http1Options, } -/// Configuration config for HTTP/1 connections. +/// HTTP/1 protocol options for customizing connection behavior. /// -/// The `Http1Config` struct provides various configuration options for HTTP/1 connections. -/// These config allow you to customize the behavior of the HTTP/1 client, such as -/// enabling support for HTTP/0.9 responses, allowing spaces after header names, and more. +/// These options allow you to customize the behavior of HTTP/1 connections, +/// such as enabling support for HTTP/0.9 responses, header case preservation, etc. #[derive(Debug, Default, Clone)] -pub struct Http1Config { +pub struct Http1Options { pub(crate) h09_responses: bool, pub(crate) h1_parser_config: ParserConfig, pub(crate) h1_writev: Option, @@ -27,7 +26,7 @@ pub struct Http1Config { pub(crate) h1_max_buf_size: Option, } -impl Http1ConfigBuilder { +impl Http1OptionsBuilder { /// Set the `http09_responses` field. pub fn http09_responses(mut self, enabled: bool) -> Self { self.config.h09_responses = enabled; @@ -164,17 +163,17 @@ impl Http1ConfigBuilder { self } - /// Build the `Http1Config` instance. - pub fn build(self) -> Http1Config { + /// Build the `Http1Options` instance. + pub fn build(self) -> Http1Options { self.config } } -impl Http1Config { - /// Create a new `Http1ConfigBuilder`. - pub fn builder() -> Http1ConfigBuilder { - Http1ConfigBuilder { - config: Http1Config::default(), +impl Http1Options { + /// Create a new `Http1OptionsBuilder`. + pub fn builder() -> Http1OptionsBuilder { + Http1OptionsBuilder { + config: Http1Options::default(), } } } diff --git a/src/core/client/config/http2.rs b/src/core/client/config/http2.rs index 873192e8f..6f081e1fe 100644 --- a/src/core/client/config/http2.rs +++ b/src/core/client/config/http2.rs @@ -11,11 +11,11 @@ use crate::core::proto::{ {self}, }; -/// Builder for `Http2Config`. +/// Builder for `Http2Options`. #[must_use] #[derive(Debug)] -pub struct Http2ConfigBuilder { - config: Http2Config, +pub struct Http2OptionsBuilder { + config: Http2Options, } /// Configuration config for an HTTP/2 connection. @@ -23,11 +23,11 @@ pub struct Http2ConfigBuilder { /// This struct defines various parameters to fine-tune the behavior of an HTTP/2 connection, /// including stream management, window sizes, frame limits, and header config. #[derive(Debug, Clone, Default)] -pub struct Http2Config { +pub struct Http2Options { pub(crate) h2_builder: Config, } -impl Http2ConfigBuilder { +impl Http2OptionsBuilder { /// Sets the [`SETTINGS_INITIAL_WINDOW_SIZE`][spec] option for HTTP2 /// stream-level flow control. /// @@ -275,17 +275,17 @@ impl Http2ConfigBuilder { self } - /// Builds the `Http2Config` instance. - pub fn build(self) -> Http2Config { + /// Builds the `Http2Options` instance. + pub fn build(self) -> Http2Options { self.config } } -impl Http2Config { - /// Creates a new `Http2ConfigBuilder` instance. - pub fn builder() -> Http2ConfigBuilder { - Http2ConfigBuilder { - config: Http2Config::default(), +impl Http2Options { + /// Creates a new `Http2OptionsBuilder` instance. + pub fn builder() -> Http2OptionsBuilder { + Http2OptionsBuilder { + config: Http2Options::default(), } } } diff --git a/src/core/client/config/mod.rs b/src/core/client/config/mod.rs index 08dae6291..de99a838a 100644 --- a/src/core/client/config/mod.rs +++ b/src/core/client/config/mod.rs @@ -1,47 +1,58 @@ pub mod http1; pub mod http2; -use http1::Http1Config; -use http2::Http2Config; +use http1::Http1Options; +use http2::Http2Options; -use crate::tls::TlsConfig; +use crate::tls::TlsOptions; -/// TransportConfig holds configuration for HTTP/1, HTTP/2, and TLS transport layers. +/// Transport options for HTTP/1, HTTP/2, and TLS layers. /// /// This struct allows you to customize protocol-specific and TLS settings /// for network connections made by the client. #[derive(Debug, Default, Clone)] -pub(crate) struct TransportConfig { - pub(super) http1_config: Option, - pub(super) http2_config: Option, - pub(super) tls_config: Option, +pub(crate) struct TransportOptions { + tls: Option, + http1: Option, + http2: Option, } -impl TransportConfig { - /// Sets the HTTP/1 configuration. +impl TransportOptions { + /// Configures HTTP/1 settings. #[inline] - pub fn set_http1_config(&mut self, config: C) + pub fn configure_http1(&mut self, config: C) where - C: Into>, + C: Into>, { - self.http1_config = config.into(); + self.http1 = config.into(); } - /// Sets the HTTP/2 configuration. + /// Configures HTTP/2 settings. #[inline] - pub fn set_http2_config(&mut self, config: C) + pub fn configure_http2(&mut self, config: C) where - C: Into>, + C: Into>, { - self.http2_config = config.into(); + self.http2 = config.into(); } - /// Sets the TLS configuration. + /// Configures TLS settings for the transport layer. #[inline] - pub fn set_tls_config(&mut self, config: C) + pub fn configure_tls(&mut self, config: C) where - C: Into>, + C: Into>, { - self.tls_config = config.into(); + self.tls = config.into(); + } + + /// Decomposes the transport options into individual protocol configurations. + pub fn into_parts( + self, + ) -> ( + Option, + Option, + Option, + ) { + (self.tls, self.http1, self.http2) } } diff --git a/src/core/client/conn/http1.rs b/src/core/client/conn/http1.rs index 3defb51f8..0f3496146 100644 --- a/src/core/client/conn/http1.rs +++ b/src/core/client/conn/http1.rs @@ -14,7 +14,7 @@ use http_body::Body; use crate::core::{ body::Incoming as IncomingBody, client::{ - config::http1::Http1Config, + config::http1::Http1Options, dispatch::{self, TrySendError}, }, error::BoxError, @@ -88,7 +88,7 @@ where /// are subject to change at any time. #[derive(Clone, Debug)] pub struct Builder { - config: Http1Config, + config: Http1Options, } // ===== impl SendRequest @@ -231,7 +231,7 @@ impl Builder { } } - pub fn config(&mut self, config: Http1Config) { + pub fn config(&mut self, config: Http1Options) { self.config = config; } diff --git a/src/core/client/conn/http2.rs b/src/core/client/conn/http2.rs index 24db5d557..ed8a73b7c 100644 --- a/src/core/client/conn/http2.rs +++ b/src/core/client/conn/http2.rs @@ -21,7 +21,7 @@ use crate::{ proto, rt::{Read, Timer, Write, bounds::Http2ClientConnExec}, }, - http2::Http2Config, + http2::Http2Options, }; /// The sender side of an established connection. @@ -64,7 +64,7 @@ where pub struct Builder { pub(super) exec: Ex, pub(super) timer: Time, - config: Http2Config, + config: Http2Options, } // ===== impl SendRequest @@ -208,7 +208,7 @@ where } /// Provide a configuration for HTTP/2. - pub fn config(&mut self, config: Http2Config) -> &mut Builder { + pub fn config(&mut self, config: Http2Options) -> &mut Builder { self.config = config; self } diff --git a/src/core/client/mod.rs b/src/core/client/mod.rs index b1e1f4502..332d708cf 100644 --- a/src/core/client/mod.rs +++ b/src/core/client/mod.rs @@ -37,7 +37,7 @@ use crate::{ core::{ body::Incoming, client::{ - config::{TransportConfig, http1::Http1Config, http2::Http2Config}, + config::{TransportOptions, http1::Http1Options, http2::Http2Options}, conn::TrySendError as ConnTrySendError, connect::{Alpn, Connect, Connected, Connection, TcpConnectOptions}, }, @@ -46,12 +46,12 @@ use crate::{ error::BoxError, ext::{ RequestConfig, RequestEnforcedHttpVersion, RequestProxyMatcher, - RequestTcpConnectOptions, RequestTransportConfig, + RequestTcpConnectOptions, RequestTransportOptions, }, rt::{Executor, Timer}, }, proxy::Matcher as ProxyMacher, - tls::{AlpnProtocol, TlsConfig}, + tls::{AlpnProtocol, TlsOptions}, }; type BoxSendFuture = Pin + Send>>; @@ -72,7 +72,7 @@ pub struct ConnExtra { alpn_protocol: Option, proxy_matcher: Option, tcp_options: Option, - tls_config: Option, + tls_options: Option, } impl ConnExtra { @@ -96,8 +96,8 @@ impl ConnExtra { /// Return the TLS configuration. #[inline] - pub(crate) fn tls_config(&self) -> Option<&TlsConfig> { - self.tls_config.as_ref() + pub(crate) fn tls_options(&self) -> Option<&TlsOptions> { + self.tls_options.as_ref() } } @@ -332,10 +332,10 @@ where }; // Extract config extensions - let (transport_cfg, version, proxy_matcher, tcp_options) = + let (transport_options, version, proxy_matcher, tcp_options) = extract_request_configs(req.extensions_mut()); - let mut tls_config = None; + let mut tls_options = None; let mut this = self.clone(); // Parse to specific ALPN protocol @@ -347,15 +347,16 @@ where _ => None, }; - // Apply transport configuration - if let Some(mut cfg) = transport_cfg { - if let Some(config) = cfg.http1_config.take() { - this.h1_builder.config(config); + // Apply transport options configuration + if let Some(opts) = transport_options { + let (tls, http1, http2) = opts.into_parts(); + tls_options = tls; + if let Some(opts) = http1 { + this.h1_builder.config(opts); } - if let Some(config) = cfg.http2_config.take() { - this.h2_builder.config(config); + if let Some(opts) = http2 { + this.h2_builder.config(opts); } - tls_config = cfg.tls_config.take(); } let conn_req = ConnRequest { @@ -366,7 +367,7 @@ where alpn_protocol, proxy_matcher, tcp_options, - tls_config, + tls_options, }, RANDOM_STATE, )), @@ -1041,12 +1042,12 @@ fn authority_form(uri: &mut Uri) { fn extract_request_configs( extensions: &mut http::Extensions, ) -> ( - Option, + Option, Option, Option, Option, ) { - let transport_config = RequestConfig::::remove(extensions); + let transport_config = RequestConfig::::remove(extensions); let version = RequestConfig::::remove(extensions); let proxy = RequestConfig::::remove(extensions); let tcp = RequestConfig::::remove(extensions); @@ -1250,14 +1251,14 @@ impl Builder { } /// Provide a configuration for HTTP/1. - pub fn http1_config(&mut self, config: Http1Config) -> &mut Self { - self.h1_builder.config(config); + pub fn http1_options(&mut self, opts: Http1Options) -> &mut Self { + self.h1_builder.config(opts); self } /// Provide a configuration for HTTP/2. - pub fn http2_config(&mut self, config: Http2Config) -> &mut Self { - self.h2_builder.config(config); + pub fn http2_options(&mut self, opts: Http2Options) -> &mut Self { + self.h2_builder.config(opts); self } diff --git a/src/core/ext/config.rs b/src/core/ext/config.rs index c7614d0cf..077ee7467 100644 --- a/src/core/ext/config.rs +++ b/src/core/ext/config.rs @@ -135,10 +135,10 @@ impl RequestConfigValue for RequestTcpConnectOptions { } #[derive(Clone, Copy)] -pub(crate) struct RequestTransportConfig; +pub(crate) struct RequestTransportOptions; -impl RequestConfigValue for RequestTransportConfig { - type Value = crate::core::client::config::TransportConfig; +impl RequestConfigValue for RequestTransportOptions { + type Value = crate::core::client::config::TransportOptions; } #[derive(Clone, Copy)] diff --git a/src/core/ext/mod.rs b/src/core/ext/mod.rs index e8945aed8..a83110e29 100644 --- a/src/core/ext/mod.rs +++ b/src/core/ext/mod.rs @@ -6,7 +6,7 @@ mod header; pub(crate) use config::{ RequestConfig, RequestConfigValue, RequestEnforcedHttpVersion, RequestOriginalHeaders, - RequestProxyMatcher, RequestTcpConnectOptions, RequestTransportConfig, + RequestProxyMatcher, RequestTcpConnectOptions, RequestTransportOptions, }; pub(crate) use h1_reason_phrase::ReasonPhrase; pub use header::OriginalHeaders; diff --git a/src/tls/config.rs b/src/tls/config.rs index 2bd936390..c531be1ef 100644 --- a/src/tls/config.rs +++ b/src/tls/config.rs @@ -6,18 +6,18 @@ use super::{ AlpnProtocol, AlpsProtocol, CertificateCompressionAlgorithm, ExtensionType, TlsVersion, }; -/// Builder for `[`TlsConfig`]`. +/// Builder for `[`TlsOptions`]`. #[must_use] #[derive(Debug, Clone)] -pub struct TlsConfigBuilder { - config: TlsConfig, +pub struct TlsOptionsBuilder { + config: TlsOptions, } /// Configuration settings for TLS connections. /// /// This struct defines various parameters to fine-tune the behavior of a TLS connection, #[derive(Debug, Clone, Hash, PartialEq, Eq)] -pub struct TlsConfig { +pub struct TlsOptions { pub(crate) alpn_protos: Option, pub(crate) alps_protos: Option, pub(crate) alps_use_new_codepoint: bool, @@ -47,9 +47,9 @@ pub struct TlsConfig { pub(crate) random_aes_hw_override: bool, } -impl TlsConfigBuilder { - /// Builds the `TlsConfig` from the builder. - pub fn build(self) -> TlsConfig { +impl TlsOptionsBuilder { + /// Builds the `TlsOptions` from the builder. + pub fn build(self) -> TlsOptions { self.config } @@ -258,18 +258,18 @@ impl TlsConfigBuilder { } } -impl TlsConfig { - /// Creates a new `TlsConfigBuilder` instance. - pub fn builder() -> TlsConfigBuilder { - TlsConfigBuilder { - config: TlsConfig::default(), +impl TlsOptions { + /// Creates a new `TlsOptionsBuilder` instance. + pub fn builder() -> TlsOptionsBuilder { + TlsOptionsBuilder { + config: TlsOptions::default(), } } } -impl Default for TlsConfig { +impl Default for TlsOptions { fn default() -> Self { - TlsConfig { + TlsOptions { alpn_protos: Some(AlpnProtocol::encode_sequence(&[ AlpnProtocol::HTTP2, AlpnProtocol::HTTP1, diff --git a/src/tls/conn/mod.rs b/src/tls/conn/mod.rs index 17eff3e5d..641bd400b 100644 --- a/src/tls/conn/mod.rs +++ b/src/tls/conn/mod.rs @@ -37,7 +37,7 @@ use crate::{ error::BoxError, sync::Mutex, tls::{ - CertStore, Identity, KeyLogPolicy, TlsConfig, TlsVersion, + CertStore, Identity, KeyLogPolicy, TlsOptions, TlsVersion, conn::ext::{ConnectConfigurationExt, SslConnectorBuilderExt}, }, }; @@ -341,7 +341,7 @@ impl TlsConnectorBuilder { } /// Build the `TlsConnector` with the provided configuration. - pub fn build(&self, mut cfg: TlsConfig) -> crate::Result { + pub fn build(&self, mut cfg: TlsOptions) -> crate::Result { // Replace the default configuration with the provided one cfg.max_tls_version = cfg.max_tls_version.or(self.max_version); cfg.min_tls_version = cfg.min_tls_version.or(self.min_version); diff --git a/src/tls/mod.rs b/src/tls/mod.rs index 15241e165..ce3b6fa33 100644 --- a/src/tls/mod.rs +++ b/src/tls/mod.rs @@ -16,7 +16,7 @@ pub(crate) use self::conn::{ EstablishedConn, HttpsConnector, MaybeHttpsStream, TlsConnector, TlsConnectorBuilder, }; pub use self::{ - config::TlsConfig, + config::TlsOptions, keylog::KeyLogPolicy, types::{ AlpnProtocol, AlpsProtocol, CertificateCompressionAlgorithm, ExtensionType, TlsVersion, diff --git a/tests/badssl.rs b/tests/badssl.rs index b3f764996..98087ffb2 100644 --- a/tests/badssl.rs +++ b/tests/badssl.rs @@ -2,7 +2,7 @@ use std::time::Duration; use wreq::{ Client, EmulationProvider, - tls::{AlpsProtocol, TlsConfig, TlsInfo, TlsVersion}, + tls::{AlpsProtocol, TlsInfo, TlsOptions, TlsVersion}, }; macro_rules! join { @@ -60,8 +60,8 @@ const CURVES_LIST: &str = join!( #[tokio::test] async fn test_3des_support() -> wreq::Result<()> { let emulation = EmulationProvider::builder() - .tls_config( - TlsConfig::builder() + .with_tls( + TlsOptions::builder() .cipher_list(join!( ":", "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", @@ -94,8 +94,8 @@ async fn test_3des_support() -> wreq::Result<()> { #[tokio::test] async fn test_firefox_7x_100_cipher() -> wreq::Result<()> { let emulation = EmulationProvider::builder() - .tls_config( - TlsConfig::builder() + .with_tls( + TlsOptions::builder() .cipher_list(join!( ":", "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", @@ -129,8 +129,8 @@ async fn test_firefox_7x_100_cipher() -> wreq::Result<()> { #[tokio::test] async fn test_alps_new_endpoint() -> wreq::Result<()> { let emulation = EmulationProvider::builder() - .tls_config( - TlsConfig::builder() + .with_tls( + TlsOptions::builder() .min_tls_version(TlsVersion::TLS_1_2) .max_tls_version(TlsVersion::TLS_1_3) .alps_protos(&[AlpsProtocol::HTTP2]) @@ -173,8 +173,8 @@ async fn test_aes_hw_override() -> wreq::Result<()> { ); let emulation = EmulationProvider::builder() - .tls_config( - TlsConfig::builder() + .with_tls( + TlsOptions::builder() .cipher_list(CIPHER_LIST) .min_tls_version(TlsVersion::TLS_1_2) .max_tls_version(TlsVersion::TLS_1_3) From 1c2a8805ffa331f7f601756276b4a93744a17b76 Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Sat, 12 Jul 2025 09:28:52 +0800 Subject: [PATCH 02/19] commit --- examples/emulation_firefox.rs | 12 +- examples/emulation_twitter.rs | 127 +++++++++++-- examples/http1_recv_case_sensitive_headers.rs | 15 +- examples/http1_websocket.rs | 2 +- examples/http2_websocket.rs | 2 +- examples/request_with_emulation.rs | 8 +- src/client/client/macros.rs | 29 --- src/client/emulation.rs | 122 +++++++------ src/client/{client => http}/aliases.rs | 6 +- src/client/{client => http}/future.rs | 22 ++- src/client/{client => http}/mod.rs | 170 +++++++----------- src/client/{client => http}/service.rs | 2 +- src/client/{middleware => layer}/config.rs | 2 +- .../{middleware => layer}/cookie/future.rs | 0 .../{middleware => layer}/cookie/layer.rs | 0 .../{middleware => layer}/cookie/mod.rs | 0 .../{middleware => layer}/decoder/layer.rs | 2 +- .../{middleware => layer}/decoder/mod.rs | 0 src/client/{middleware => layer}/mod.rs | 0 .../{middleware => layer}/redirect/future.rs | 0 .../{middleware => layer}/redirect/mod.rs | 0 .../{middleware => layer}/redirect/policy.rs | 0 src/client/{middleware => layer}/retry/mod.rs | 0 .../{middleware => layer}/timeout/body.rs | 0 .../{middleware => layer}/timeout/future.rs | 0 .../{middleware => layer}/timeout/layer.rs | 2 +- .../{middleware => layer}/timeout/mod.rs | 0 src/client/mod.rs | 11 +- src/client/request.rs | 24 +-- src/client/{websocket => ws}/json.rs | 0 src/client/{websocket => ws}/message.rs | 0 src/client/{websocket => ws}/mod.rs | 14 +- src/connect.rs | 151 +++------------- src/core/client/conn/http1.rs | 8 +- src/core/client/conn/http2.rs | 10 +- src/core/client/connect/http.rs | 2 +- src/core/client/mod.rs | 20 +-- src/core/client/{config => options}/http1.rs | 0 src/core/client/{config => options}/http2.rs | 0 src/core/client/{config => options}/mod.rs | 0 src/core/ext/config.rs | 2 +- src/lib.rs | 16 +- src/redirect.rs | 2 +- src/tls/conn/mod.rs | 24 ++- src/tls/mod.rs | 6 +- src/tls/{config.rs => options.rs} | 6 +- src/tls/types.rs | 11 +- tests/badssl.rs | 86 ++++----- 48 files changed, 437 insertions(+), 479 deletions(-) delete mode 100644 src/client/client/macros.rs rename src/client/{client => http}/aliases.rs (90%) rename src/client/{client => http}/future.rs (87%) rename src/client/{client => http}/mod.rs (92%) rename src/client/{client => http}/service.rs (98%) rename src/client/{middleware => layer}/config.rs (94%) rename src/client/{middleware => layer}/cookie/future.rs (100%) rename src/client/{middleware => layer}/cookie/layer.rs (100%) rename src/client/{middleware => layer}/cookie/mod.rs (100%) rename src/client/{middleware => layer}/decoder/layer.rs (96%) rename src/client/{middleware => layer}/decoder/mod.rs (100%) rename src/client/{middleware => layer}/mod.rs (100%) rename src/client/{middleware => layer}/redirect/future.rs (100%) rename src/client/{middleware => layer}/redirect/mod.rs (100%) rename src/client/{middleware => layer}/redirect/policy.rs (100%) rename src/client/{middleware => layer}/retry/mod.rs (100%) rename src/client/{middleware => layer}/timeout/body.rs (100%) rename src/client/{middleware => layer}/timeout/future.rs (100%) rename src/client/{middleware => layer}/timeout/layer.rs (99%) rename src/client/{middleware => layer}/timeout/mod.rs (100%) rename src/client/{websocket => ws}/json.rs (100%) rename src/client/{websocket => ws}/message.rs (100%) rename src/client/{websocket => ws}/mod.rs (98%) rename src/core/client/{config => options}/http1.rs (100%) rename src/core/client/{config => options}/http2.rs (100%) rename src/core/client/{config => options}/mod.rs (100%) rename src/tls/{config.rs => options.rs} (97%) diff --git a/examples/emulation_firefox.rs b/examples/emulation_firefox.rs index bf8620482..cb8e7ab2e 100644 --- a/examples/emulation_firefox.rs +++ b/examples/emulation_firefox.rs @@ -1,6 +1,6 @@ use http::{HeaderMap, HeaderValue, header}; use wreq::{ - Client, EmulationProvider, OriginalHeaders, + Client, Emulation, OriginalHeaders, http1::Http1Options, http2::{ Http2Options, Priorities, Priority, PseudoId, PseudoOrder, SettingId, SettingsOrder, @@ -21,7 +21,7 @@ async fn main() -> wreq::Result<()> { .with_max_level(tracing::Level::TRACE) .init(); - // TLS config + // TLS options config let tls = TlsOptions::builder() .curves_list(join!( ":", @@ -107,13 +107,13 @@ async fn main() -> wreq::Result<()> { ]) .build(); - // HTTP/1 config + // HTTP/1 options config let http1 = Http1Options::builder() .allow_obsolete_multiline_headers_in_responses(true) .max_headers(100) .build(); - // HTTP/2 config + // HTTP/2 options config let http2 = { // HTTP/2 headers frame pseudo-header order let headers_pseudo_order = PseudoOrder::builder() @@ -212,9 +212,9 @@ async fn main() -> wreq::Result<()> { // Create emulation provider with all configurations // This provider encapsulates TLS, HTTP/1, HTTP/2, default headers, and original headers - let emulation = EmulationProvider::builder() + let emulation = Emulation::builder() .with_tls(tls) - .with_config(http1) + .with_http1(http1) .with_http2(http2) .with_headers(headers) .with_original_headers(original_headers) diff --git a/examples/emulation_twitter.rs b/examples/emulation_twitter.rs index 7c3353006..d44b0f84c 100644 --- a/examples/emulation_twitter.rs +++ b/examples/emulation_twitter.rs @@ -1,25 +1,120 @@ -use wreq::Client; +use http::{HeaderMap, HeaderValue, header}; +use wreq::{ + Client, Emulation, OriginalHeaders, + http2::{Http2Options, PseudoId, PseudoOrder}, + tls::{AlpnProtocol, TlsOptions, TlsVersion}, +}; + +macro_rules! join { + ($sep:expr, $first:expr $(, $rest:expr)*) => { + concat!($first $(, $sep, $rest)*) + }; +} #[tokio::main] -async fn main() -> Result<(), wreq::Error> { - // Build a client to emulation Firefox136 +async fn main() -> wreq::Result<()> { + tracing_subscriber::fmt() + .with_max_level(tracing::Level::TRACE) + .init(); + + // TLS options config + let tls = TlsOptions::builder() + .enable_ocsp_stapling(true) + .curves_list(join!(":", "X25519", "P-256", "P-384")) + .cipher_list(join!( + ":", + "TLS_AES_128_GCM_SHA256", + "TLS_AES_256_GCM_SHA384", + "TLS_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256" + )) + .sigalgs_list(join!( + ":", + "ecdsa_secp256r1_sha256", + "rsa_pss_rsae_sha256", + "rsa_pkcs1_sha256", + "ecdsa_secp384r1_sha384", + "rsa_pss_rsae_sha384", + "rsa_pkcs1_sha384", + "rsa_pss_rsae_sha512", + "rsa_pkcs1_sha512", + "rsa_pkcs1_sha1" + )) + .alpn_protos(&[AlpnProtocol::HTTP2, AlpnProtocol::HTTP1]) + .min_tls_version(TlsVersion::TLS_1_2) + .max_tls_version(TlsVersion::TLS_1_3) + .build(); + + // HTTP/2 options config + let http2 = Http2Options::builder() + .initial_stream_id(3) + .initial_stream_window_size(16777216) + .initial_connection_window_size(16711681 + 65535) + .headers_pseudo_order( + PseudoOrder::builder() + .extend([ + PseudoId::Method, + PseudoId::Path, + PseudoId::Authority, + PseudoId::Scheme, + ]) + .build(), + ) + .build(); + + // Default headers + let headers = { + let mut headers = HeaderMap::new(); + headers.insert(header::USER_AGENT, HeaderValue::from_static("TwitterAndroid/10.89.0-release.0 (310890000-r-0) G011A/9 (google;G011A;google;G011A;0;;1;2016)")); + headers.insert(header::ACCEPT_LANGUAGE, HeaderValue::from_static("en-US")); + headers.insert( + header::ACCEPT_ENCODING, + HeaderValue::from_static("br, gzip, deflate"), + ); + headers.insert(header::ACCEPT, HeaderValue::from_static("application/json")); + headers.insert(header::CACHE_CONTROL, HeaderValue::from_static("no-store")); + headers.insert( + header::COOKIE, + HeaderValue::from_static("ct0=YOUR_CT0_VALUE;"), + ); + headers + }; + + // Original headers + // The headers keep the original case and order + let original_headers = { + let mut original_headers = OriginalHeaders::new(); + original_headers.insert("cookie"); + original_headers.insert("content-length"); + original_headers.insert("USER-AGENT"); + original_headers.insert("ACCEPT-LANGUAGE"); + original_headers.insert("ACCEPT-ENCODING"); + original_headers + }; + + // Create emulation provider with all configurations + // This provider encapsulates TLS, HTTP/1, HTTP/2, default headers, and original headers + let emulation = Emulation::builder() + .with_tls(tls) + .with_http2(http2) + .with_headers(headers) + .with_original_headers(original_headers) + .build(); + + // Build a client with emulation config let client = Client::builder() - .gzip(true) - .brotli(false) - .zstd(false) - .deflate(false) + .emulation(emulation) + .cert_verification(false) .build()?; // Use the API you're already familiar with - let respnose = client - .get("https://httpbin.org/brotli") - .header("Accept-Encoding", "gzip,br,zstd,deflate") - .send() - .await?; - - dbg!(&respnose.headers()); - - println!("{}", respnose.text().await?); + let resp = client.post("https://tls.peet.ws/api/all").send().await?; + println!("{}", resp.text().await?); Ok(()) } diff --git a/examples/http1_recv_case_sensitive_headers.rs b/examples/http1_recv_case_sensitive_headers.rs index 249f3172d..17f345f85 100644 --- a/examples/http1_recv_case_sensitive_headers.rs +++ b/examples/http1_recv_case_sensitive_headers.rs @@ -2,13 +2,16 @@ use wreq::{OriginalHeaders, http1::Http1Options}; #[tokio::main] async fn main() -> wreq::Result<()> { + // Enable case-sensitive header handling in HTTP/1 + let http1_options = Http1Options::builder() + .preserve_header_case(true) + .http09_responses(true) + .max_headers(100) + .build(); + + // Create a client with the HTTP/1 options let client = wreq::Client::builder() - .http1_options( - Http1Options::builder() - .preserve_header_case(true) - .http09_responses(true) - .build(), - ) + .emulation(http1_options) .http1_only() .build()?; diff --git a/examples/http1_websocket.rs b/examples/http1_websocket.rs index aac642447..0ff0d07ca 100644 --- a/examples/http1_websocket.rs +++ b/examples/http1_websocket.rs @@ -1,7 +1,7 @@ use std::time::Duration; use futures_util::{SinkExt, StreamExt, TryStreamExt}; -use wreq::{Client, header, websocket::Message}; +use wreq::{Client, header, ws::Message}; #[tokio::main] async fn main() -> wreq::Result<()> { diff --git a/examples/http2_websocket.rs b/examples/http2_websocket.rs index e77ad2a74..e0d92646c 100644 --- a/examples/http2_websocket.rs +++ b/examples/http2_websocket.rs @@ -8,7 +8,7 @@ use std::time::Duration; use futures_util::{SinkExt, StreamExt, TryStreamExt}; -use wreq::{Client, header, websocket::Message}; +use wreq::{Client, header, ws::Message}; #[tokio::main] async fn main() -> wreq::Result<()> { diff --git a/examples/request_with_emulation.rs b/examples/request_with_emulation.rs index b7ec962c0..eebbf5b25 100644 --- a/examples/request_with_emulation.rs +++ b/examples/request_with_emulation.rs @@ -1,6 +1,6 @@ use http::{HeaderMap, HeaderValue, header}; use wreq::{ - Client, EmulationProvider, OriginalHeaders, + Client, Emulation, OriginalHeaders, http2::{Http2Options, PseudoId, PseudoOrder}, tls::{AlpnProtocol, TlsOptions, TlsVersion}, }; @@ -17,7 +17,7 @@ async fn main() -> wreq::Result<()> { .with_max_level(tracing::Level::TRACE) .init(); - // TLS config + // TLS options config let tls = TlsOptions::builder() .enable_ocsp_stapling(true) .curves_list(join!(":", "X25519", "P-256", "P-384")) @@ -50,7 +50,7 @@ async fn main() -> wreq::Result<()> { .max_tls_version(TlsVersion::TLS_1_3) .build(); - // HTTP/2 config + // HTTP/2 options config let http2 = Http2Options::builder() .initial_stream_id(3) .initial_stream_window_size(16777216) @@ -99,7 +99,7 @@ async fn main() -> wreq::Result<()> { // Create emulation provider with all configurations // This provider encapsulates TLS, HTTP/1, HTTP/2, default headers, and original headers - let emulation = EmulationProvider::builder() + let emulation = Emulation::builder() .with_tls(tls) .with_http2(http2) .with_headers(headers) diff --git a/src/client/client/macros.rs b/src/client/client/macros.rs deleted file mode 100644 index f173623ee..000000000 --- a/src/client/client/macros.rs +++ /dev/null @@ -1,29 +0,0 @@ -macro_rules! take_url { - ($url:ident) => { - match $url.take() { - Some(url) => url, - None => { - return Poll::Ready(Err(Error::builder("URL already taken in Pending::Request"))) - } - } - }; -} - -macro_rules! take_err { - ($err:ident) => { - match $err.take() { - Some(err) => err, - None => Error::builder("Error already taken in Error"), - } - }; -} - -macro_rules! apply_option { - ($builder:expr, $(($option:expr, $method:ident)),* $(,)?) => { - $( - if let Some(value) = $option { - $builder = $builder.$method(value); - } - )* - }; -} diff --git a/src/client/emulation.rs b/src/client/emulation.rs index 021b4fd06..ea61c1424 100644 --- a/src/client/emulation.rs +++ b/src/client/emulation.rs @@ -1,114 +1,112 @@ use http::HeaderMap; use crate::{ - OriginalHeaders, core::client::config::TransportOptions, http1::Http1Options, + OriginalHeaders, core::client::options::TransportOptions, http1::Http1Options, http2::Http2Options, tls::TlsOptions, }; -/// Trait defining the interface for providing an `EmulationProvider`. +/// Factory trait for creating emulation configurations. /// -/// The `EmulationProviderFactory` trait is designed to be implemented by types that can provide -/// an `EmulationProvider` instance. This trait abstracts the creation and configuration of -/// `EmulationProvider`, allowing different types to offer their own specific configurations. -pub trait EmulationProviderFactory { - /// Provides an `EmulationProvider` instance. - fn emulation(self) -> EmulationProvider; +/// This trait allows different types (enums, structs, etc.) to provide +/// their own emulation configurations. It's particularly useful for: +/// - Predefined browser profiles +/// - Dynamic configuration based on runtime conditions +/// - User-defined custom emulation strategies +pub trait EmulationFactory { + /// Creates an `Emulation` instance from this factory. + fn emulation(self) -> Emulation; } -/// Builder for creating an `EmulationProvider`. +/// Builder for creating an `Emulation` configuration. #[must_use] #[derive(Debug)] -pub struct EmulationProviderBuilder { - provider: EmulationProvider, +pub struct EmulationBuilder { + emulation: Emulation, } -/// HTTP connection context that manages both HTTP and TLS configurations. +/// HTTP emulation configuration for mimicking different browsers or clients. /// -/// The `EmulationProvider` provides a complete environment for HTTP connections, -/// including both HTTP-specific settings and the underlying TLS configuration. -/// This unified context ensures consistent behavior across connections. +/// This struct combines transport-layer options (HTTP/1, HTTP/2, TLS) with +/// request-level settings (headers, header case preservation) to provide +/// a complete emulation profile. #[derive(Default, Debug)] -pub struct EmulationProvider { - transport_options: Option, - default_headers: Option, +pub struct Emulation { + transport: Option, + headers: Option, original_headers: Option, } -impl EmulationProviderBuilder { - /// Sets the TLS configuration for the `EmulationProvider`. +impl EmulationBuilder { + /// Sets the TLS options configuration for the emulation. pub fn with_tls(mut self, config: C) -> Self where C: Into>, { - self.provider - .transport_options + self.emulation + .transport .get_or_insert_with(TransportOptions::default) .configure_tls(config); self } - /// Sets the HTTP/1 configuration for the `EmulationProvider`. - pub fn with_config(mut self, config: C) -> Self + /// Sets the HTTP/1 options configuration for the emulation. + pub fn with_http1(mut self, config: C) -> Self where C: Into>, { - self.provider - .transport_options + self.emulation + .transport .get_or_insert_with(TransportOptions::default) .configure_http1(config); self } - /// Sets the HTTP/2 configuration for the `EmulationProvider`. + /// Sets the HTTP/2 options configuration for the emulation. pub fn with_http2(mut self, config: C) -> Self where C: Into>, { - self.provider - .transport_options + self.emulation + .transport .get_or_insert_with(TransportOptions::default) .configure_http2(config); self } - /// Sets the default headers for the `EmulationProvider`. + /// Sets the default headers for the emulation. pub fn with_headers(mut self, headers: H) -> Self where H: Into>, { - self.provider.default_headers = headers.into(); + self.emulation.headers = headers.into(); self } - /// Sets the original headers for the `EmulationProvider`. + /// Sets the original headers for the emulation. pub fn with_original_headers(mut self, headers: H) -> Self where H: Into>, { - self.provider.original_headers = headers.into(); + self.emulation.original_headers = headers.into(); self } - /// Builds the `EmulationProvider` instance. - pub fn build(self) -> EmulationProvider { - self.provider + /// Builds the `Emulation` instance. + pub fn build(self) -> Emulation { + self.emulation } } -impl EmulationProvider { - /// Creates a new `EmulationProviderBuilder`. - /// - /// # Returns - /// - /// Returns a new `EmulationProviderBuilder` instance. +impl Emulation { + /// Creates a new `EmulationBuilder`. #[inline] - pub fn builder() -> EmulationProviderBuilder { - EmulationProviderBuilder { - provider: EmulationProvider::default(), + pub fn builder() -> EmulationBuilder { + EmulationBuilder { + emulation: Emulation::default(), } } - /// Decomposes the `EmulationProvider` into its components. + /// Decomposes the emulation into its components. #[inline] pub(crate) fn into_parts( self, @@ -117,20 +115,30 @@ impl EmulationProvider { Option, Option, ) { - ( - self.transport_options, - self.default_headers, - self.original_headers, - ) + (self.transport, self.headers, self.original_headers) } } -/// Implement `EmulationProviderFactory` for `EmulationProvider`. -/// -/// This implementation allows an `EmulationProvider` to be used wherever an -/// `EmulationProviderFactory` is required, providing a default emulation configuration. -impl EmulationProviderFactory for EmulationProvider { - fn emulation(self) -> EmulationProvider { +impl EmulationFactory for Emulation { + fn emulation(self) -> Emulation { self } } + +impl EmulationFactory for Http1Options { + fn emulation(self) -> Emulation { + Emulation::builder().with_http1(self).build() + } +} + +impl EmulationFactory for Http2Options { + fn emulation(self) -> Emulation { + Emulation::builder().with_http2(self).build() + } +} + +impl EmulationFactory for TlsOptions { + fn emulation(self) -> Emulation { + Emulation::builder().with_tls(self).build() + } +} diff --git a/src/client/client/aliases.rs b/src/client/http/aliases.rs similarity index 90% rename from src/client/client/aliases.rs rename to src/client/http/aliases.rs index 50e027c91..d2c94903f 100644 --- a/src/client/client/aliases.rs +++ b/src/client/http/aliases.rs @@ -6,7 +6,7 @@ use tower::{ use super::{Body, service::ClientService}; use crate::{ - client::middleware::{ + client::layer::{ redirect::FollowRedirect, retry::Http2RetryPolicy, timeout::{ResponseBodyTimeout, Timeout, TimeoutBody}, @@ -20,7 +20,7 @@ use crate::{ type CookieLayer = T; #[cfg(feature = "cookies")] -type CookieLayer = crate::client::middleware::cookie::CookieManager; +type CookieLayer = crate::client::layer::cookie::CookieManager; #[cfg(not(any( feature = "gzip", @@ -36,7 +36,7 @@ type Decompression = T; feature = "brotli", feature = "deflate" ))] -type Decompression = crate::client::middleware::decoder::Decompression; +type Decompression = crate::client::layer::decoder::Decompression; #[cfg(any( feature = "gzip", diff --git a/src/client/client/future.rs b/src/client/http/future.rs similarity index 87% rename from src/client/client/future.rs rename to src/client/http/future.rs index bbace7b27..2709d3fe6 100644 --- a/src/client/client/future.rs +++ b/src/client/http/future.rs @@ -14,12 +14,32 @@ use super::{ }; use crate::{ Body, Error, - client::{body, middleware::redirect::RequestUri}, + client::{body, layer::redirect::RequestUri}, core::body::Incoming, error::BoxError, into_url::IntoUrlSealed, }; +macro_rules! take_url { + ($url:ident) => { + match $url.take() { + Some(url) => url, + None => { + return Poll::Ready(Err(Error::builder("URL already taken in Pending::Request"))) + } + } + }; +} + +macro_rules! take_err { + ($err:ident) => { + match $err.take() { + Some(err) => err, + None => Error::builder("Error already taken in Error"), + } + }; +} + pin_project! { #[project = PendingProj] pub enum Pending { diff --git a/src/client/client/mod.rs b/src/client/http/mod.rs similarity index 92% rename from src/client/client/mod.rs rename to src/client/http/mod.rs index 1730fafc2..c6af30503 100644 --- a/src/client/client/mod.rs +++ b/src/client/http/mod.rs @@ -1,5 +1,3 @@ -#[macro_use] -mod macros; mod aliases; mod future; mod service; @@ -27,7 +25,7 @@ use tower::{ util::{BoxCloneSyncService, BoxCloneSyncServiceLayer}, }; #[cfg(feature = "cookies")] -use {super::middleware::cookie::CookieManagerLayer, crate::cookie}; +use {super::layer::cookie::CookieManagerLayer, crate::cookie}; #[cfg(any( feature = "gzip", @@ -35,12 +33,12 @@ use {super::middleware::cookie::CookieManagerLayer, crate::cookie}; feature = "brotli", feature = "deflate", ))] -use super::middleware::decoder::{AcceptEncoding, DecompressionLayer}; +use super::layer::decoder::{AcceptEncoding, DecompressionLayer}; #[cfg(feature = "websocket")] -use super::websocket::WebSocketRequestBuilder; +use super::ws::WebSocketRequestBuilder; use super::{ - Body, EmulationProviderFactory, - middleware::{ + Body, EmulationFactory, + layer::{ redirect::FollowRedirectLayer, retry::Http2RetryPolicy, timeout::{ResponseBodyTimeoutLayer, TimeoutLayer}, @@ -55,20 +53,16 @@ use crate::{ connect::{BoxedConnectorLayer, BoxedConnectorService, Conn, Connector, Unnameable}, core::{ client::{ - Builder, Client as NativeClient, config::TransportOptions, connect::TcpConnectOptions, + Builder, Client as NativeClient, connect::TcpConnectOptions, options::TransportOptions, }, ext::RequestConfig, rt::{TokioExecutor, tokio::TokioTimer}, }, dns::{DnsResolverWithOverrides, DynResolver, Resolve, gai::GaiResolver}, error::{self, BoxError, Error}, - http1::Http1Options, - http2::Http2Options, proxy::Matcher as ProxyMatcher, redirect::{self, RedirectPolicy}, - tls::{ - AlpnProtocol, CertStore, CertificateInput, Identity, KeyLogPolicy, TlsOptions, TlsVersion, - }, + tls::{AlpnProtocol, CertStore, CertificateInput, Identity, KeyLogPolicy, TlsVersion}, }; /// An `Client` to make Requests with. @@ -148,8 +142,6 @@ struct Config { dns_resolver: Option>, http_version_pref: HttpVersionPref, https_only: bool, - http1_options: Http1Options, - http2_options: Http2Options, http2_max_retry: usize, request_layers: Option>, connector_layers: Option>, @@ -163,7 +155,7 @@ struct Config { tls_cert_verification: bool, min_tls_version: Option, max_tls_version: Option, - tls_options: TlsOptions, + transport_options: TransportOptions, } impl Default for ClientBuilder { @@ -219,8 +211,6 @@ impl ClientBuilder { http_version_pref: HttpVersionPref::All, builder: NativeClient::builder(TokioExecutor::new()), https_only: false, - http1_options: Http1Options::default(), - http2_options: Http2Options::default(), http2_max_retry: 2, request_layers: None, connector_layers: None, @@ -233,7 +223,8 @@ impl ClientBuilder { tls_cert_verification: true, min_tls_version: None, max_tls_version: None, - tls_options: TlsOptions::default(), + // Transport options for HTTP/1/2 and TLS. + transport_options: TransportOptions::default(), }, } } @@ -261,10 +252,12 @@ impl ClientBuilder { .iter() .any(ProxyMatcher::maybe_has_http_custom_headers); + let (tls_opts, http1_opts, http2_opts) = config.transport_options.into_parts(); + config .builder - .http1_options(config.http1_options) - .http2_options(config.http2_options) + .http1_options(http1_opts) + .http2_options(http2_opts) .http2_only(matches!(config.http_version_pref, HttpVersionPref::Http2)) .http2_timer(TokioTimer::new()) .pool_timer(TokioTimer::new()) @@ -292,39 +285,40 @@ impl ClientBuilder { DynResolver::new(resolver) }; - match config.http_version_pref { - HttpVersionPref::Http1 => { - config.tls_options.alpn_protos = Some(AlpnProtocol::HTTP1.encode()); - } - HttpVersionPref::Http2 => { - config.tls_options.alpn_protos = Some(AlpnProtocol::HTTP2.encode()); - } - _ => {} - } + let tls_opts = tls_opts.unwrap_or_default(); + let alpn_protocol = match config.http_version_pref { + HttpVersionPref::Http1 => Some(AlpnProtocol::HTTP1), + + HttpVersionPref::Http2 => Some(AlpnProtocol::HTTP2), + _ => None, + }; Connector::builder(proxies.clone(), resolver) .connect_timeout(config.connect_timeout) - .tcp_keepalive(config.tcp_keepalive) - .tcp_keepalive_interval(config.tcp_keepalive_interval) - .tcp_keepalive_retries(config.tcp_keepalive_retries) - .tcp_reuse_address(config.tcp_reuse_address) - .tcp_connect_options(config.tcp_connect_options) - .tcp_nodelay(config.tcp_nodelay) - .verbose(config.connection_verbose) - .tls_max_version(config.max_tls_version) - .tls_min_version(config.min_tls_version) .tls_info(config.tls_info) - .tls_sni(config.tls_sni) - .tls_verify_hostname(config.tls_verify_hostname) - .tls_cert_verification(config.tls_cert_verification) - .tls_cert_store(config.tls_cert_store) - .tls_identity(config.tls_identity) - .tls_keylog_policy(config.tls_keylog_policy) - .tcp_user_timeout( + .verbose(config.connection_verbose) + .with_http(|http| { + http.set_keepalive(config.tcp_keepalive); + http.set_keepalive_interval(config.tcp_keepalive_interval); + http.set_keepalive_retries(config.tcp_keepalive_retries); + http.set_reuse_address(config.tcp_reuse_address); + http.set_connect_options(config.tcp_connect_options); + http.set_nodelay(config.tcp_nodelay); #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] - config.tcp_user_timeout, - ) - .build(config.tls_options, config.connector_layers)? + http.set_tcp_user_timeout(dur); + }) + .with_tls(|tls| { + tls.alpn_protocol(alpn_protocol) + .max_version(config.max_tls_version) + .min_version(config.min_tls_version) + .tls_sni(config.tls_sni) + .verify_hostname(config.tls_verify_hostname) + .cert_verification(config.tls_cert_verification) + .cert_store(config.tls_cert_store) + .identity(config.tls_identity) + .keylog(config.tls_keylog_policy) + }) + .build(tls_opts, config.connector_layers)? }; let service = { @@ -883,20 +877,6 @@ impl ClientBuilder { self } - /// Sets the HTTP/1 options configuration for the client. - #[inline] - pub fn http1_options(mut self, opts: Http1Options) -> ClientBuilder { - self.config.http1_options = opts; - self - } - - /// Sets the HTTP/2 options configuration for the client. - #[inline] - pub fn http2_options(mut self, opts: Http2Options) -> ClientBuilder { - self.config.http2_options = opts; - self - } - // TCP options /// Set whether sockets have `TCP_NODELAY` enabled. @@ -1173,13 +1153,6 @@ impl ClientBuilder { self } - /// Sets the TLS options configuration for the client. - #[inline] - pub fn tls_options(mut self, opts: TlsOptions) -> ClientBuilder { - self.config.tls_options = opts; - self - } - // DNS options /// Disables the hickory-dns async resolver. @@ -1314,10 +1287,10 @@ impl ClientBuilder { /// Configures the client builder to emulation the specified HTTP context. /// - /// This method sets the necessary headers, HTTP/1 and HTTP/2 configurations, and TLS config - /// 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. + /// This method sets the necessary headers, HTTP/1 and HTTP/2 options configurations, and TLS + /// options config 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. /// /// # Note /// This will overwrite the existing configuration. @@ -1340,25 +1313,20 @@ impl ClientBuilder { #[inline] pub fn emulation

(mut self, factory: P) -> ClientBuilder where - P: EmulationProviderFactory, + P: EmulationFactory, { - let (transport_opts, default_headers, original_headers) = factory.emulation().into_parts(); - if let Some((tls_opts, http1_opts, http2_opts)) = - transport_opts.map(TransportOptions::into_parts) - { - apply_option!( - self, - (tls_opts, tls_options), - (http1_opts, http1_options), - (http2_opts, http2_options) - ); - } + let emulation = factory.emulation(); + let (transport_opts, headers, original_headers) = emulation.into_parts(); - apply_option!( - self, - (default_headers, default_headers), - (original_headers, original_headers) - ); + if let Some(transport_opts) = transport_opts { + self.config.transport_options = transport_opts; + } + if let Some(headers) = headers { + self = self.default_headers(headers); + } + if let Some(original_headers) = original_headers { + self = self.original_headers(original_headers); + } self } } @@ -1403,16 +1371,6 @@ impl Client { self.request(Method::GET, url) } - /// Upgrades the [`RequestBuilder`] to perform a - /// websocket handshake. This returns a wrapped type, so you must do - /// this after you set up your request, and just before you send the - /// request. - #[inline] - #[cfg(feature = "websocket")] - pub fn websocket(&self, url: U) -> WebSocketRequestBuilder { - WebSocketRequestBuilder::new(self.request(Method::GET, url)) - } - /// Convenience method to make a `POST` request to a URL. /// /// # Errors @@ -1486,6 +1444,16 @@ impl Client { RequestBuilder::new(self.clone(), req) } + /// Upgrades the [`RequestBuilder`] to perform a + /// websocket handshake. This returns a wrapped type, so you must do + /// this after you set up your request, and just before you send the + /// request. + #[inline] + #[cfg(feature = "websocket")] + pub fn websocket(&self, url: U) -> WebSocketRequestBuilder { + WebSocketRequestBuilder::new(self.request(Method::GET, url)) + } + /// Executes a `Request`. /// /// A `Request` can be built manually with `Request::new()` or obtained diff --git a/src/client/client/service.rs b/src/client/http/service.rs similarity index 98% rename from src/client/client/service.rs rename to src/client/http/service.rs index ecdc43f0a..30151f008 100644 --- a/src/client/client/service.rs +++ b/src/client/http/service.rs @@ -8,7 +8,7 @@ use tower::Service; use super::{Body, future::CorePending}; use crate::{ - client::middleware::config::RequestSkipDefaultHeaders, + client::layer::config::RequestSkipDefaultHeaders, connect::Connector, core::{ body::Incoming, diff --git a/src/client/middleware/config.rs b/src/client/layer/config.rs similarity index 94% rename from src/client/middleware/config.rs rename to src/client/layer/config.rs index 40bdde82a..9248f6750 100644 --- a/src/client/middleware/config.rs +++ b/src/client/layer/config.rs @@ -47,7 +47,7 @@ pub(crate) struct RequestAcceptEncoding; feature = "deflate", ))] impl RequestConfigValue for RequestAcceptEncoding { - type Value = crate::client::middleware::decoder::AcceptEncoding; + type Value = crate::client::layer::decoder::AcceptEncoding; } #[derive(Clone, Copy)] diff --git a/src/client/middleware/cookie/future.rs b/src/client/layer/cookie/future.rs similarity index 100% rename from src/client/middleware/cookie/future.rs rename to src/client/layer/cookie/future.rs diff --git a/src/client/middleware/cookie/layer.rs b/src/client/layer/cookie/layer.rs similarity index 100% rename from src/client/middleware/cookie/layer.rs rename to src/client/layer/cookie/layer.rs diff --git a/src/client/middleware/cookie/mod.rs b/src/client/layer/cookie/mod.rs similarity index 100% rename from src/client/middleware/cookie/mod.rs rename to src/client/layer/cookie/mod.rs diff --git a/src/client/middleware/decoder/layer.rs b/src/client/layer/decoder/layer.rs similarity index 96% rename from src/client/middleware/decoder/layer.rs rename to src/client/layer/decoder/layer.rs index 8114cd983..6182f81e7 100644 --- a/src/client/middleware/decoder/layer.rs +++ b/src/client/layer/decoder/layer.rs @@ -9,7 +9,7 @@ use tower_http::decompression::{ use tower_service::Service; use super::AcceptEncoding; -use crate::{client::middleware::config::RequestAcceptEncoding, core::ext::RequestConfig}; +use crate::{client::layer::config::RequestAcceptEncoding, core::ext::RequestConfig}; /// Decompresses response bodies of the underlying service. /// diff --git a/src/client/middleware/decoder/mod.rs b/src/client/layer/decoder/mod.rs similarity index 100% rename from src/client/middleware/decoder/mod.rs rename to src/client/layer/decoder/mod.rs diff --git a/src/client/middleware/mod.rs b/src/client/layer/mod.rs similarity index 100% rename from src/client/middleware/mod.rs rename to src/client/layer/mod.rs diff --git a/src/client/middleware/redirect/future.rs b/src/client/layer/redirect/future.rs similarity index 100% rename from src/client/middleware/redirect/future.rs rename to src/client/layer/redirect/future.rs diff --git a/src/client/middleware/redirect/mod.rs b/src/client/layer/redirect/mod.rs similarity index 100% rename from src/client/middleware/redirect/mod.rs rename to src/client/layer/redirect/mod.rs diff --git a/src/client/middleware/redirect/policy.rs b/src/client/layer/redirect/policy.rs similarity index 100% rename from src/client/middleware/redirect/policy.rs rename to src/client/layer/redirect/policy.rs diff --git a/src/client/middleware/retry/mod.rs b/src/client/layer/retry/mod.rs similarity index 100% rename from src/client/middleware/retry/mod.rs rename to src/client/layer/retry/mod.rs diff --git a/src/client/middleware/timeout/body.rs b/src/client/layer/timeout/body.rs similarity index 100% rename from src/client/middleware/timeout/body.rs rename to src/client/layer/timeout/body.rs diff --git a/src/client/middleware/timeout/future.rs b/src/client/layer/timeout/future.rs similarity index 100% rename from src/client/middleware/timeout/future.rs rename to src/client/layer/timeout/future.rs diff --git a/src/client/middleware/timeout/layer.rs b/src/client/layer/timeout/layer.rs similarity index 99% rename from src/client/middleware/timeout/layer.rs rename to src/client/layer/timeout/layer.rs index a3639f0d0..61550991a 100644 --- a/src/client/middleware/timeout/layer.rs +++ b/src/client/layer/timeout/layer.rs @@ -9,7 +9,7 @@ use tower_service::Service; use super::future::{ResponseBodyTimeoutFuture, ResponseFuture}; use crate::{ - client::middleware::{ + client::layer::{ config::{RequestReadTimeout, RequestTotalTimeout}, timeout::TimeoutBody, }, diff --git a/src/client/middleware/timeout/mod.rs b/src/client/layer/timeout/mod.rs similarity index 100% rename from src/client/middleware/timeout/mod.rs rename to src/client/layer/timeout/mod.rs diff --git a/src/client/mod.rs b/src/client/mod.rs index 3126db460..6762a78ce 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -1,21 +1,20 @@ pub use self::{ body::Body, - client::{Client, ClientBuilder}, - emulation::{EmulationProvider, EmulationProviderFactory}, + emulation::{Emulation, EmulationFactory}, + http::{Client, ClientBuilder}, request::{Request, RequestBuilder}, response::Response, upgrade::Upgraded, }; pub mod body; -#[allow(clippy::module_inception)] -mod client; mod emulation; -pub(crate) mod middleware; +mod http; +pub(crate) mod layer; #[cfg(feature = "multipart")] pub mod multipart; pub(crate) mod request; mod response; mod upgrade; #[cfg(feature = "websocket")] -pub mod websocket; +pub mod ws; diff --git a/src/client/request.rs b/src/client/request.rs index 64d5cf516..33d77436f 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -15,21 +15,21 @@ use serde::Serialize; feature = "brotli", feature = "deflate", ))] -use super::middleware::{config::RequestAcceptEncoding, decoder::AcceptEncoding}; +use super::layer::{config::RequestAcceptEncoding, decoder::AcceptEncoding}; #[cfg(feature = "multipart")] use super::multipart; use super::{ body::Body, - client::{Client, Pending}, - middleware::config::{ + http::{Client, Pending}, + layer::config::{ RequestReadTimeout, RequestRedirectPolicy, RequestSkipDefaultHeaders, RequestTotalTimeout, }, response::Response, }; use crate::{ - EmulationProviderFactory, Error, Method, OriginalHeaders, Proxy, Url, + EmulationFactory, Error, Method, OriginalHeaders, Proxy, Url, core::{ - client::{config::TransportOptions, connect::TcpConnectOptions}, + client::{connect::TcpConnectOptions, options::TransportOptions}, ext::{ RequestConfig, RequestEnforcedHttpVersion, RequestOriginalHeaders, RequestProxyMatcher, RequestTcpConnectOptions, RequestTransportOptions, @@ -646,20 +646,20 @@ impl RequestBuilder { /// Configures the request builder to emulation the specified HTTP context. /// - /// This method sets the necessary headers, HTTP/1 and HTTP/2 configurations, and TLS config - /// 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. + /// This method sets the necessary headers, HTTP/1 and HTTP/2 options configurations, and TLS + /// options config 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. pub fn emulation

(mut self, factory: P) -> RequestBuilder where - P: EmulationProviderFactory, + P: EmulationFactory, { if let Ok(ref mut req) = self.request { - let opts = req.transport_options_mut().get_or_insert_default(); let emulation = factory.emulation(); let (transport_opts, default_headers, original_headers) = emulation.into_parts(); + if let Some(transport_opts) = transport_opts { - *opts = transport_opts; + *req.transport_options_mut() = Some(transport_opts); } if let Some(default_headers) = default_headers { diff --git a/src/client/websocket/json.rs b/src/client/ws/json.rs similarity index 100% rename from src/client/websocket/json.rs rename to src/client/ws/json.rs diff --git a/src/client/websocket/message.rs b/src/client/ws/message.rs similarity index 100% rename from src/client/websocket/message.rs rename to src/client/ws/message.rs diff --git a/src/client/websocket/mod.rs b/src/client/ws/mod.rs similarity index 98% rename from src/client/websocket/mod.rs rename to src/client/ws/mod.rs index f644e97a7..2eca8493f 100644 --- a/src/client/websocket/mod.rs +++ b/src/client/ws/mod.rs @@ -21,8 +21,8 @@ use tungstenite::protocol::WebSocketConfig; pub use self::message::{CloseCode, CloseFrame, Message, Utf8Bytes}; use crate::{ - EmulationProviderFactory, Error, OriginalHeaders, RequestBuilder, Response, - core::ext::Protocol, proxy::Proxy, + EmulationFactory, Error, OriginalHeaders, RequestBuilder, Response, core::ext::Protocol, + proxy::Proxy, }; /// A WebSocket stream. @@ -292,14 +292,14 @@ impl WebSocketRequestBuilder { /// Configures the request builder to emulation the specified WebSocket context. /// - /// This method sets the necessary headers, HTTP/1 and HTTP/2 configurations, and TLS config - /// 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. + /// This method sets the necessary headers, HTTP/1 and HTTP/2 options configurations, and TLS + /// options config 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. #[inline] pub fn emulation

(mut self, factory: P) -> RequestBuilder where - P: EmulationProviderFactory, + P: EmulationFactory, { self.inner = self.inner.emulation(factory); self.inner diff --git a/src/connect.rs b/src/connect.rs index 803ecb5b8..cdbc36804 100644 --- a/src/connect.rs +++ b/src/connect.rs @@ -24,7 +24,7 @@ use crate::{ core::{ client::{ ConnRequest, - connect::{self, Connected, Connection, TcpConnectOptions, proxy}, + connect::{self, Connected, Connection, proxy}, }, rt::{Read, ReadBufCursor, TokioIo, Write}, }, @@ -32,8 +32,8 @@ use crate::{ error::{BoxError, TimedOut, map_timeout_to_connector_error}, proxy::{Intercepted, Matcher as ProxyMatcher}, tls::{ - CertStore, EstablishedConn, HttpsConnector, Identity, KeyLogPolicy, MaybeHttpsStream, - TlsConnector, TlsConnectorBuilder, TlsInfo, TlsOptions, TlsVersion, + EstablishedConn, HttpsConnector, MaybeHttpsStream, TlsConnector, TlsConnectorBuilder, + TlsInfo, TlsOptions, }, }; @@ -66,47 +66,23 @@ pub(crate) struct ConnectorBuilder { } impl ConnectorBuilder { - /// Set that all sockets have `SO_KEEPALIVE` set with the supplied duration - /// to remain idle before sending TCP keepalive probes. - #[inline(always)] - pub(crate) fn tcp_keepalive(mut self, dur: Option) -> ConnectorBuilder { - self.http.set_keepalive(dur); - self - } - - /// Set the duration between two successive TCP keepalive retransmissions, - /// if acknowledgement to the previous keepalive transmission is not received. - #[inline(always)] - pub(crate) fn tcp_keepalive_interval(mut self, dur: Option) -> ConnectorBuilder { - self.http.set_keepalive_interval(dur); - self - } - - /// Set the number of retransmissions to be carried out before declaring that remote end is not - /// available. - #[inline(always)] - pub(crate) fn tcp_keepalive_retries(mut self, retries: Option) -> ConnectorBuilder { - self.http.set_keepalive_retries(retries); - self - } - - /// Sets the value of the `SO_REUSEADDR` option on the socket. - #[inline(always)] - pub(crate) fn tcp_reuse_address(mut self, enabled: bool) -> ConnectorBuilder { - self.http.set_reuse_address(enabled); + /// Set the HTTP connector to use. + #[inline] + pub(crate) fn with_http(mut self, call: F) -> ConnectorBuilder + where + F: FnOnce(&mut HttpConnector), + { + call(&mut self.http); self } - /// Sets the value of the TCP_USER_TIMEOUT option on the socket. - #[inline(always)] - pub(crate) fn tcp_user_timeout( - #[allow(unused_mut)] mut self, - #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] dur: Option< - Duration, - >, - ) -> ConnectorBuilder { - #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] - self.http.set_tcp_user_timeout(dur); + /// Set the TLS connector builder to use. + #[inline] + pub(crate) fn with_tls(mut self, call: F) -> ConnectorBuilder + where + F: FnOnce(TlsConnectorBuilder) -> TlsConnectorBuilder, + { + self.tls_builder = call(self.tls_builder); self } @@ -114,29 +90,9 @@ impl ConnectorBuilder { /// /// If a domain resolves to multiple IP addresses, the timeout will be /// evenly divided across them. - #[inline(always)] + #[inline] pub(crate) fn connect_timeout(mut self, timeout: Option) -> ConnectorBuilder { self.timeout = timeout; - self.http.set_connect_timeout(timeout); - self - } - - /// Sets the name of the interface to bind sockets produced by this - /// connector. - #[inline(always)] - pub(crate) fn tcp_connect_options( - mut self, - options: Option, - ) -> ConnectorBuilder { - self.http.set_tcp_connect_options(options); - self - } - - /// Set the tcp_nodelay flag for the connector. - #[inline(always)] - pub(crate) fn tcp_nodelay(mut self, enabled: bool) -> ConnectorBuilder { - self.tcp_nodelay = enabled; - self.http.set_nodelay(enabled); self } @@ -147,36 +103,6 @@ impl ConnectorBuilder { self } - /// Sets the maximum TLS version to be used. - #[inline(always)] - pub(crate) fn tls_max_version(mut self, version: T) -> ConnectorBuilder - where - T: Into>, - { - self.tls_builder = self.tls_builder.max_version(version); - self - } - - /// Sets the minimum TLS version to be used. - #[inline(always)] - pub(crate) fn tls_min_version(mut self, version: T) -> ConnectorBuilder - where - T: Into>, - { - self.tls_builder = self.tls_builder.min_version(version); - self - } - - /// Sets the TLS keylog policy. - #[inline(always)] - pub(crate) fn tls_keylog_policy( - mut self, - keylog_policy: Option, - ) -> ConnectorBuilder { - self.tls_builder = self.tls_builder.keylog(keylog_policy); - self - } - /// Sets the TLS info flag. #[inline(always)] pub(crate) fn tls_info(mut self, enabled: bool) -> ConnectorBuilder { @@ -184,42 +110,7 @@ impl ConnectorBuilder { self } - /// Sets the Server Name Indication (SNI) flag. - #[inline(always)] - pub(crate) fn tls_sni(mut self, enabled: bool) -> ConnectorBuilder { - self.tls_builder = self.tls_builder.tls_sni(enabled); - self - } - - /// Sets the hostname verification flag. - #[inline(always)] - pub(crate) fn tls_verify_hostname(mut self, enabled: bool) -> ConnectorBuilder { - self.tls_builder = self.tls_builder.verify_hostname(enabled); - self - } - - /// Sets the identity to be used for client certificate authentication. - #[inline(always)] - pub(crate) fn tls_identity(mut self, identity: Option) -> ConnectorBuilder { - self.tls_builder = self.tls_builder.identity(identity); - self - } - - /// Sets the certificate store used for TLS verification. - #[inline(always)] - pub(crate) fn tls_cert_store(mut self, cert_store: CertStore) -> ConnectorBuilder { - self.tls_builder = self.tls_builder.cert_store(cert_store); - self - } - - /// Sets the certificate verification flag. - #[inline(always)] - pub(crate) fn tls_cert_verification(mut self, enabled: bool) -> ConnectorBuilder { - self.tls_builder = self.tls_builder.cert_verification(enabled); - self - } - - /// Builds the connector with the provided TLS configuration and optional layers. + /// Builds the connector with the provided TLS options configuration and optional layers. pub(crate) fn build( self, opts: TlsOptions, @@ -356,7 +247,7 @@ pub(crate) struct ConnectorService { #[cfg(feature = "socks")] resolver: DynResolver, - // TLS configuration + // TLS options configuration // Note: these are not used in the `TlsConnectorBuilder` but rather // in the `TlsConnector` that is built from it. tls_info: bool, @@ -371,7 +262,7 @@ impl ConnectorService { req: &ConnRequest, ) -> Result, BoxError> { let ex_data = req.ex_data(); - http.set_tcp_connect_options(ex_data.tcp_connect_options().cloned()); + http.set_connect_options(ex_data.tcp_connect_options().cloned()); let tls = match ex_data.tls_options() { Some(cfg) => self.tls_builder.build(cfg.clone())?, None => self.tls.clone(), diff --git a/src/core/client/conn/http1.rs b/src/core/client/conn/http1.rs index 0f3496146..4594ebc66 100644 --- a/src/core/client/conn/http1.rs +++ b/src/core/client/conn/http1.rs @@ -14,8 +14,8 @@ use http_body::Body; use crate::core::{ body::Incoming as IncomingBody, client::{ - config::http1::Http1Options, dispatch::{self, TrySendError}, + options::http1::Http1Options, }, error::BoxError, proto, @@ -231,8 +231,10 @@ impl Builder { } } - pub fn config(&mut self, config: Http1Options) { - self.config = config; + pub fn config(&mut self, opts: Option) { + if let Some(config) = opts { + self.config = config; + } } /// Constructs a connection with the configured options and IO. diff --git a/src/core/client/conn/http2.rs b/src/core/client/conn/http2.rs index ed8a73b7c..d6ff85985 100644 --- a/src/core/client/conn/http2.rs +++ b/src/core/client/conn/http2.rs @@ -199,18 +199,18 @@ where } /// Provide a timer to execute background HTTP2 tasks. - pub fn timer(&mut self, timer: M) -> &mut Builder + pub fn timer(&mut self, timer: M) where M: Timer + Send + Sync + 'static, { self.timer = Time::Timer(Arc::new(timer)); - self } /// Provide a configuration for HTTP/2. - pub fn config(&mut self, config: Http2Options) -> &mut Builder { - self.config = config; - self + pub fn config(&mut self, opts: Option) { + if let Some(config) = opts { + self.config = config; + } } /// Constructs a connection with the configured options and IO. diff --git a/src/core/client/connect/http.rs b/src/core/client/connect/http.rs index 9090e9da7..33d924625 100644 --- a/src/core/client/connect/http.rs +++ b/src/core/client/connect/http.rs @@ -290,7 +290,7 @@ impl HttpConnector { /// Set the connect options to be used when connecting. #[inline] - pub fn set_tcp_connect_options(&mut self, options: Option) { + pub fn set_connect_options(&mut self, options: Option) { self.config_mut().tcp_connect_options = options; } diff --git a/src/core/client/mod.rs b/src/core/client/mod.rs index 332d708cf..7d72255b5 100644 --- a/src/core/client/mod.rs +++ b/src/core/client/mod.rs @@ -2,9 +2,9 @@ //! //! crate::core: provides HTTP over a single connection. See the [`conn`] module. -pub mod config; pub mod conn; pub(super) mod dispatch; +pub mod options; pub mod proxy; pub mod connect; @@ -37,9 +37,9 @@ use crate::{ core::{ body::Incoming, client::{ - config::{TransportOptions, http1::Http1Options, http2::Http2Options}, conn::TrySendError as ConnTrySendError, connect::{Alpn, Connect, Connected, Connection, TcpConnectOptions}, + options::{TransportOptions, http1::Http1Options, http2::Http2Options}, }, collections::{RANDOM_STATE, memo::HashMemo}, common::{Exec, Lazy, lazy, timer}, @@ -94,7 +94,7 @@ impl ConnExtra { self.tcp_options.as_ref() } - /// Return the TLS configuration. + /// Return the TLS options configuration. #[inline] pub(crate) fn tls_options(&self) -> Option<&TlsOptions> { self.tls_options.as_ref() @@ -118,7 +118,7 @@ pub(crate) struct ConnKey(Arc>); /// /// A `ConnRequest` encapsulates the information required to initiate /// an outgoing network connection, including the HTTP target URI, protocol -/// version, optional proxy handling, TCP options, and TLS configuration. +/// version, optional proxy handling, TCP options, and TLS options configuration. /// /// This struct is used internally to drive the connection setup process /// and may influence connection pooling, ALPN negotiation, and proxy routing. @@ -351,12 +351,8 @@ where if let Some(opts) = transport_options { let (tls, http1, http2) = opts.into_parts(); tls_options = tls; - if let Some(opts) = http1 { - this.h1_builder.config(opts); - } - if let Some(opts) = http2 { - this.h2_builder.config(opts); - } + this.h1_builder.config(http1); + this.h2_builder.config(http2); } let conn_req = ConnRequest { @@ -1251,13 +1247,13 @@ impl Builder { } /// Provide a configuration for HTTP/1. - pub fn http1_options(&mut self, opts: Http1Options) -> &mut Self { + pub fn http1_options(&mut self, opts: Option) -> &mut Self { self.h1_builder.config(opts); self } /// Provide a configuration for HTTP/2. - pub fn http2_options(&mut self, opts: Http2Options) -> &mut Self { + pub fn http2_options(&mut self, opts: Option) -> &mut Self { self.h2_builder.config(opts); self } diff --git a/src/core/client/config/http1.rs b/src/core/client/options/http1.rs similarity index 100% rename from src/core/client/config/http1.rs rename to src/core/client/options/http1.rs diff --git a/src/core/client/config/http2.rs b/src/core/client/options/http2.rs similarity index 100% rename from src/core/client/config/http2.rs rename to src/core/client/options/http2.rs diff --git a/src/core/client/config/mod.rs b/src/core/client/options/mod.rs similarity index 100% rename from src/core/client/config/mod.rs rename to src/core/client/options/mod.rs diff --git a/src/core/ext/config.rs b/src/core/ext/config.rs index 077ee7467..7556ebce4 100644 --- a/src/core/ext/config.rs +++ b/src/core/ext/config.rs @@ -138,7 +138,7 @@ impl RequestConfigValue for RequestTcpConnectOptions { pub(crate) struct RequestTransportOptions; impl RequestConfigValue for RequestTransportOptions { - type Value = crate::core::client::config::TransportOptions; + type Value = crate::core::client::options::TransportOptions; } #[derive(Clone, Copy)] diff --git a/src/lib.rs b/src/lib.rs index e0cb4168d..67451ffc1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -53,7 +53,7 @@ //! use std::time::Duration; //! //! use futures_util::{SinkExt, StreamExt, TryStreamExt}; -//! use wreq::{Client, header, websocket::Message}; +//! use wreq::{Client, header, ws::Message}; //! //! #[tokio::main] //! async fn main() -> wreq::Result<()> { @@ -304,13 +304,13 @@ fn _assert_impls() { assert_send::(); assert_send::(); #[cfg(feature = "websocket")] - assert_send::(); + assert_send::(); assert_send::(); #[cfg(feature = "websocket")] - assert_send::(); + assert_send::(); #[cfg(feature = "websocket")] - assert_send::(); + assert_send::(); assert_send::(); assert_sync::(); @@ -319,14 +319,14 @@ fn _assert_impls() { #[cfg(feature = "multipart")] pub use self::client::multipart; #[cfg(feature = "websocket")] -pub use self::client::websocket; +pub use self::client::ws; pub use self::{ client::{ - Body, Client, ClientBuilder, EmulationProvider, EmulationProviderFactory, Request, - RequestBuilder, Response, Upgraded, + Body, Client, ClientBuilder, Emulation, EmulationFactory, Request, RequestBuilder, + Response, Upgraded, }, core::{ - client::config::{http1, http2}, + client::options::{http1, http2}, ext::{OriginalHeaders, Protocol}, }, proxy::{NoProxy, Proxy}, diff --git a/src/redirect.rs b/src/redirect.rs index 9665b9055..8c5c85007 100644 --- a/src/redirect.rs +++ b/src/redirect.rs @@ -12,7 +12,7 @@ use crate::{ Url, client::{ Body, - middleware::{config::RequestRedirectPolicy, redirect::policy}, + layer::{config::RequestRedirectPolicy, redirect::policy}, }, core::ext::RequestConfig, error::{BoxError, Error}, diff --git a/src/tls/conn/mod.rs b/src/tls/conn/mod.rs index 641bd400b..35fb52c8e 100644 --- a/src/tls/conn/mod.rs +++ b/src/tls/conn/mod.rs @@ -37,7 +37,7 @@ use crate::{ error::BoxError, sync::Mutex, tls::{ - CertStore, Identity, KeyLogPolicy, TlsOptions, TlsVersion, + AlpnProtocol, CertStore, Identity, KeyLogPolicy, TlsOptions, TlsVersion, conn::ext::{ConnectConfigurationExt, SslConnectorBuilderExt}, }, }; @@ -155,7 +155,7 @@ struct Inner { #[derive(Clone)] pub struct TlsConnectorBuilder { session_cache: Arc>>, - keylog: Option, + alpn_protocol: Option, max_version: Option, min_version: Option, tls_sni: bool, @@ -163,6 +163,7 @@ pub struct TlsConnectorBuilder { identity: Option, cert_store: Option, cert_verification: bool, + keylog: Option, } /// A layer which wraps services in an `SslConnector`. @@ -275,6 +276,13 @@ impl Inner { // ====== impl TlsConnectorBuilder ===== impl TlsConnectorBuilder { + /// Sets the alpn protocol to be used. + #[inline(always)] + pub fn alpn_protocol(mut self, protocol: Option) -> Self { + self.alpn_protocol = protocol; + self + } + /// Sets the TLS keylog policy. #[inline(always)] pub fn keylog(mut self, policy: Option) -> Self { @@ -345,6 +353,11 @@ impl TlsConnectorBuilder { // Replace the default configuration with the provided one cfg.max_tls_version = cfg.max_tls_version.or(self.max_version); cfg.min_tls_version = cfg.min_tls_version.or(self.min_version); + cfg.alpn_protos = self + .alpn_protocol + .as_ref() + .map(|p| p.encode()) + .or(cfg.alpn_protos); let mut connector = SslConnector::no_default_verify_builder(SslMethod::tls_client()) .map_err(Error::tls)? @@ -501,14 +514,15 @@ impl TlsConnector { session_cache: Arc::new(Mutex::new(SessionCache::with_capacity( DEFAULT_SESSION_CACHE_CAPACITY, ))), - keylog: None, + alpn_protocol: None, + min_version: None, + max_version: None, identity: None, cert_store: None, cert_verification: true, - min_version: None, - max_version: None, tls_sni: true, verify_hostname: true, + keylog: None, } } } diff --git a/src/tls/mod.rs b/src/tls/mod.rs index ce3b6fa33..c4225fbae 100644 --- a/src/tls/mod.rs +++ b/src/tls/mod.rs @@ -1,4 +1,4 @@ -//! TLS configuration +//! TLS options configuration //! //! By default, a `Client` will make use of BoringSSL for TLS. //! @@ -6,9 +6,9 @@ #[macro_use] mod macros; -mod config; mod conn; mod keylog; +mod options; mod types; mod x509; @@ -16,8 +16,8 @@ pub(crate) use self::conn::{ EstablishedConn, HttpsConnector, MaybeHttpsStream, TlsConnector, TlsConnectorBuilder, }; pub use self::{ - config::TlsOptions, keylog::KeyLogPolicy, + options::TlsOptions, types::{ AlpnProtocol, AlpsProtocol, CertificateCompressionAlgorithm, ExtensionType, TlsVersion, }, diff --git a/src/tls/config.rs b/src/tls/options.rs similarity index 97% rename from src/tls/config.rs rename to src/tls/options.rs index c531be1ef..486b0f0ed 100644 --- a/src/tls/config.rs +++ b/src/tls/options.rs @@ -13,9 +13,11 @@ pub struct TlsOptionsBuilder { config: TlsOptions, } -/// Configuration settings for TLS connections. +/// TLS connection configuration options. /// -/// This struct defines various parameters to fine-tune the behavior of a TLS connection, +/// This struct provides fine-grained control over TLS connection behavior, +/// allowing customization of protocol versions, cipher suites, extensions, +/// and various security features. #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct TlsOptions { pub(crate) alpn_protos: Option, diff --git a/src/tls/types.rs b/src/tls/types.rs index c05c0cc8e..2749b5999 100644 --- a/src/tls/types.rs +++ b/src/tls/types.rs @@ -1,5 +1,5 @@ use boring2::ssl; -use bytes::{Bytes, BytesMut}; +use bytes::{BufMut, Bytes, BytesMut}; /// A TLS protocol version. #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] @@ -25,17 +25,17 @@ pub struct AlpnProtocol(&'static [u8]); impl AlpnProtocol { /// Prefer HTTP/1.1 - pub const HTTP1: AlpnProtocol = AlpnProtocol(b"\x08http/1.1"); + pub const HTTP1: AlpnProtocol = AlpnProtocol(b"http/1.1"); /// Prefer HTTP/2 - pub const HTTP2: AlpnProtocol = AlpnProtocol(b"\x02h2"); + pub const HTTP2: AlpnProtocol = AlpnProtocol(b"h2"); /// Prefer HTTP/3 - pub const HTTP3: AlpnProtocol = AlpnProtocol(b"\x02h3"); + pub const HTTP3: AlpnProtocol = AlpnProtocol(b"h3"); #[inline] pub(crate) fn encode(self) -> Bytes { - Bytes::from_static(self.0) + Self::encode_sequence(std::iter::once(&self)) } #[inline] @@ -45,6 +45,7 @@ impl AlpnProtocol { { let mut buf = BytesMut::new(); for item in items { + buf.put_u8(item.0.len() as u8); buf.extend_from_slice(item.0); } buf.freeze() diff --git a/tests/badssl.rs b/tests/badssl.rs index 98087ffb2..1dbdcb5d8 100644 --- a/tests/badssl.rs +++ b/tests/badssl.rs @@ -1,7 +1,7 @@ use std::time::Duration; use wreq::{ - Client, EmulationProvider, + Client, tls::{AlpsProtocol, TlsInfo, TlsOptions, TlsVersion}, }; @@ -59,21 +59,18 @@ const CURVES_LIST: &str = join!( #[tokio::test] async fn test_3des_support() -> wreq::Result<()> { - let emulation = EmulationProvider::builder() - .with_tls( - TlsOptions::builder() - .cipher_list(join!( - ":", - "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", - "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA" - )) - .curves_list(CURVES_LIST) - .build(), - ) + let tls_options = TlsOptions::builder() + .cipher_list(join!( + ":", + "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", + "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA" + )) + .curves_list(CURVES_LIST) .build(); + // Create a client with the TLS options let client = Client::builder() - .emulation(emulation) + .emulation(tls_options) .cert_verification(false) .connect_timeout(Duration::from_secs(360)) .build()?; @@ -93,22 +90,20 @@ async fn test_3des_support() -> wreq::Result<()> { #[tokio::test] async fn test_firefox_7x_100_cipher() -> wreq::Result<()> { - let emulation = EmulationProvider::builder() - .with_tls( - TlsOptions::builder() - .cipher_list(join!( - ":", - "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", - "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", - "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", - "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256" - )) - .curves_list(CURVES_LIST) - .build(), - ) + let tls_options = TlsOptions::builder() + .cipher_list(join!( + ":", + "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", + "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", + "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256" + )) + .curves_list(CURVES_LIST) .build(); + + // Create a client with the TLS options let client = Client::builder() - .emulation(emulation) + .emulation(tls_options) .cert_verification(false) .connect_timeout(Duration::from_secs(360)) .build()?; @@ -128,19 +123,15 @@ async fn test_firefox_7x_100_cipher() -> wreq::Result<()> { #[tokio::test] async fn test_alps_new_endpoint() -> wreq::Result<()> { - let emulation = EmulationProvider::builder() - .with_tls( - TlsOptions::builder() - .min_tls_version(TlsVersion::TLS_1_2) - .max_tls_version(TlsVersion::TLS_1_3) - .alps_protos(&[AlpsProtocol::HTTP2]) - .alps_use_new_codepoint(true) - .build(), - ) + let tls_options = TlsOptions::builder() + .min_tls_version(TlsVersion::TLS_1_2) + .max_tls_version(TlsVersion::TLS_1_3) + .alps_protos(&[AlpsProtocol::HTTP2]) + .alps_use_new_codepoint(true) .build(); let client = wreq::Client::builder() - .emulation(emulation) + .emulation(tls_options) .connect_timeout(Duration::from_secs(360)) .build()?; @@ -172,21 +163,18 @@ async fn test_aes_hw_override() -> wreq::Result<()> { "TLS_RSA_WITH_AES_256_CBC_SHA" ); - let emulation = EmulationProvider::builder() - .with_tls( - TlsOptions::builder() - .cipher_list(CIPHER_LIST) - .min_tls_version(TlsVersion::TLS_1_2) - .max_tls_version(TlsVersion::TLS_1_3) - .enable_ech_grease(true) - .aes_hw_override(false) - .prefer_chacha20(true) - .build(), - ) + let tls_options = TlsOptions::builder() + .cipher_list(CIPHER_LIST) + .min_tls_version(TlsVersion::TLS_1_2) + .max_tls_version(TlsVersion::TLS_1_3) + .enable_ech_grease(true) + .aes_hw_override(false) + .prefer_chacha20(true) .build(); + // Create a client with the TLS options let client = wreq::Client::builder() - .emulation(emulation) + .emulation(tls_options) .connect_timeout(Duration::from_secs(360)) .build()?; From da9a47ba4c06354c53f9f6defa4f57a5f1d47366 Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Sat, 12 Jul 2025 09:44:21 +0800 Subject: [PATCH 03/19] Update docs --- src/client/emulation.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/client/emulation.rs b/src/client/emulation.rs index ea61c1424..e15564808 100644 --- a/src/client/emulation.rs +++ b/src/client/emulation.rs @@ -24,11 +24,12 @@ pub struct EmulationBuilder { emulation: Emulation, } -/// HTTP emulation configuration for mimicking different browsers or clients. +/// HTTP emulation configuration for mimicking different HTTP clients. /// /// This struct combines transport-layer options (HTTP/1, HTTP/2, TLS) with /// request-level settings (headers, header case preservation) to provide -/// a complete emulation profile. +/// a complete emulation profile for web browsers, mobile applications, +/// API clients, and other HTTP implementations. #[derive(Default, Debug)] pub struct Emulation { transport: Option, From fb5336906c6061c3de545f5cc764e1807388696e Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Sat, 12 Jul 2025 09:52:08 +0800 Subject: [PATCH 04/19] fmt --- src/client/http/mod.rs | 65 +++++++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/src/client/http/mod.rs b/src/client/http/mod.rs index c6af30503..290c743f7 100644 --- a/src/client/http/mod.rs +++ b/src/client/http/mod.rs @@ -50,7 +50,9 @@ use super::{ use crate::dns::hickory::{HickoryDnsResolver, LookupIpStrategy}; use crate::{ IntoUrl, Method, OriginalHeaders, Proxy, - connect::{BoxedConnectorLayer, BoxedConnectorService, Conn, Connector, Unnameable}, + connect::{ + BoxedConnectorLayer, BoxedConnectorService, Conn, Connector, HttpConnector, Unnameable, + }, core::{ client::{ Builder, Client as NativeClient, connect::TcpConnectOptions, options::TransportOptions, @@ -62,7 +64,10 @@ use crate::{ error::{self, BoxError, Error}, proxy::Matcher as ProxyMatcher, redirect::{self, RedirectPolicy}, - tls::{AlpnProtocol, CertStore, CertificateInput, Identity, KeyLogPolicy, TlsVersion}, + tls::{ + AlpnProtocol, CertStore, CertificateInput, Identity, KeyLogPolicy, TlsConnectorBuilder, + TlsVersion, + }, }; /// An `Client` to make Requests with. @@ -285,40 +290,42 @@ impl ClientBuilder { DynResolver::new(resolver) }; - let tls_opts = tls_opts.unwrap_or_default(); - let alpn_protocol = match config.http_version_pref { - HttpVersionPref::Http1 => Some(AlpnProtocol::HTTP1), + let http = |http: &mut HttpConnector| { + http.set_keepalive(config.tcp_keepalive); + http.set_keepalive_interval(config.tcp_keepalive_interval); + http.set_keepalive_retries(config.tcp_keepalive_retries); + http.set_reuse_address(config.tcp_reuse_address); + http.set_connect_options(config.tcp_connect_options); + http.set_nodelay(config.tcp_nodelay); + #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] + http.set_tcp_user_timeout(dur); + }; + + let tls = |tls: TlsConnectorBuilder| { + let alpn_protocol = match config.http_version_pref { + HttpVersionPref::Http1 => Some(AlpnProtocol::HTTP1), - HttpVersionPref::Http2 => Some(AlpnProtocol::HTTP2), - _ => None, + HttpVersionPref::Http2 => Some(AlpnProtocol::HTTP2), + _ => None, + }; + tls.alpn_protocol(alpn_protocol) + .max_version(config.max_tls_version) + .min_version(config.min_tls_version) + .tls_sni(config.tls_sni) + .verify_hostname(config.tls_verify_hostname) + .cert_verification(config.tls_cert_verification) + .cert_store(config.tls_cert_store) + .identity(config.tls_identity) + .keylog(config.tls_keylog_policy) }; Connector::builder(proxies.clone(), resolver) .connect_timeout(config.connect_timeout) .tls_info(config.tls_info) .verbose(config.connection_verbose) - .with_http(|http| { - http.set_keepalive(config.tcp_keepalive); - http.set_keepalive_interval(config.tcp_keepalive_interval); - http.set_keepalive_retries(config.tcp_keepalive_retries); - http.set_reuse_address(config.tcp_reuse_address); - http.set_connect_options(config.tcp_connect_options); - http.set_nodelay(config.tcp_nodelay); - #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] - http.set_tcp_user_timeout(dur); - }) - .with_tls(|tls| { - tls.alpn_protocol(alpn_protocol) - .max_version(config.max_tls_version) - .min_version(config.min_tls_version) - .tls_sni(config.tls_sni) - .verify_hostname(config.tls_verify_hostname) - .cert_verification(config.tls_cert_verification) - .cert_store(config.tls_cert_store) - .identity(config.tls_identity) - .keylog(config.tls_keylog_policy) - }) - .build(tls_opts, config.connector_layers)? + .with_http(http) + .with_tls(tls) + .build(tls_opts.unwrap_or_default(), config.connector_layers)? }; let service = { From 142a38103f35207f9280680b242662151bbf97ba Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Sat, 12 Jul 2025 12:43:32 +0800 Subject: [PATCH 05/19] fmt --- src/tls/{ => conn}/macros.rs | 0 src/tls/conn/mod.rs | 2 ++ src/tls/mod.rs | 2 -- 3 files changed, 2 insertions(+), 2 deletions(-) rename src/tls/{ => conn}/macros.rs (100%) diff --git a/src/tls/macros.rs b/src/tls/conn/macros.rs similarity index 100% rename from src/tls/macros.rs rename to src/tls/conn/macros.rs diff --git a/src/tls/conn/mod.rs b/src/tls/conn/mod.rs index 35fb52c8e..32b82f4ad 100644 --- a/src/tls/conn/mod.rs +++ b/src/tls/conn/mod.rs @@ -1,5 +1,7 @@ //! SSL support via BoringSSL. +#[macro_use] +mod macros; mod cache; mod cert_compression; mod ext; diff --git a/src/tls/mod.rs b/src/tls/mod.rs index c4225fbae..0a7654443 100644 --- a/src/tls/mod.rs +++ b/src/tls/mod.rs @@ -4,8 +4,6 @@ //! //! - Various parts of TLS can also be configured or even disabled on the `ClientBuilder`. -#[macro_use] -mod macros; mod conn; mod keylog; mod options; From 8ae65e1e1bd44637a60de95e4c6c4c8a2bf33516 Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Sat, 12 Jul 2025 17:39:47 +0800 Subject: [PATCH 06/19] fmt --- src/client/emulation.rs | 10 ++++++++++ src/connect.rs | 8 ++++---- src/core/client/options/mod.rs | 10 ++++++---- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/client/emulation.rs b/src/client/emulation.rs index e15564808..f764ab139 100644 --- a/src/client/emulation.rs +++ b/src/client/emulation.rs @@ -39,6 +39,7 @@ pub struct Emulation { impl EmulationBuilder { /// Sets the TLS options configuration for the emulation. + #[inline] pub fn with_tls(mut self, config: C) -> Self where C: Into>, @@ -51,6 +52,7 @@ impl EmulationBuilder { } /// Sets the HTTP/1 options configuration for the emulation. + #[inline] pub fn with_http1(mut self, config: C) -> Self where C: Into>, @@ -63,6 +65,7 @@ impl EmulationBuilder { } /// Sets the HTTP/2 options configuration for the emulation. + #[inline] pub fn with_http2(mut self, config: C) -> Self where C: Into>, @@ -75,6 +78,7 @@ impl EmulationBuilder { } /// Sets the default headers for the emulation. + #[inline] pub fn with_headers(mut self, headers: H) -> Self where H: Into>, @@ -84,6 +88,7 @@ impl EmulationBuilder { } /// Sets the original headers for the emulation. + #[inline] pub fn with_original_headers(mut self, headers: H) -> Self where H: Into>, @@ -93,6 +98,7 @@ impl EmulationBuilder { } /// Builds the `Emulation` instance. + #[inline] pub fn build(self) -> Emulation { self.emulation } @@ -121,24 +127,28 @@ impl Emulation { } impl EmulationFactory for Emulation { + #[inline] fn emulation(self) -> Emulation { self } } impl EmulationFactory for Http1Options { + #[inline] fn emulation(self) -> Emulation { Emulation::builder().with_http1(self).build() } } impl EmulationFactory for Http2Options { + #[inline] fn emulation(self) -> Emulation { Emulation::builder().with_http2(self).build() } } impl EmulationFactory for TlsOptions { + #[inline] fn emulation(self) -> Emulation { Emulation::builder().with_tls(self).build() } diff --git a/src/connect.rs b/src/connect.rs index cdbc36804..c8517f0b7 100644 --- a/src/connect.rs +++ b/src/connect.rs @@ -272,7 +272,7 @@ impl ConnectorService { /// Establishes a direct connection to the target URI without using a proxy. /// May perform a plain TCP or a TLS handshake depending on the URI scheme. - async fn connect_direct(self, mut req: ConnRequest, is_proxy: bool) -> Result { + async fn connect_direct(self, req: ConnRequest, is_proxy: bool) -> Result { trace!("connect with maybe proxy: {:?}", is_proxy); let uri = req.uri().clone(); @@ -285,7 +285,7 @@ impl ConnectorService { http.set_nodelay(true); } - let mut connector = self.build_tls_connector(http, &mut req)?; + let mut connector = self.build_tls_connector(http, &req)?; let io = connector.call(req).await?; // If the connection is HTTPS, wrap the TLS stream in a TlsConn for unified handling. @@ -344,7 +344,7 @@ impl ConnectorService { return if uri.scheme() == Some(&Scheme::HTTPS) { trace!("socks HTTPS over proxy"); - let mut connector = self.build_tls_connector(self.http.clone(), &mut req)?; + let mut connector = self.build_tls_connector(self.http.clone(), &req)?; let established_conn = EstablishedConn::new(req, conn); let io = connector.call(established_conn).await?; @@ -368,7 +368,7 @@ impl ConnectorService { // Handle HTTPS proxy tunneling connection if uri.scheme() == Some(&Scheme::HTTPS) { trace!("tunneling HTTPS over HTTP proxy: {:?}", proxy_uri); - let mut connector = self.build_tls_connector(self.http.clone(), &mut req)?; + let mut connector = self.build_tls_connector(self.http.clone(), &req)?; let mut tunnel = proxy::Tunnel::new(proxy_uri, connector.clone()); if let Some(auth) = proxy.basic_auth() { diff --git a/src/core/client/options/mod.rs b/src/core/client/options/mod.rs index de99a838a..847003f02 100644 --- a/src/core/client/options/mod.rs +++ b/src/core/client/options/mod.rs @@ -10,6 +10,7 @@ use crate::tls::TlsOptions; /// /// This struct allows you to customize protocol-specific and TLS settings /// for network connections made by the client. +#[must_use] #[derive(Debug, Default, Clone)] pub(crate) struct TransportOptions { tls: Option, @@ -18,7 +19,7 @@ pub(crate) struct TransportOptions { } impl TransportOptions { - /// Configures HTTP/1 settings. + /// Sets the HTTP/1 options configuration. #[inline] pub fn configure_http1(&mut self, config: C) where @@ -27,7 +28,7 @@ impl TransportOptions { self.http1 = config.into(); } - /// Configures HTTP/2 settings. + /// Sets the HTTP/2 options configuration. #[inline] pub fn configure_http2(&mut self, config: C) where @@ -36,7 +37,7 @@ impl TransportOptions { self.http2 = config.into(); } - /// Configures TLS settings for the transport layer. + /// Sets the TLS options configuration. #[inline] pub fn configure_tls(&mut self, config: C) where @@ -45,7 +46,8 @@ impl TransportOptions { self.tls = config.into(); } - /// Decomposes the transport options into individual protocol configurations. + /// Consumes the transport options and returns the individual parts. + #[inline] pub fn into_parts( self, ) -> ( From 89bd45e44a1e9ea3414877b327c0baf9c3fb674a Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Sat, 12 Jul 2025 18:13:12 +0800 Subject: [PATCH 07/19] fmt --- src/client/http/mod.rs | 8 ++++++-- src/client/request.rs | 9 +++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/client/http/mod.rs b/src/client/http/mod.rs index 290c743f7..f809b2a1a 100644 --- a/src/client/http/mod.rs +++ b/src/client/http/mod.rs @@ -1325,8 +1325,12 @@ impl ClientBuilder { let emulation = factory.emulation(); let (transport_opts, headers, original_headers) = emulation.into_parts(); - if let Some(transport_opts) = transport_opts { - self.config.transport_options = transport_opts; + if let Some((tls_opts, http1_opts, http2_opts)) = + transport_opts.map(TransportOptions::into_parts) + { + self.config.transport_options.configure_http1(http1_opts); + self.config.transport_options.configure_http2(http2_opts); + self.config.transport_options.configure_tls(tls_opts); } if let Some(headers) = headers { self = self.default_headers(headers); diff --git a/src/client/request.rs b/src/client/request.rs index 33d77436f..eca2265a5 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -658,8 +658,13 @@ impl RequestBuilder { let emulation = factory.emulation(); let (transport_opts, default_headers, original_headers) = emulation.into_parts(); - if let Some(transport_opts) = transport_opts { - *req.transport_options_mut() = Some(transport_opts); + if let Some((tls_opts, http1_opts, http2_opts)) = + transport_opts.map(TransportOptions::into_parts) + { + let transport_opts = req.transport_options_mut().get_or_insert_default(); + transport_opts.configure_http1(http1_opts); + transport_opts.configure_http2(http2_opts); + transport_opts.configure_tls(tls_opts); } if let Some(default_headers) = default_headers { From 3988b65771b2666915548bd7db193ac653009b4c Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Sat, 12 Jul 2025 18:21:19 +0800 Subject: [PATCH 08/19] Ensures that already set values are not overwritten with None --- src/client/emulation.rs | 12 ++++++------ src/client/http/mod.rs | 8 +++++--- src/client/request.rs | 11 +++++------ src/core/client/options/mod.rs | 21 +++++++++++++++------ 4 files changed, 31 insertions(+), 21 deletions(-) diff --git a/src/client/emulation.rs b/src/client/emulation.rs index f764ab139..d468624fd 100644 --- a/src/client/emulation.rs +++ b/src/client/emulation.rs @@ -46,8 +46,8 @@ impl EmulationBuilder { { self.emulation .transport - .get_or_insert_with(TransportOptions::default) - .configure_tls(config); + .get_or_insert_default() + .with_tls(config); self } @@ -59,8 +59,8 @@ impl EmulationBuilder { { self.emulation .transport - .get_or_insert_with(TransportOptions::default) - .configure_http1(config); + .get_or_insert_default() + .with_http1(config); self } @@ -72,8 +72,8 @@ impl EmulationBuilder { { self.emulation .transport - .get_or_insert_with(TransportOptions::default) - .configure_http2(config); + .get_or_insert_default() + .with_http2(config); self } diff --git a/src/client/http/mod.rs b/src/client/http/mod.rs index f809b2a1a..025599448 100644 --- a/src/client/http/mod.rs +++ b/src/client/http/mod.rs @@ -1328,9 +1328,11 @@ impl ClientBuilder { if let Some((tls_opts, http1_opts, http2_opts)) = transport_opts.map(TransportOptions::into_parts) { - self.config.transport_options.configure_http1(http1_opts); - self.config.transport_options.configure_http2(http2_opts); - self.config.transport_options.configure_tls(tls_opts); + self.config + .transport_options + .with_http1(http1_opts) + .with_http2(http2_opts) + .with_tls(tls_opts); } if let Some(headers) = headers { self = self.default_headers(headers); diff --git a/src/client/request.rs b/src/client/request.rs index eca2265a5..90b20cc64 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -661,16 +661,15 @@ impl RequestBuilder { if let Some((tls_opts, http1_opts, http2_opts)) = transport_opts.map(TransportOptions::into_parts) { - let transport_opts = req.transport_options_mut().get_or_insert_default(); - transport_opts.configure_http1(http1_opts); - transport_opts.configure_http2(http2_opts); - transport_opts.configure_tls(tls_opts); + req.transport_options_mut() + .get_or_insert_default() + .with_http1(http1_opts) + .with_http2(http2_opts) + .with_tls(tls_opts); } - if let Some(default_headers) = default_headers { self = self.headers(default_headers); } - if let Some(original_headers) = original_headers { self = self.original_headers(original_headers); } diff --git a/src/core/client/options/mod.rs b/src/core/client/options/mod.rs index 847003f02..dd9d6446c 100644 --- a/src/core/client/options/mod.rs +++ b/src/core/client/options/mod.rs @@ -21,29 +21,38 @@ pub(crate) struct TransportOptions { impl TransportOptions { /// Sets the HTTP/1 options configuration. #[inline] - pub fn configure_http1(&mut self, config: C) + pub fn with_http1(&mut self, config: C) -> &mut Self where C: Into>, { - self.http1 = config.into(); + if let Some(http1) = config.into() { + self.http1 = Some(http1); + } + self } /// Sets the HTTP/2 options configuration. #[inline] - pub fn configure_http2(&mut self, config: C) + pub fn with_http2(&mut self, config: C) -> &mut Self where C: Into>, { - self.http2 = config.into(); + if let Some(http2) = config.into() { + self.http2 = Some(http2); + } + self } /// Sets the TLS options configuration. #[inline] - pub fn configure_tls(&mut self, config: C) + pub fn with_tls(&mut self, config: C) -> &mut Self where C: Into>, { - self.tls = config.into(); + if let Some(tls) = config.into() { + self.tls = Some(tls); + } + self } /// Consumes the transport options and returns the individual parts. From 07a54458681328647e703f006db50ee5dcfdbd33 Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Sat, 12 Jul 2025 18:30:36 +0800 Subject: [PATCH 09/19] fmt --- src/core/client/options/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/client/options/mod.rs b/src/core/client/options/mod.rs index dd9d6446c..caee01026 100644 --- a/src/core/client/options/mod.rs +++ b/src/core/client/options/mod.rs @@ -12,7 +12,7 @@ use crate::tls::TlsOptions; /// for network connections made by the client. #[must_use] #[derive(Debug, Default, Clone)] -pub(crate) struct TransportOptions { +pub struct TransportOptions { tls: Option, http1: Option, http2: Option, From 028564c0e7df81dff77183a2cede5019c87eb319 Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Sat, 12 Jul 2025 21:31:05 +0800 Subject: [PATCH 10/19] fmt --- examples/emulation_firefox.rs | 12 ++++----- examples/emulation_twitter.rs | 9 +++---- examples/http1_recv_case_sensitive_headers.rs | 4 +-- examples/http1_send_case_sensitive_headers.rs | 6 ++--- examples/request_with_emulation.rs | 10 +++---- src/client/emulation.rs | 27 ++++++++++++------- src/core/ext/header/original.rs | 22 ++++++--------- 7 files changed, 43 insertions(+), 47 deletions(-) diff --git a/examples/emulation_firefox.rs b/examples/emulation_firefox.rs index cb8e7ab2e..6bb0060e8 100644 --- a/examples/emulation_firefox.rs +++ b/examples/emulation_firefox.rs @@ -198,7 +198,6 @@ async fn main() -> wreq::Result<()> { headers }; - // Original headers // The headers keep the original case and order let original_headers = { let mut original_headers = OriginalHeaders::new(); @@ -210,14 +209,13 @@ async fn main() -> wreq::Result<()> { original_headers }; - // Create emulation provider with all configurations // This provider encapsulates TLS, HTTP/1, HTTP/2, default headers, and original headers let emulation = Emulation::builder() - .with_tls(tls) - .with_http1(http1) - .with_http2(http2) - .with_headers(headers) - .with_original_headers(original_headers) + .tls_options(tls) + .http1_options(http1) + .http2_options(http2) + .headers(headers) + .original_headers(original_headers) .build(); // Build a client with emulation config diff --git a/examples/emulation_twitter.rs b/examples/emulation_twitter.rs index d44b0f84c..8f51fab1e 100644 --- a/examples/emulation_twitter.rs +++ b/examples/emulation_twitter.rs @@ -97,13 +97,12 @@ async fn main() -> wreq::Result<()> { original_headers }; - // Create emulation provider with all configurations // This provider encapsulates TLS, HTTP/1, HTTP/2, default headers, and original headers let emulation = Emulation::builder() - .with_tls(tls) - .with_http2(http2) - .with_headers(headers) - .with_original_headers(original_headers) + .tls_options(tls) + .http2_options(http2) + .headers(headers) + .original_headers(original_headers) .build(); // Build a client with emulation config diff --git a/examples/http1_recv_case_sensitive_headers.rs b/examples/http1_recv_case_sensitive_headers.rs index 17f345f85..a4aa51583 100644 --- a/examples/http1_recv_case_sensitive_headers.rs +++ b/examples/http1_recv_case_sensitive_headers.rs @@ -1,4 +1,4 @@ -use wreq::{OriginalHeaders, http1::Http1Options}; +use wreq::{Client, OriginalHeaders, http1::Http1Options}; #[tokio::main] async fn main() -> wreq::Result<()> { @@ -10,7 +10,7 @@ async fn main() -> wreq::Result<()> { .build(); // Create a client with the HTTP/1 options - let client = wreq::Client::builder() + let client = Client::builder() .emulation(http1_options) .http1_only() .build()?; diff --git a/examples/http1_send_case_sensitive_headers.rs b/examples/http1_send_case_sensitive_headers.rs index 8f7f6319b..03a1c87e1 100644 --- a/examples/http1_send_case_sensitive_headers.rs +++ b/examples/http1_send_case_sensitive_headers.rs @@ -1,9 +1,9 @@ use http::{HeaderMap, HeaderName, HeaderValue}; -use wreq::OriginalHeaders; +use wreq::{Client, OriginalHeaders}; #[tokio::main] async fn main() -> wreq::Result<()> { - let client = wreq::Client::builder() + let client = Client::builder() .cert_verification(false) .http1_only() .build()?; @@ -12,7 +12,7 @@ async fn main() -> wreq::Result<()> { let mut original_headers = OriginalHeaders::new(); original_headers.insert("Host"); original_headers.insert("X-custom-Header1"); - original_headers.extend(["x-Custom-Header2"]); + original_headers.insert("x-Custom-Header2"); original_headers.insert(HeaderName::from_static("x-custom-header3")); // Use the API you're already familiar with diff --git a/examples/request_with_emulation.rs b/examples/request_with_emulation.rs index eebbf5b25..e53b943a3 100644 --- a/examples/request_with_emulation.rs +++ b/examples/request_with_emulation.rs @@ -85,7 +85,6 @@ async fn main() -> wreq::Result<()> { headers }; - // Original headers // The headers keep the original case and order let original_headers = { let mut original_headers = OriginalHeaders::new(); @@ -97,13 +96,12 @@ async fn main() -> wreq::Result<()> { original_headers }; - // Create emulation provider with all configurations // This provider encapsulates TLS, HTTP/1, HTTP/2, default headers, and original headers let emulation = Emulation::builder() - .with_tls(tls) - .with_http2(http2) - .with_headers(headers) - .with_original_headers(original_headers) + .tls_options(tls) + .http2_options(http2) + .headers(headers) + .original_headers(original_headers) .build(); // Use the API you're already familiar with diff --git a/src/client/emulation.rs b/src/client/emulation.rs index d468624fd..61a4fb4d7 100644 --- a/src/client/emulation.rs +++ b/src/client/emulation.rs @@ -40,7 +40,7 @@ pub struct Emulation { impl EmulationBuilder { /// Sets the TLS options configuration for the emulation. #[inline] - pub fn with_tls(mut self, config: C) -> Self + pub fn tls_options(mut self, config: C) -> Self where C: Into>, { @@ -53,7 +53,7 @@ impl EmulationBuilder { /// Sets the HTTP/1 options configuration for the emulation. #[inline] - pub fn with_http1(mut self, config: C) -> Self + pub fn http1_options(mut self, config: C) -> Self where C: Into>, { @@ -66,7 +66,7 @@ impl EmulationBuilder { /// Sets the HTTP/2 options configuration for the emulation. #[inline] - pub fn with_http2(mut self, config: C) -> Self + pub fn http2_options(mut self, config: C) -> Self where C: Into>, { @@ -79,21 +79,28 @@ impl EmulationBuilder { /// Sets the default headers for the emulation. #[inline] - pub fn with_headers(mut self, headers: H) -> Self + pub fn headers(mut self, headers: H) -> Self where H: Into>, { - self.emulation.headers = headers.into(); + if let Some(src) = headers.into() { + crate::util::replace_headers(self.emulation.headers.get_or_insert_default(), src); + } self } /// Sets the original headers for the emulation. #[inline] - pub fn with_original_headers(mut self, headers: H) -> Self + pub fn original_headers(mut self, headers: H) -> Self where H: Into>, { - self.emulation.original_headers = headers.into(); + if let Some(src) = headers.into() { + self.emulation + .original_headers + .get_or_insert_default() + .extend(src); + } self } @@ -136,20 +143,20 @@ impl EmulationFactory for Emulation { impl EmulationFactory for Http1Options { #[inline] fn emulation(self) -> Emulation { - Emulation::builder().with_http1(self).build() + Emulation::builder().http1_options(self).build() } } impl EmulationFactory for Http2Options { #[inline] fn emulation(self) -> Emulation { - Emulation::builder().with_http2(self).build() + Emulation::builder().http2_options(self).build() } } impl EmulationFactory for TlsOptions { #[inline] fn emulation(self) -> Emulation { - Emulation::builder().with_tls(self).build() + Emulation::builder().tls_options(self).build() } } diff --git a/src/core/ext/header/original.rs b/src/core/ext/header/original.rs index f3da71048..0042078ad 100644 --- a/src/core/ext/header/original.rs +++ b/src/core/ext/header/original.rs @@ -37,6 +37,7 @@ impl OriginalHeaders { /// of the list of values currently associated with the key. The key is not /// updated, though; this matters for types that can be `==` without being /// identical. + #[inline] pub fn insert(&mut self, orig: N) -> bool where N: TryInto, @@ -47,17 +48,10 @@ impl OriginalHeaders { } } - /// Extends a collection with the contents of an iterator. - pub fn extend(&mut self, iter: I) - where - I: IntoIterator, - I::Item: TryInto, - { - let iter = iter.into_iter().filter_map(|item| match item.try_into() { - Ok(orig) => Some((orig.name, orig.orig)), - Err(_) => None, - }); - self.0.extend(iter); + /// Extends a a collection with the contents of an iterator. + #[inline] + pub fn extend(&mut self, iter: OriginalHeaders) { + self.0.extend(iter.0); } /// Returns an iterator over all header names and their original spellings. @@ -81,7 +75,7 @@ impl OriginalHeaders { impl OriginalHeaders { /// Appends a header name to the end of the collection. - #[inline(always)] + #[inline] pub(crate) fn append(&mut self, name: N, orig: Bytes) where N: IntoHeaderName, @@ -91,7 +85,7 @@ impl OriginalHeaders { /// Returns a view of all spellings associated with that header name, /// in the order they were found. - #[inline(always)] + #[inline] pub(crate) fn get_all<'a>( &'a self, name: &HeaderName, @@ -100,7 +94,7 @@ impl OriginalHeaders { } /// Returns an iterator over all header names and their original spellings. - #[inline(always)] + #[inline] pub(crate) fn keys(&self) -> impl Iterator { self.0.keys() } From 73b6fabe095f93f5d23da532827a76a9cfa8a2f5 Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Sat, 12 Jul 2025 21:51:42 +0800 Subject: [PATCH 11/19] fmt --- src/client/emulation.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/client/emulation.rs b/src/client/emulation.rs index 61a4fb4d7..cb4808eb7 100644 --- a/src/client/emulation.rs +++ b/src/client/emulation.rs @@ -84,7 +84,8 @@ impl EmulationBuilder { H: Into>, { if let Some(src) = headers.into() { - crate::util::replace_headers(self.emulation.headers.get_or_insert_default(), src); + let dst = self.emulation.headers.get_or_insert_default(); + crate::util::replace_headers(dst, src); } self } From 97bca950b41fcc8eb97a4aa105047492d5be1950 Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Sat, 12 Jul 2025 22:31:02 +0800 Subject: [PATCH 12/19] Avoid cloning --- src/connect.rs | 2 +- src/tls/conn/ext.rs | 6 ++-- src/tls/conn/macros.rs | 16 ++++----- src/tls/conn/mod.rs | 81 ++++++++++++++++++++++++------------------ src/tls/options.rs | 14 ++++++++ 5 files changed, 72 insertions(+), 47 deletions(-) diff --git a/src/connect.rs b/src/connect.rs index c8517f0b7..d556f6f58 100644 --- a/src/connect.rs +++ b/src/connect.rs @@ -264,7 +264,7 @@ impl ConnectorService { let ex_data = req.ex_data(); http.set_connect_options(ex_data.tcp_connect_options().cloned()); let tls = match ex_data.tls_options() { - Some(cfg) => self.tls_builder.build(cfg.clone())?, + Some(cfg) => self.tls_builder.build(cfg)?, None => self.tls.clone(), }; Ok(HttpsConnector::with_connector(http, tls)) diff --git a/src/tls/conn/ext.rs b/src/tls/conn/ext.rs index 9712cf68e..1b23fccff 100644 --- a/src/tls/conn/ext.rs +++ b/src/tls/conn/ext.rs @@ -1,5 +1,3 @@ -use std::borrow::Cow; - use boring2::{ error::ErrorStack, ssl::{ConnectConfiguration, SslConnectorBuilder, SslSessionRef, SslVerifyMode}, @@ -27,7 +25,7 @@ pub trait SslConnectorBuilderExt { /// Configure the certificate compression algorithm for the given `SslConnectorBuilder`. fn add_certificate_compression_algorithms( self, - algs: Option>, + algs: Option<&[CertificateCompressionAlgorithm]>, ) -> crate::Result; } @@ -72,7 +70,7 @@ impl SslConnectorBuilderExt for SslConnectorBuilder { #[inline] fn add_certificate_compression_algorithms( mut self, - algs: Option>, + algs: Option<&[CertificateCompressionAlgorithm]>, ) -> crate::Result { if let Some(algs) = algs { for algorithm in algs.iter() { diff --git a/src/tls/conn/macros.rs b/src/tls/conn/macros.rs index a1e070989..2e0035fda 100644 --- a/src/tls/conn/macros.rs +++ b/src/tls/conn/macros.rs @@ -20,6 +20,11 @@ macro_rules! set_option { } macro_rules! set_option_ref_try { + ($field:ident, $conn:expr, $setter:ident) => { + if let Some(val) = $field.as_ref() { + $conn.$setter(val).map_err(Error::tls)?; + } + }; ($cfg:expr, $field:ident, $conn:expr, $setter:ident) => { if let Some(val) = $cfg.$field.as_ref() { $conn.$setter(val).map_err(Error::tls)?; @@ -28,17 +33,12 @@ macro_rules! set_option_ref_try { } macro_rules! set_option_inner_try { + ($field:ident, $conn:expr, $setter:ident) => { + $conn.$setter($field.map(|v| v.0)).map_err(Error::tls)?; + }; ($cfg:expr, $field:ident, $conn:expr, $setter:ident) => { $conn .$setter($cfg.$field.map(|v| v.0)) .map_err(Error::tls)?; }; } - -macro_rules! call_option_ref_try { - ($owner:expr, $field:ident, $target:expr, $method:ident) => { - if let Some(val) = $owner.$field.as_ref() { - val.$method($target)?; - } - }; -} diff --git a/src/tls/conn/mod.rs b/src/tls/conn/mod.rs index 32b82f4ad..3f938c996 100644 --- a/src/tls/conn/mod.rs +++ b/src/tls/conn/mod.rs @@ -8,6 +8,7 @@ mod ext; mod service; use std::{ + borrow::Cow, fmt::{self, Debug}, io, pin::Pin, @@ -351,37 +352,46 @@ impl TlsConnectorBuilder { } /// Build the `TlsConnector` with the provided configuration. - pub fn build(&self, mut cfg: TlsOptions) -> crate::Result { + pub fn build<'a, C>(&self, opts: C) -> crate::Result + where + C: Into>, + { + let opts = opts.into(); + // Replace the default configuration with the provided one - cfg.max_tls_version = cfg.max_tls_version.or(self.max_version); - cfg.min_tls_version = cfg.min_tls_version.or(self.min_version); - cfg.alpn_protos = self + let max_tls_version = opts.max_tls_version.or(self.max_version); + let min_tls_version = opts.min_tls_version.or(self.min_version); + let alpn_protos = self .alpn_protocol - .as_ref() .map(|p| p.encode()) - .or(cfg.alpn_protos); + .or(opts.alpn_protos.clone()); + // Create the SslConnector with the provided options let mut connector = SslConnector::no_default_verify_builder(SslMethod::tls_client()) .map_err(Error::tls)? .set_cert_store(self.cert_store.as_ref())? .set_cert_verification(self.cert_verification)? - .add_certificate_compression_algorithms(cfg.certificate_compression_algorithms)?; + .add_certificate_compression_algorithms( + opts.certificate_compression_algorithms.as_deref(), + )?; // Set Identity - call_option_ref_try!(self, identity, &mut connector, add_to_tls); + if let Some(ref identity) = self.identity { + identity.add_to_tls(&mut connector)?; + } // Set minimum TLS version - set_option_inner_try!(cfg, min_tls_version, connector, set_min_proto_version); + set_option_inner_try!(min_tls_version, connector, set_min_proto_version); // Set maximum TLS version - set_option_inner_try!(cfg, max_tls_version, connector, set_max_proto_version); + set_option_inner_try!(max_tls_version, connector, set_max_proto_version); // Set OCSP stapling - set_bool!(cfg, enable_ocsp_stapling, connector, enable_ocsp_stapling); + set_bool!(opts, enable_ocsp_stapling, connector, enable_ocsp_stapling); // Set Signed Certificate Timestamps (SCT) set_bool!( - cfg, + opts, enable_signed_cert_timestamps, connector, enable_signed_cert_timestamps @@ -389,7 +399,7 @@ impl TlsConnectorBuilder { // Set TLS Session ticket options set_bool!( - cfg, + opts, !session_ticket, connector, set_options, @@ -398,7 +408,7 @@ impl TlsConnectorBuilder { // Set TLS PSK DHE key exchange options set_bool!( - cfg, + opts, !psk_dhe_ke, connector, set_options, @@ -407,7 +417,7 @@ impl TlsConnectorBuilder { // Set TLS No Renegotiation options set_bool!( - cfg, + opts, !renegotiation, connector, set_options, @@ -415,46 +425,49 @@ impl TlsConnectorBuilder { ); // Set TLS grease options - set_option!(cfg, grease_enabled, connector, set_grease_enabled); + set_option!(opts, grease_enabled, connector, set_grease_enabled); // Set TLS permute extensions options - set_option!(cfg, permute_extensions, connector, set_permute_extensions); + set_option!(opts, permute_extensions, connector, set_permute_extensions); // Set TLS ALPN protocols - set_option_ref_try!(cfg, alpn_protos, connector, set_alpn_protos); + set_option_ref_try!(alpn_protos, connector, set_alpn_protos); // Set TLS curves list - set_option_ref_try!(cfg, curves_list, connector, set_curves_list); + set_option_ref_try!(opts, curves_list, connector, set_curves_list); // Set TLS signature algorithms list - set_option_ref_try!(cfg, sigalgs_list, connector, set_sigalgs_list); + set_option_ref_try!(opts, sigalgs_list, connector, set_sigalgs_list); // Set TLS cipher list - set_option_ref_try!(cfg, cipher_list, connector, set_cipher_list); + set_option_ref_try!(opts, cipher_list, connector, set_cipher_list); // Set TLS delegated credentials set_option_ref_try!( - cfg, + opts, delegated_credentials, connector, set_delegated_credentials ); // Set TLS record size limit - set_option!(cfg, record_size_limit, connector, set_record_size_limit); + set_option!(opts, record_size_limit, connector, set_record_size_limit); // Set TLS key shares limit - set_option!(cfg, key_shares_limit, connector, set_key_shares_limit); + set_option!(opts, key_shares_limit, connector, set_key_shares_limit); // Set TLS aes hardware override - set_option!(cfg, aes_hw_override, connector, set_aes_hw_override); + set_option!(opts, aes_hw_override, connector, set_aes_hw_override); // Set TLS prefer chacha20 (Encryption order between AES-256-GCM/AES-128-GCM) - set_option!(cfg, prefer_chacha20, connector, set_prefer_chacha20); + set_option!(opts, prefer_chacha20, connector, set_prefer_chacha20); // Set TLS extension permutation - if let Some(val) = cfg.extension_permutation { - let indices = val.iter().map(|ext| ext.0).collect::>(); + if let Some(ref extension_permutation) = opts.extension_permutation { + let indices = extension_permutation + .iter() + .map(|ext| ext.0) + .collect::>(); connector .set_extension_permutation(&indices) .map_err(Error::tls)?; @@ -470,17 +483,17 @@ impl TlsConnectorBuilder { // Create the `HandshakeConfig` with the default session cache capacity. let config = HandshakeConfig::builder() - .no_ticket(cfg.psk_skip_session_ticket) - .alps_protos(cfg.alps_protos) - .alps_use_new_codepoint(cfg.alps_use_new_codepoint) - .enable_ech_grease(cfg.enable_ech_grease) + .no_ticket(opts.psk_skip_session_ticket) + .alps_protos(opts.alps_protos.clone()) + .alps_use_new_codepoint(opts.alps_use_new_codepoint) + .enable_ech_grease(opts.enable_ech_grease) .tls_sni(self.tls_sni) .verify_hostname(self.verify_hostname) - .random_aes_hw_override(cfg.random_aes_hw_override) + .random_aes_hw_override(opts.random_aes_hw_override) .build(); // If the session cache is disabled, we don't need to set up any callbacks. - let cache = cfg.pre_shared_key.then(|| { + let cache = opts.pre_shared_key.then(|| { let cache = self.session_cache.clone(); connector.set_session_cache_mode(SslSessionCacheMode::CLIENT); diff --git a/src/tls/options.rs b/src/tls/options.rs index 486b0f0ed..aea781559 100644 --- a/src/tls/options.rs +++ b/src/tls/options.rs @@ -304,3 +304,17 @@ impl Default for TlsOptions { } } } + +impl<'a> From for Cow<'a, TlsOptions> { + #[inline] + fn from(opts: TlsOptions) -> Self { + Cow::Owned(opts) + } +} + +impl<'a> From<&'a TlsOptions> for Cow<'a, TlsOptions> { + #[inline] + fn from(opts: &'a TlsOptions) -> Self { + Cow::Borrowed(opts) + } +} From eaf07642c2f0a98568d277fc2d12413e3a985e6c Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Sat, 12 Jul 2025 22:56:18 +0800 Subject: [PATCH 13/19] update --- src/connect.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/connect.rs b/src/connect.rs index d556f6f58..5ce81180b 100644 --- a/src/connect.rs +++ b/src/connect.rs @@ -264,7 +264,7 @@ impl ConnectorService { let ex_data = req.ex_data(); http.set_connect_options(ex_data.tcp_connect_options().cloned()); let tls = match ex_data.tls_options() { - Some(cfg) => self.tls_builder.build(cfg)?, + Some(opts) => self.tls_builder.build(opts)?, None => self.tls.clone(), }; Ok(HttpsConnector::with_connector(http, tls)) From c98348c54cceca673de04f9af54eab78d8ca67aa Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Sun, 13 Jul 2025 07:31:37 +0800 Subject: [PATCH 14/19] fix linux build --- src/client/http/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/http/mod.rs b/src/client/http/mod.rs index 025599448..63d1d2546 100644 --- a/src/client/http/mod.rs +++ b/src/client/http/mod.rs @@ -298,7 +298,7 @@ impl ClientBuilder { http.set_connect_options(config.tcp_connect_options); http.set_nodelay(config.tcp_nodelay); #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] - http.set_tcp_user_timeout(dur); + http.set_tcp_user_timeout(config.tcp_user_timeout); }; let tls = |tls: TlsConnectorBuilder| { From 6e6706740162a188a463d2c0fb1941d46994990e Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Sun, 13 Jul 2025 07:40:52 +0800 Subject: [PATCH 15/19] Use local address test to prevent host IP changes --- tests/timeouts.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/timeouts.rs b/tests/timeouts.rs index e97f30204..0b79119c9 100644 --- a/tests/timeouts.rs +++ b/tests/timeouts.rs @@ -96,7 +96,7 @@ async fn connect_many_timeout_succeeds() { let client = wreq::Client::builder() .resolve_to_addrs( "many_addrs", - &["192.0.2.1:81".parse().unwrap(), server.addr()], + &["127.0.0.1:81".parse().unwrap(), server.addr()], ) .connect_timeout(Duration::from_millis(100)) .no_proxy() From e5aaba6a71301711dc229e61eca4d53eab998c6e Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Sun, 13 Jul 2025 08:06:22 +0800 Subject: [PATCH 16/19] fmt --- examples/emulation_firefox.rs | 2 +- examples/emulation_twitter.rs | 2 +- examples/request_with_emulation.rs | 2 +- src/client/emulation.rs | 6 +++--- src/client/http/mod.rs | 6 +++--- src/client/request.rs | 6 +++--- src/core/client/options/mod.rs | 20 ++++++++++---------- src/tls/conn/mod.rs | 18 +++++++++--------- src/tls/options.rs | 16 ++++++++-------- tests/badssl.rs | 2 +- 10 files changed, 40 insertions(+), 40 deletions(-) diff --git a/examples/emulation_firefox.rs b/examples/emulation_firefox.rs index 6bb0060e8..970624346 100644 --- a/examples/emulation_firefox.rs +++ b/examples/emulation_firefox.rs @@ -78,7 +78,7 @@ async fn main() -> wreq::Result<()> { CertificateCompressionAlgorithm::BROTLI, CertificateCompressionAlgorithm::ZSTD, ]) - .alpn_protos(&[AlpnProtocol::HTTP2, AlpnProtocol::HTTP1]) + .alpn_protocols(&[AlpnProtocol::HTTP2, AlpnProtocol::HTTP1]) .record_size_limit(0x4001) .pre_shared_key(true) .enable_ech_grease(true) diff --git a/examples/emulation_twitter.rs b/examples/emulation_twitter.rs index 8f51fab1e..1dfdef9f6 100644 --- a/examples/emulation_twitter.rs +++ b/examples/emulation_twitter.rs @@ -45,7 +45,7 @@ async fn main() -> wreq::Result<()> { "rsa_pkcs1_sha512", "rsa_pkcs1_sha1" )) - .alpn_protos(&[AlpnProtocol::HTTP2, AlpnProtocol::HTTP1]) + .alpn_protocols(&[AlpnProtocol::HTTP2, AlpnProtocol::HTTP1]) .min_tls_version(TlsVersion::TLS_1_2) .max_tls_version(TlsVersion::TLS_1_3) .build(); diff --git a/examples/request_with_emulation.rs b/examples/request_with_emulation.rs index e53b943a3..127b2d9f3 100644 --- a/examples/request_with_emulation.rs +++ b/examples/request_with_emulation.rs @@ -45,7 +45,7 @@ async fn main() -> wreq::Result<()> { "rsa_pkcs1_sha512", "rsa_pkcs1_sha1" )) - .alpn_protos(&[AlpnProtocol::HTTP2, AlpnProtocol::HTTP1]) + .alpn_protocols(&[AlpnProtocol::HTTP2, AlpnProtocol::HTTP1]) .min_tls_version(TlsVersion::TLS_1_2) .max_tls_version(TlsVersion::TLS_1_3) .build(); diff --git a/src/client/emulation.rs b/src/client/emulation.rs index cb4808eb7..40819bdfb 100644 --- a/src/client/emulation.rs +++ b/src/client/emulation.rs @@ -47,7 +47,7 @@ impl EmulationBuilder { self.emulation .transport .get_or_insert_default() - .with_tls(config); + .tls_options(config); self } @@ -60,7 +60,7 @@ impl EmulationBuilder { self.emulation .transport .get_or_insert_default() - .with_http1(config); + .http1_options(config); self } @@ -73,7 +73,7 @@ impl EmulationBuilder { self.emulation .transport .get_or_insert_default() - .with_http2(config); + .http2_options(config); self } diff --git a/src/client/http/mod.rs b/src/client/http/mod.rs index 63d1d2546..57c30c5e0 100644 --- a/src/client/http/mod.rs +++ b/src/client/http/mod.rs @@ -1330,9 +1330,9 @@ impl ClientBuilder { { self.config .transport_options - .with_http1(http1_opts) - .with_http2(http2_opts) - .with_tls(tls_opts); + .http1_options(http1_opts) + .http2_options(http2_opts) + .tls_options(tls_opts); } if let Some(headers) = headers { self = self.default_headers(headers); diff --git a/src/client/request.rs b/src/client/request.rs index 90b20cc64..7e2537a4f 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -663,9 +663,9 @@ impl RequestBuilder { { req.transport_options_mut() .get_or_insert_default() - .with_http1(http1_opts) - .with_http2(http2_opts) - .with_tls(tls_opts); + .http1_options(http1_opts) + .http2_options(http2_opts) + .tls_options(tls_opts); } if let Some(default_headers) = default_headers { self = self.headers(default_headers); diff --git a/src/core/client/options/mod.rs b/src/core/client/options/mod.rs index caee01026..843c44d58 100644 --- a/src/core/client/options/mod.rs +++ b/src/core/client/options/mod.rs @@ -13,44 +13,44 @@ use crate::tls::TlsOptions; #[must_use] #[derive(Debug, Default, Clone)] pub struct TransportOptions { - tls: Option, - http1: Option, - http2: Option, + tls_options: Option, + http1_options: Option, + http2_options: Option, } impl TransportOptions { /// Sets the HTTP/1 options configuration. #[inline] - pub fn with_http1(&mut self, config: C) -> &mut Self + pub fn http1_options(&mut self, config: C) -> &mut Self where C: Into>, { if let Some(http1) = config.into() { - self.http1 = Some(http1); + self.http1_options = Some(http1); } self } /// Sets the HTTP/2 options configuration. #[inline] - pub fn with_http2(&mut self, config: C) -> &mut Self + pub fn http2_options(&mut self, config: C) -> &mut Self where C: Into>, { if let Some(http2) = config.into() { - self.http2 = Some(http2); + self.http2_options = Some(http2); } self } /// Sets the TLS options configuration. #[inline] - pub fn with_tls(&mut self, config: C) -> &mut Self + pub fn tls_options(&mut self, config: C) -> &mut Self where C: Into>, { if let Some(tls) = config.into() { - self.tls = Some(tls); + self.tls_options = Some(tls); } self } @@ -64,6 +64,6 @@ impl TransportOptions { Option, Option, ) { - (self.tls, self.http1, self.http2) + (self.tls_options, self.http1_options, self.http2_options) } } diff --git a/src/tls/conn/mod.rs b/src/tls/conn/mod.rs index 3f938c996..e4c8722cb 100644 --- a/src/tls/conn/mod.rs +++ b/src/tls/conn/mod.rs @@ -63,7 +63,7 @@ pub struct HandshakeConfig { enable_ech_grease: bool, verify_hostname: bool, tls_sni: bool, - alps_protos: Option, + alps_protocols: Option, alps_use_new_codepoint: bool, random_aes_hw_override: bool, } @@ -94,8 +94,8 @@ impl HandshakeConfigBuilder { } /// Sets ALPS protocol. - pub fn alps_protos(mut self, protos: Option) -> Self { - self.settings.alps_protos = protos; + pub fn alps_protocols(mut self, protos: Option) -> Self { + self.settings.alps_protocols = protos; self } @@ -133,7 +133,7 @@ impl Default for HandshakeConfig { enable_ech_grease: false, verify_hostname: true, tls_sni: true, - alps_protos: None, + alps_protocols: None, alps_use_new_codepoint: false, random_aes_hw_override: false, } @@ -221,7 +221,7 @@ impl Inner { // Set ALPS protos cfg.set_alps_protos( - self.config.alps_protos.as_ref(), + self.config.alps_protocols.as_ref(), self.config.alps_use_new_codepoint, )?; @@ -361,10 +361,10 @@ impl TlsConnectorBuilder { // Replace the default configuration with the provided one let max_tls_version = opts.max_tls_version.or(self.max_version); let min_tls_version = opts.min_tls_version.or(self.min_version); - let alpn_protos = self + let alpn_protocols = self .alpn_protocol .map(|p| p.encode()) - .or(opts.alpn_protos.clone()); + .or(opts.alpn_protocols.clone()); // Create the SslConnector with the provided options let mut connector = SslConnector::no_default_verify_builder(SslMethod::tls_client()) @@ -431,7 +431,7 @@ impl TlsConnectorBuilder { set_option!(opts, permute_extensions, connector, set_permute_extensions); // Set TLS ALPN protocols - set_option_ref_try!(alpn_protos, connector, set_alpn_protos); + set_option_ref_try!(alpn_protocols, connector, set_alpn_protos); // Set TLS curves list set_option_ref_try!(opts, curves_list, connector, set_curves_list); @@ -484,7 +484,7 @@ impl TlsConnectorBuilder { // Create the `HandshakeConfig` with the default session cache capacity. let config = HandshakeConfig::builder() .no_ticket(opts.psk_skip_session_ticket) - .alps_protos(opts.alps_protos.clone()) + .alps_protocols(opts.alps_protocols.clone()) .alps_use_new_codepoint(opts.alps_use_new_codepoint) .enable_ech_grease(opts.enable_ech_grease) .tls_sni(self.tls_sni) diff --git a/src/tls/options.rs b/src/tls/options.rs index aea781559..c31ca216b 100644 --- a/src/tls/options.rs +++ b/src/tls/options.rs @@ -20,8 +20,8 @@ pub struct TlsOptionsBuilder { /// and various security features. #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct TlsOptions { - pub(crate) alpn_protos: Option, - pub(crate) alps_protos: Option, + pub(crate) alpn_protocols: Option, + pub(crate) alps_protocols: Option, pub(crate) alps_use_new_codepoint: bool, pub(crate) session_ticket: bool, pub(crate) min_tls_version: Option, @@ -56,20 +56,20 @@ impl TlsOptionsBuilder { } /// Sets the ALPN protocols to use. - pub fn alpn_protos<'a, I>(mut self, alpn: I) -> Self + pub fn alpn_protocols<'a, I>(mut self, alpn: I) -> Self where I: IntoIterator, { - self.config.alpn_protos = Some(AlpnProtocol::encode_sequence(alpn)); + self.config.alpn_protocols = Some(AlpnProtocol::encode_sequence(alpn)); self } /// Sets the ALPS protocols to use. - pub fn alps_protos<'a, I>(mut self, alps: I) -> Self + pub fn alps_protocols<'a, I>(mut self, alps: I) -> Self where I: IntoIterator, { - self.config.alps_protos = Some(AlpsProtocol::encode_sequence(alps)); + self.config.alps_protocols = Some(AlpsProtocol::encode_sequence(alps)); self } @@ -272,11 +272,11 @@ impl TlsOptions { impl Default for TlsOptions { fn default() -> Self { TlsOptions { - alpn_protos: Some(AlpnProtocol::encode_sequence(&[ + alpn_protocols: Some(AlpnProtocol::encode_sequence(&[ AlpnProtocol::HTTP2, AlpnProtocol::HTTP1, ])), - alps_protos: None, + alps_protocols: None, alps_use_new_codepoint: false, session_ticket: true, min_tls_version: None, diff --git a/tests/badssl.rs b/tests/badssl.rs index 1dbdcb5d8..bc762b4e9 100644 --- a/tests/badssl.rs +++ b/tests/badssl.rs @@ -126,7 +126,7 @@ async fn test_alps_new_endpoint() -> wreq::Result<()> { let tls_options = TlsOptions::builder() .min_tls_version(TlsVersion::TLS_1_2) .max_tls_version(TlsVersion::TLS_1_3) - .alps_protos(&[AlpsProtocol::HTTP2]) + .alps_protocols(&[AlpsProtocol::HTTP2]) .alps_use_new_codepoint(true) .build(); From 2e9c49cfaac02ea0755424d33559d7f4b8fc66ed Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Sun, 13 Jul 2025 08:16:01 +0800 Subject: [PATCH 17/19] fmt --- src/tls/conn/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tls/conn/mod.rs b/src/tls/conn/mod.rs index e4c8722cb..bcd297697 100644 --- a/src/tls/conn/mod.rs +++ b/src/tls/conn/mod.rs @@ -363,8 +363,7 @@ impl TlsConnectorBuilder { let min_tls_version = opts.min_tls_version.or(self.min_version); let alpn_protocols = self .alpn_protocol - .map(|p| p.encode()) - .or(opts.alpn_protocols.clone()); + .map_or(opts.alpn_protocols.clone(), |p| Some(p.encode())); // Create the SslConnector with the provided options let mut connector = SslConnector::no_default_verify_builder(SslMethod::tls_client()) From 2ac6d1f4994edf683e5e3de5d0e00b43931f1efd Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Sun, 13 Jul 2025 10:05:07 +0800 Subject: [PATCH 18/19] fmt --- src/tls/x509/mod.rs | 1 + src/tls/x509/{store => }/parser.rs | 30 +++-- src/tls/x509/{store/mod.rs => store.rs} | 143 ++++++++++-------------- 3 files changed, 72 insertions(+), 102 deletions(-) rename src/tls/x509/{store => }/parser.rs (63%) rename src/tls/x509/{store/mod.rs => store.rs} (69%) diff --git a/src/tls/x509/mod.rs b/src/tls/x509/mod.rs index 6d24700a2..b507ef794 100644 --- a/src/tls/x509/mod.rs +++ b/src/tls/x509/mod.rs @@ -1,4 +1,5 @@ mod identity; +mod parser; mod store; use boring2::x509::X509; diff --git a/src/tls/x509/store/parser.rs b/src/tls/x509/parser.rs similarity index 63% rename from src/tls/x509/store/parser.rs rename to src/tls/x509/parser.rs index bb8e6e1f0..b12c31ffe 100644 --- a/src/tls/x509/store/parser.rs +++ b/src/tls/x509/parser.rs @@ -1,36 +1,34 @@ -use std::sync::Arc; +use boring2::x509::store::{X509Store, X509StoreBuilder}; -use boring2::x509::store::X509StoreBuilder; +use super::{Certificate, CertificateInput}; +use crate::{Error, Result}; -use super::{CertStore, Certificate, CertificateInput}; -use crate::Error; - -pub fn parse_certs_with_iter<'c, I>( +pub fn parse_certs<'c, I>( certs: I, parser: fn(&'c [u8]) -> crate::Result, -) -> crate::Result +) -> Result where I: IntoIterator, I::Item: Into>, { let mut store = X509StoreBuilder::new().map_err(Error::tls)?; let certs = filter_map_certs(certs, parser); - process_certs_with_builder(certs.into_iter(), &mut store)?; - Ok(CertStore(Arc::new(store.build()))) + process_certs(certs.into_iter(), &mut store)?; + Ok(store.build()) } -pub fn parse_certs_with_stack(certs: C, x509: F) -> crate::Result +pub fn parse_certs_with_stack(certs: C, parse: F) -> Result where C: AsRef<[u8]>, - F: Fn(C) -> crate::Result>, + F: Fn(C) -> Result>, { let mut store = X509StoreBuilder::new().map_err(Error::tls)?; - let certs = x509(certs)?; - process_certs_with_builder(certs.into_iter(), &mut store)?; - Ok(CertStore(Arc::new(store.build()))) + let certs = parse(certs)?; + process_certs(certs.into_iter(), &mut store)?; + Ok(store.build()) } -pub fn process_certs_with_builder(iter: I, store: &mut X509StoreBuilder) -> crate::Result<()> +pub fn process_certs(iter: I, store: &mut X509StoreBuilder) -> Result<()> where I: Iterator, { @@ -54,7 +52,7 @@ where pub fn filter_map_certs<'c, I>( certs: I, - parser: fn(&'c [u8]) -> crate::Result, + parser: fn(&'c [u8]) -> Result, ) -> impl Iterator where I: IntoIterator, diff --git a/src/tls/x509/store/mod.rs b/src/tls/x509/store.rs similarity index 69% rename from src/tls/x509/store/mod.rs rename to src/tls/x509/store.rs index 212331f5f..1aa03f6c1 100644 --- a/src/tls/x509/store/mod.rs +++ b/src/tls/x509/store.rs @@ -1,17 +1,15 @@ -mod parser; - -use std::{fmt::Debug, path::Path, sync::Arc}; +use std::sync::Arc; use boring2::{ ssl::SslConnectorBuilder, x509::store::{X509Store, X509StoreBuilder}, }; -use parser::{ - filter_map_certs, parse_certs_with_iter, parse_certs_with_stack, process_certs_with_builder, -}; -use super::{Certificate, CertificateInput}; -use crate::Error; +use super::{ + Certificate, CertificateInput, + parser::{filter_map_certs, parse_certs, parse_certs_with_stack, process_certs}, +}; +use crate::{Error, Result}; /// A builder for constructing a `CertStore`. /// @@ -19,9 +17,11 @@ use crate::Error; /// and to set default paths for the certificate store. Once all desired certificates /// have been added, the `build` method can be used to create the `CertStore`. pub struct CertStoreBuilder { - builder: crate::Result, + builder: Result, } +// ====== impl CertStoreBuilder ====== + impl CertStoreBuilder { /// Adds a DER-encoded certificate to the certificate store. #[inline] @@ -68,7 +68,7 @@ impl CertStoreBuilder { { if let Ok(ref mut builder) = self.builder { let result = Certificate::stack_from_pem(certs.as_ref()) - .and_then(|certs| process_certs_with_builder(certs.into_iter(), builder)); + .and_then(|certs| process_certs(certs.into_iter(), builder)); if let Err(err) = result { self.builder = Err(err); @@ -77,23 +77,6 @@ impl CertStoreBuilder { self } - /// Adds PEM-encoded certificates from a file to the certificate store. - /// - /// This method reads the file at the specified path, expecting it to contain a PEM-encoded - /// certificate stack, and then adds the certificates to the store. - pub fn add_file_pem_certs

(mut self, path: P) -> Self - where - P: AsRef, - { - match std::fs::read(path) { - Ok(data) => return self.add_stack_pem_certs(data), - Err(err) => { - self.builder = Err(Error::builder(err)); - } - } - self - } - /// Load certificates from their default locations. /// /// These locations are read from the `SSL_CERT_FILE` and `SSL_CERT_DIR` @@ -112,15 +95,20 @@ impl CertStoreBuilder { /// /// This method finalizes the builder and constructs the `CertStore` /// containing all the added certificates. - pub fn build(self) -> crate::Result { - let builder = self.builder?; - Ok(CertStore(Arc::new(builder.build()))) + #[inline] + pub fn build(self) -> Result { + self.builder + .map(X509StoreBuilder::build) + .map(Arc::new) + .map(CertStore) } +} +impl CertStoreBuilder { fn parse_cert<'c, C, P>(mut self, cert: C, parser: P) -> Self where C: Into>, - P: Fn(&'c [u8]) -> crate::Result, + P: Fn(&'c [u8]) -> Result, { if let Ok(ref mut builder) = self.builder { let input = cert.into(); @@ -135,18 +123,14 @@ impl CertStoreBuilder { self } - fn parse_certs<'c, I>( - mut self, - certs: I, - parser: fn(&'c [u8]) -> crate::Result, - ) -> Self + fn parse_certs<'c, I>(mut self, certs: I, parser: fn(&'c [u8]) -> Result) -> Self where I: IntoIterator, I::Item: Into>, { if let Ok(ref mut builder) = self.builder { let certs = filter_map_certs(certs, parser); - if let Err(err) = process_certs_with_builder(certs, builder) { + if let Err(err) = process_certs(certs, builder) { self.builder = Err(err); } } @@ -158,37 +142,8 @@ impl CertStoreBuilder { #[derive(Clone)] pub struct CertStore(Arc); -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") - }); - - #[cfg(not(feature = "webpki-roots"))] - { - CertStore::builder() - .set_default_paths() - .build() - .expect("failed to load default cert store") - } +// ====== impl CertStore ====== - #[cfg(feature = "webpki-roots")] - LOAD_CERTS.clone() - } -} - -impl Debug for CertStore { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("CertStore").finish() - } -} - -/// ====== impl CertStore ====== impl CertStore { /// Creates a new `CertStoreBuilder`. #[inline] @@ -200,45 +155,37 @@ impl CertStore { /// Creates a new `CertStore` from a collection of DER-encoded certificates. #[inline] - pub fn from_der_certs<'c, C>(certs: C) -> crate::Result + pub fn from_der_certs<'c, C>(certs: C) -> Result where C: IntoIterator, C::Item: Into>, { - parse_certs_with_iter(certs, Certificate::from_der) + parse_certs(certs, Certificate::from_der) + .map(Arc::new) + .map(CertStore) } /// Creates a new `CertStore` from a collection of PEM-encoded certificates. #[inline] - pub fn from_pem_certs<'c, C>(certs: C) -> crate::Result + pub fn from_pem_certs<'c, C>(certs: C) -> Result where C: IntoIterator, C::Item: Into>, { - parse_certs_with_iter(certs, Certificate::from_pem) + parse_certs(certs, Certificate::from_pem) + .map(Arc::new) + .map(CertStore) } /// Creates a new `CertStore` from a PEM-encoded certificate stack. #[inline] - pub fn from_pem_stack(certs: C) -> crate::Result + pub fn from_pem_stack(certs: C) -> Result where C: AsRef<[u8]>, { parse_certs_with_stack(certs, Certificate::stack_from_pem) - } - - /// Creates a new `CertStore` from a PEM-encoded certificate file. - /// - /// This method reads the file at the specified path, expecting it to contain a PEM-encoded - /// certificate stack, and then constructs a `CertStore` from it. - #[inline] - pub fn from_pem_file

(path: P) -> crate::Result - where - P: AsRef, - { - std::fs::read(path) - .map_err(Error::builder) - .and_then(Self::from_pem_stack) + .map(Arc::new) + .map(CertStore) } } @@ -248,3 +195,27 @@ impl CertStore { tls.set_cert_store_ref(&self.0); } } + +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") + }); + + #[cfg(not(feature = "webpki-roots"))] + { + CertStore::builder() + .set_default_paths() + .build() + .expect("failed to load default cert store") + } + + #[cfg(feature = "webpki-roots")] + LOAD_CERTS.clone() + } +} From edf2629cc0f8e4643a66bdbdc4d64bc9c914c6c1 Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Sun, 13 Jul 2025 10:05:49 +0800 Subject: [PATCH 19/19] fmt --- src/tls/x509/store.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/tls/x509/store.rs b/src/tls/x509/store.rs index 1aa03f6c1..483dde3f8 100644 --- a/src/tls/x509/store.rs +++ b/src/tls/x509/store.rs @@ -12,10 +12,6 @@ use super::{ use crate::{Error, Result}; /// A builder for constructing a `CertStore`. -/// -/// This builder provides methods to add certificates to the store from various formats, -/// and to set default paths for the certificate store. Once all desired certificates -/// have been added, the `build` method can be used to create the `CertStore`. pub struct CertStoreBuilder { builder: Result, }