unistore_http/
config.rs

1//! HTTP 客户端配置
2//!
3//! 职责:
4//! - 定义 HTTP 客户端的配置选项
5//! - 提供合理的默认值
6//! - 支持 Builder 模式构建配置
7
8use std::time::Duration;
9
10/// HTTP 客户端配置
11#[derive(Debug, Clone)]
12pub struct HttpClientConfig {
13    /// 连接超时(默认 10s)
14    pub connect_timeout: Duration,
15
16    /// 请求超时(默认 30s)
17    pub request_timeout: Duration,
18
19    /// 用户代理
20    pub user_agent: String,
21
22    /// 最大重试次数(默认 3)
23    pub max_retries: u32,
24
25    /// 重试间隔策略
26    pub retry_strategy: RetryStrategy,
27
28    /// 代理配置
29    pub proxy: Option<ProxyConfig>,
30
31    /// 是否跟随重定向(默认 true)
32    pub follow_redirects: bool,
33
34    /// 最大重定向次数(默认 10)
35    pub max_redirects: u32,
36
37    /// 是否接受无效证书(默认 false,生产环境勿启用)
38    pub accept_invalid_certs: bool,
39}
40
41impl Default for HttpClientConfig {
42    fn default() -> Self {
43        Self {
44            connect_timeout: Duration::from_secs(10),
45            request_timeout: Duration::from_secs(30),
46            user_agent: format!("unistore-http/{}", env!("CARGO_PKG_VERSION")),
47            max_retries: 3,
48            retry_strategy: RetryStrategy::ExponentialBackoff {
49                initial: Duration::from_millis(100),
50                max: Duration::from_secs(5),
51            },
52            proxy: None,
53            follow_redirects: true,
54            max_redirects: 10,
55            accept_invalid_certs: false,
56        }
57    }
58}
59
60impl HttpClientConfig {
61    /// 创建默认配置
62    pub fn new() -> Self {
63        Self::default()
64    }
65
66    /// 创建用于快速请求的配置(短超时)
67    pub fn quick() -> Self {
68        Self {
69            connect_timeout: Duration::from_secs(5),
70            request_timeout: Duration::from_secs(10),
71            max_retries: 1,
72            retry_strategy: RetryStrategy::None,
73            ..Self::default()
74        }
75    }
76
77    /// 创建用于长时间请求的配置(长超时)
78    pub fn long_running() -> Self {
79        Self {
80            connect_timeout: Duration::from_secs(30),
81            request_timeout: Duration::from_secs(300), // 5 分钟
82            max_retries: 5,
83            ..Self::default()
84        }
85    }
86
87    /// 创建无重试配置
88    pub fn no_retry() -> Self {
89        Self {
90            max_retries: 0,
91            retry_strategy: RetryStrategy::None,
92            ..Self::default()
93        }
94    }
95}
96
97/// 重试策略
98#[derive(Debug, Clone)]
99pub enum RetryStrategy {
100    /// 不重试
101    None,
102
103    /// 固定间隔重试
104    Fixed(Duration),
105
106    /// 指数退避重试
107    ExponentialBackoff {
108        /// 初始间隔
109        initial: Duration,
110        /// 最大间隔
111        max: Duration,
112    },
113}
114
115impl RetryStrategy {
116    /// 计算第 n 次重试的延迟时间
117    ///
118    /// # Arguments
119    /// * `attempt` - 当前重试次数(从 1 开始)
120    pub fn delay_for_attempt(&self, attempt: u32) -> Option<Duration> {
121        match self {
122            Self::None => None,
123            Self::Fixed(duration) => Some(*duration),
124            Self::ExponentialBackoff { initial, max } => {
125                let delay = initial.saturating_mul(2u32.saturating_pow(attempt.saturating_sub(1)));
126                Some(delay.min(*max))
127            }
128        }
129    }
130}
131
132/// 代理配置
133#[derive(Debug, Clone)]
134pub struct ProxyConfig {
135    /// HTTP 代理地址
136    pub http: Option<String>,
137
138    /// HTTPS 代理地址
139    pub https: Option<String>,
140
141    /// 不使用代理的地址列表
142    pub no_proxy: Vec<String>,
143}
144
145impl ProxyConfig {
146    /// 创建 HTTP 代理配置
147    pub fn http(url: impl Into<String>) -> Self {
148        Self {
149            http: Some(url.into()),
150            https: None,
151            no_proxy: Vec::new(),
152        }
153    }
154
155    /// 创建 HTTPS 代理配置
156    pub fn https(url: impl Into<String>) -> Self {
157        Self {
158            http: None,
159            https: Some(url.into()),
160            no_proxy: Vec::new(),
161        }
162    }
163
164    /// 创建同时用于 HTTP 和 HTTPS 的代理配置
165    pub fn all(url: impl Into<String>) -> Self {
166        let url = url.into();
167        Self {
168            http: Some(url.clone()),
169            https: Some(url),
170            no_proxy: Vec::new(),
171        }
172    }
173
174    /// 添加不使用代理的地址
175    pub fn with_no_proxy(mut self, patterns: Vec<String>) -> Self {
176        self.no_proxy = patterns;
177        self
178    }
179}
180
181/// HTTP 客户端配置构建器
182#[derive(Debug, Clone, Default)]
183pub struct HttpClientConfigBuilder {
184    config: HttpClientConfig,
185}
186
187impl HttpClientConfigBuilder {
188    /// 创建新的构建器
189    pub fn new() -> Self {
190        Self::default()
191    }
192
193    /// 设置连接超时
194    pub fn connect_timeout(mut self, timeout: Duration) -> Self {
195        self.config.connect_timeout = timeout;
196        self
197    }
198
199    /// 设置请求超时
200    pub fn request_timeout(mut self, timeout: Duration) -> Self {
201        self.config.request_timeout = timeout;
202        self
203    }
204
205    /// 设置统一超时(连接和请求使用相同值)
206    pub fn timeout(mut self, timeout: Duration) -> Self {
207        self.config.connect_timeout = timeout;
208        self.config.request_timeout = timeout;
209        self
210    }
211
212    /// 设置用户代理
213    pub fn user_agent(mut self, ua: impl Into<String>) -> Self {
214        self.config.user_agent = ua.into();
215        self
216    }
217
218    /// 设置最大重试次数
219    pub fn max_retries(mut self, retries: u32) -> Self {
220        self.config.max_retries = retries;
221        self
222    }
223
224    /// 设置重试策略
225    pub fn retry_strategy(mut self, strategy: RetryStrategy) -> Self {
226        self.config.retry_strategy = strategy;
227        self
228    }
229
230    /// 禁用重试
231    pub fn no_retry(mut self) -> Self {
232        self.config.max_retries = 0;
233        self.config.retry_strategy = RetryStrategy::None;
234        self
235    }
236
237    /// 设置 HTTP 代理
238    pub fn proxy_http(mut self, url: impl Into<String>) -> Self {
239        let proxy = self.config.proxy.get_or_insert_with(|| ProxyConfig {
240            http: None,
241            https: None,
242            no_proxy: Vec::new(),
243        });
244        proxy.http = Some(url.into());
245        self
246    }
247
248    /// 设置 HTTPS 代理
249    pub fn proxy_https(mut self, url: impl Into<String>) -> Self {
250        let proxy = self.config.proxy.get_or_insert_with(|| ProxyConfig {
251            http: None,
252            https: None,
253            no_proxy: Vec::new(),
254        });
255        proxy.https = Some(url.into());
256        self
257    }
258
259    /// 设置所有协议使用同一代理
260    pub fn proxy_all(mut self, url: impl Into<String>) -> Self {
261        let url = url.into();
262        self.config.proxy = Some(ProxyConfig {
263            http: Some(url.clone()),
264            https: Some(url),
265            no_proxy: Vec::new(),
266        });
267        self
268    }
269
270    /// 是否跟随重定向
271    pub fn follow_redirects(mut self, follow: bool) -> Self {
272        self.config.follow_redirects = follow;
273        self
274    }
275
276    /// 设置最大重定向次数
277    pub fn max_redirects(mut self, max: u32) -> Self {
278        self.config.max_redirects = max;
279        self
280    }
281
282    /// 是否接受无效证书(危险:仅用于测试)
283    pub fn accept_invalid_certs(mut self, accept: bool) -> Self {
284        self.config.accept_invalid_certs = accept;
285        self
286    }
287
288    /// 构建配置
289    pub fn build(self) -> HttpClientConfig {
290        self.config
291    }
292
293    /// 构建客户端(快捷方法)
294    pub fn build_client(self) -> Result<super::HttpClient, super::HttpError> {
295        super::HttpClient::with_config(self.build())
296    }
297}
298
299#[cfg(test)]
300mod tests {
301    use super::*;
302
303    #[test]
304    fn test_default_config() {
305        let config = HttpClientConfig::default();
306        assert_eq!(config.connect_timeout, Duration::from_secs(10));
307        assert_eq!(config.request_timeout, Duration::from_secs(30));
308        assert_eq!(config.max_retries, 3);
309        assert!(config.follow_redirects);
310        assert!(!config.accept_invalid_certs);
311    }
312
313    #[test]
314    fn test_quick_config() {
315        let config = HttpClientConfig::quick();
316        assert_eq!(config.connect_timeout, Duration::from_secs(5));
317        assert_eq!(config.max_retries, 1);
318    }
319
320    #[test]
321    fn test_retry_strategy_exponential() {
322        let strategy = RetryStrategy::ExponentialBackoff {
323            initial: Duration::from_millis(100),
324            max: Duration::from_secs(5),
325        };
326
327        assert_eq!(
328            strategy.delay_for_attempt(1),
329            Some(Duration::from_millis(100))
330        );
331        assert_eq!(
332            strategy.delay_for_attempt(2),
333            Some(Duration::from_millis(200))
334        );
335        assert_eq!(
336            strategy.delay_for_attempt(3),
337            Some(Duration::from_millis(400))
338        );
339        assert_eq!(
340            strategy.delay_for_attempt(10),
341            Some(Duration::from_secs(5))
342        );
343    }
344
345    #[test]
346    fn test_retry_strategy_fixed() {
347        let strategy = RetryStrategy::Fixed(Duration::from_millis(500));
348        assert_eq!(
349            strategy.delay_for_attempt(1),
350            Some(Duration::from_millis(500))
351        );
352        assert_eq!(
353            strategy.delay_for_attempt(5),
354            Some(Duration::from_millis(500))
355        );
356    }
357
358    #[test]
359    fn test_retry_strategy_none() {
360        let strategy = RetryStrategy::None;
361        assert_eq!(strategy.delay_for_attempt(1), None);
362    }
363
364    #[test]
365    fn test_builder() {
366        let config = HttpClientConfigBuilder::new()
367            .connect_timeout(Duration::from_secs(5))
368            .request_timeout(Duration::from_secs(60))
369            .max_retries(5)
370            .user_agent("TestAgent/1.0")
371            .build();
372
373        assert_eq!(config.connect_timeout, Duration::from_secs(5));
374        assert_eq!(config.request_timeout, Duration::from_secs(60));
375        assert_eq!(config.max_retries, 5);
376        assert_eq!(config.user_agent, "TestAgent/1.0");
377    }
378
379    #[test]
380    fn test_proxy_config() {
381        let proxy = ProxyConfig::all("http://proxy.example.com:8080")
382            .with_no_proxy(vec!["localhost".into(), "127.0.0.1".into()]);
383
384        assert_eq!(
385            proxy.http,
386            Some("http://proxy.example.com:8080".to_string())
387        );
388        assert_eq!(
389            proxy.https,
390            Some("http://proxy.example.com:8080".to_string())
391        );
392        assert_eq!(proxy.no_proxy.len(), 2);
393    }
394}