1515
1616use crate :: commons:: RetrySettings ;
1717use crate :: error:: {
18+ EndpointValidationError ,
1819 Error :: { ClientErrorResponse , NotFound , ServerErrorResponse } ,
1920 ErrorDetails ,
2021} ;
2122use crate :: responses;
2223use backtrace:: Backtrace ;
2324use log:: trace;
24- use reqwest:: { Client as HttpClient , StatusCode , header:: HeaderMap } ;
25+ use reqwest:: { Client as HttpClient , StatusCode , header:: HeaderMap , redirect } ;
2526use serde:: Serialize ;
2627use serde:: de:: DeserializeOwned ;
2728use std:: fmt:: Display ;
@@ -44,7 +45,11 @@ pub type Result<T> = result::Result<T, HttpClientError>;
4445/// let endpoint = "http://localhost:15672/api";
4546/// let username = "username";
4647/// let password = "password";
47- /// let rc = ClientBuilder::new().with_endpoint(&endpoint).with_basic_auth_credentials(&username, &password).build();
48+ /// let rc = ClientBuilder::new()
49+ /// .with_endpoint(&endpoint)
50+ /// .with_basic_auth_credentials(&username, &password)
51+ /// .build()
52+ /// .unwrap();
4853/// // list cluster nodes
4954/// rc.list_nodes().await;
5055/// // list user connections
@@ -60,6 +65,7 @@ pub struct ClientBuilder<E = &'static str, U = &'static str, P = &'static str> {
6065 retry_settings : RetrySettings ,
6166 connect_timeout : Option < Duration > ,
6267 request_timeout : Option < Duration > ,
68+ redirect_policy : Option < redirect:: Policy > ,
6369}
6470
6571impl Default for ClientBuilder {
@@ -85,6 +91,7 @@ impl ClientBuilder {
8591 retry_settings : RetrySettings :: default ( ) ,
8692 connect_timeout : None ,
8793 request_timeout : None ,
94+ redirect_policy : None ,
8895 }
8996 }
9097}
@@ -96,14 +103,16 @@ where
96103 P : Display ,
97104{
98105 /// Applies recommended default settings: 60 second request timeout,
99- /// 3 retry attempts with 1 second delay between retries.
106+ /// 3 retry attempts with 1 second delay between retries,
107+ /// and no redirects (the RabbitMQ HTTP API does not use redirects).
100108 pub fn with_recommended_defaults ( self ) -> Self {
101109 Self {
102110 request_timeout : Some ( Duration :: from_secs ( 60 ) ) ,
103111 retry_settings : RetrySettings {
104112 max_attempts : 3 ,
105113 delay_ms : 1000 ,
106114 } ,
115+ redirect_policy : Some ( redirect:: Policy :: none ( ) ) ,
107116 ..self
108117 }
109118 }
@@ -126,6 +135,7 @@ where
126135 retry_settings : self . retry_settings ,
127136 connect_timeout : self . connect_timeout ,
128137 request_timeout : self . request_timeout ,
138+ redirect_policy : self . redirect_policy ,
129139 }
130140 }
131141
@@ -145,6 +155,7 @@ where
145155 retry_settings : self . retry_settings ,
146156 connect_timeout : self . connect_timeout ,
147157 request_timeout : self . request_timeout ,
158+ redirect_policy : self . redirect_policy ,
148159 }
149160 }
150161
@@ -194,7 +205,8 @@ where
194205 /// .with_endpoint("http://localhost:15672/api")
195206 /// .with_basic_auth_credentials("user", "password")
196207 /// .with_request_timeout(Duration::from_secs(30))
197- /// .build();
208+ /// .build()
209+ /// .unwrap();
198210 /// ```
199211 pub fn with_request_timeout ( self , timeout : Duration ) -> Self {
200212 ClientBuilder {
@@ -213,10 +225,34 @@ where
213225 }
214226 }
215227
228+ /// Sets the redirect policy for HTTP requests.
229+ ///
230+ /// By default, `reqwest` follows up to 10 redirects. Use
231+ /// `reqwest::redirect::Policy::none()` to disable redirects entirely,
232+ /// which is recommended when the endpoint comes from a potentially untrusted source.
233+ ///
234+ /// This setting is ignored if a custom HTTP client is used
235+ /// via [`Self::with_client`].
236+ pub fn with_redirect_policy ( self , policy : redirect:: Policy ) -> Self {
237+ ClientBuilder {
238+ redirect_policy : Some ( policy) ,
239+ ..self
240+ }
241+ }
242+
216243 /// Builds and returns a configured `Client`.
217244 ///
245+ /// Returns an error if the endpoint scheme is not `http` or `https`.
246+ ///
218247 /// This consumes the `ClientBuilder`.
219- pub fn build ( self ) -> Client < E , U , P > {
248+ pub fn build ( self ) -> result:: Result < Client < E , U , P > , EndpointValidationError > {
249+ let endpoint_str = self . endpoint . to_string ( ) ;
250+ if !endpoint_str. starts_with ( "http://" ) && !endpoint_str. starts_with ( "https://" ) {
251+ return Err ( EndpointValidationError :: UnsupportedScheme {
252+ endpoint : endpoint_str,
253+ } ) ;
254+ }
255+
220256 let client = match self . client {
221257 Some ( c) => c,
222258 None => {
@@ -227,17 +263,22 @@ where
227263 if let Some ( timeout) = self . request_timeout {
228264 builder = builder. timeout ( timeout) ;
229265 }
230- builder. build ( ) . unwrap ( )
266+ if let Some ( policy) = self . redirect_policy {
267+ builder = builder. redirect ( policy) ;
268+ }
269+ builder
270+ . build ( )
271+ . map_err ( |e| EndpointValidationError :: ClientBuildError { source : e } ) ?
231272 }
232273 } ;
233274
234- Client :: from_http_client_with_retry (
275+ Ok ( Client :: from_http_client_with_retry (
235276 client,
236277 self . endpoint ,
237278 self . username ,
238279 self . password ,
239280 self . retry_settings ,
240- )
281+ ) )
241282 }
242283}
243284
0 commit comments