99use std:: collections:: { hash_map, HashMap , VecDeque } ;
1010use std:: sync:: { Arc , Mutex } ;
1111
12+ #[ cfg( feature = "tokio-rustls" ) ]
13+ use crate :: connection:: tls_config:: { TlsConfig , TlsConfigBuilder } ;
1214use crate :: connection:: AsyncConnection ;
1315use crate :: request:: { OwnedConnectionParams as ConnectionKey , ParsedRequest } ;
1416use crate :: { Error , Request , Response } ;
1517
18+ #[ derive( Clone ) ]
19+ pub ( crate ) struct ClientConfig {
20+ #[ cfg( feature = "tokio-rustls" ) ]
21+ pub ( crate ) tls : Option < TlsConfig > ,
22+ }
23+
24+ pub struct ClientBuilder {
25+ capacity : usize ,
26+ #[ cfg( feature = "tokio-rustls" ) ]
27+ tls_config : Option < TlsConfigBuilder > ,
28+ }
29+
30+ /// Builder for configuring a `Client` with custom settings.
31+ ///
32+ /// # Example
33+ ///
34+ /// ```no_run
35+ /// # async fn example() -> Result<(), bitreq::Error> {
36+ /// use bitreq::{Client, RequestExt};
37+ ///
38+ /// let client = Client::builder().with_capacity(20).build()?;
39+ ///
40+ /// let response = bitreq::get("https://example.com")
41+ /// .send_async_with_client(&client)
42+ /// .await?;
43+ /// # Ok(())
44+ /// # }
45+ /// ```
46+ impl ClientBuilder {
47+ /// Creates a new `ClientBuilder` with a default pool capacity of 10.
48+ pub fn new ( ) -> Self {
49+ Self {
50+ capacity : 10 ,
51+ #[ cfg( feature = "tokio-rustls" ) ]
52+ tls_config : None ,
53+ }
54+ }
55+
56+ /// Sets the maximum number of connections to keep in the pool.
57+ pub fn with_capacity ( mut self , capacity : usize ) -> Self {
58+ self . capacity = capacity;
59+ self
60+ }
61+
62+ #[ cfg( feature = "tokio-rustls" ) ]
63+ /// Builds the `Client` with the configured settings.
64+ pub fn build ( self ) -> Result < Client , Error > {
65+ let build_config = if let Some ( builder) = self . tls_config {
66+ let tls_config = builder. build ( ) ?;
67+ Some ( ClientConfig { tls : Some ( tls_config) } )
68+ } else {
69+ None
70+ } ;
71+ let client_config = build_config. map ( Arc :: new) ;
72+
73+ Ok ( Client {
74+ r#async : Arc :: new ( Mutex :: new ( ClientImpl {
75+ connections : HashMap :: new ( ) ,
76+ lru_order : VecDeque :: new ( ) ,
77+ capacity : self . capacity ,
78+ client_config,
79+ } ) ) ,
80+ } )
81+ }
82+
83+ /// Builds the `Client` with the configured settings.
84+ #[ cfg( not( feature = "tokio-rustls" ) ) ]
85+ pub fn build ( self ) -> Result < Client , Error > {
86+ Ok ( Client {
87+ r#async : Arc :: new ( Mutex :: new ( ClientImpl {
88+ connections : HashMap :: new ( ) ,
89+ lru_order : VecDeque :: new ( ) ,
90+ capacity : self . capacity ,
91+ client_config : None ,
92+ } ) ) ,
93+ } )
94+ }
95+
96+ /// Adds a custom DER-encoded root certificate for TLS verification.
97+ /// The certificate must be provided in DER format. This method accepts any type
98+ /// that can be converted into a `Vec<u8>`.
99+ /// The certificate is appended to the default trust store rather than replacing it.
100+ /// The trust store used depends on the TLS backend: system certificates for native-tls,
101+ /// Mozilla's root certificates(rustls-webpki) and/or system certificates(rustls-native-certs) for rustls.
102+ ///
103+ /// # Example
104+ ///
105+ /// ```no_run
106+ /// # use bitreq::Client;
107+ /// # async fn example() -> Result<(), bitreq::Error> {
108+ /// let client = Client::builder()
109+ /// .with_root_certificate(include_bytes!("../tests/test_cert.der"))?
110+ /// .build()?;
111+ /// # Ok(())
112+ /// # }
113+ /// ```
114+ #[ cfg( feature = "tokio-rustls" ) ]
115+ pub fn with_root_certificate < T : Into < Vec < u8 > > > ( mut self , cert_der : T ) -> Result < Self , Error > {
116+ let cert_der = cert_der. into ( ) ;
117+ if let Some ( ref mut tls_config) = self . tls_config {
118+ tls_config. append_certificate ( cert_der) ?;
119+
120+ return Ok ( self ) ;
121+ }
122+
123+ self . tls_config = Some ( TlsConfigBuilder :: new ( Some ( cert_der) ) ?) ;
124+ Ok ( self )
125+ }
126+
127+ /// Disables default root certificates for TLS connections.
128+ /// Returns [`Error::InvalidTlsConfig`] if TLS has not been configured.
129+ #[ cfg( feature = "tokio-rustls" ) ]
130+ pub fn disable_default_certificates ( mut self ) -> Result < Self , Error > {
131+ match self . tls_config {
132+ Some ( ref mut tls_config) => tls_config. disable_default_certificates ( ) ?,
133+ None => return Err ( Error :: InvalidTlsConfig ) ,
134+ } ;
135+
136+ Ok ( self )
137+ }
138+ }
139+
140+ impl Default for ClientBuilder {
141+ fn default ( ) -> Self { Self :: new ( ) }
142+ }
143+
16144/// A client that caches connections for reuse.
17145///
18146/// The client maintains a pool of up to `capacity` connections, evicting
@@ -39,10 +167,11 @@ struct ClientImpl<T> {
39167 connections : HashMap < ConnectionKey , Arc < T > > ,
40168 lru_order : VecDeque < ConnectionKey > ,
41169 capacity : usize ,
170+ client_config : Option < Arc < ClientConfig > > ,
42171}
43172
44173impl Client {
45- /// Creates a new `Client` with the specified connection cache capacity.
174+ /// Creates a new `Client` with the specified connection pool capacity.
46175 ///
47176 /// # Arguments
48177 ///
@@ -54,10 +183,14 @@ impl Client {
54183 connections : HashMap :: new ( ) ,
55184 lru_order : VecDeque :: new ( ) ,
56185 capacity,
186+ client_config : None ,
57187 } ) ) ,
58188 }
59189 }
60190
191+ /// Create a builder for a client
192+ pub fn builder ( ) -> ClientBuilder { ClientBuilder :: new ( ) }
193+
61194 /// Sends a request asynchronously using a cached connection if available.
62195 pub async fn send_async ( & self , request : Request ) -> Result < Response , Error > {
63196 let parsed_request = ParsedRequest :: new ( request) ?;
@@ -77,7 +210,13 @@ impl Client {
77210 let conn = if let Some ( conn) = conn_opt {
78211 conn
79212 } else {
80- let connection = AsyncConnection :: new ( key, parsed_request. timeout_at ) . await ?;
213+ let client_config = {
214+ let state = self . r#async . lock ( ) . unwrap ( ) ;
215+ state. client_config . as_ref ( ) . map ( Arc :: clone)
216+ } ;
217+
218+ let connection =
219+ AsyncConnection :: new ( key, parsed_request. timeout_at , client_config) . await ?;
81220 let connection = Arc :: new ( connection) ;
82221
83222 let mut state = self . r#async . lock ( ) . unwrap ( ) ;
0 commit comments