unistore_http/
client.rs

1//! HTTP 客户端实现
2//!
3//! 职责:
4//! - 封装 reqwest 客户端
5//! - 提供自动重试机制
6//! - 管理连接池和配置
7
8use super::config::{HttpClientConfig, HttpClientConfigBuilder};
9use super::error::HttpError;
10use super::request::{CancellableRequest, Method, RequestBody, RequestBuilder};
11use super::response::Response;
12
13/// HTTP 客户端
14///
15/// 封装 reqwest,提供:
16/// - 合理的默认配置
17/// - 自动重试
18/// - 与取消系统集成
19///
20/// # 使用层级
21///
22/// 1. **Level 3 - 便捷函数**:`get()`, `post_json()`
23/// 2. **Level 2 - 客户端实例**:`HttpClient::new()`, `HttpClient::builder()`
24/// 3. **Level 1 - 底层访问**:`client.inner()` 获取 reqwest::Client
25///
26/// # Example
27///
28/// ```ignore
29/// // 默认配置
30/// let client = HttpClient::new()?;
31/// let resp = client.get("https://api.example.com/data").await?;
32///
33/// // 自定义配置
34/// let client = HttpClient::builder()
35///     .timeout(Duration::from_secs(60))
36///     .max_retries(5)
37///     .build_client()?;
38/// ```
39#[derive(Debug, Clone)]
40pub struct HttpClient {
41    inner: reqwest::Client,
42    config: HttpClientConfig,
43}
44
45impl HttpClient {
46    /// 使用默认配置创建客户端
47    pub fn new() -> Result<Self, HttpError> {
48        Self::with_config(HttpClientConfig::default())
49    }
50
51    /// 使用自定义配置创建客户端
52    pub fn with_config(config: HttpClientConfig) -> Result<Self, HttpError> {
53        let mut builder = reqwest::Client::builder()
54            .connect_timeout(config.connect_timeout)
55            .timeout(config.request_timeout)
56            .user_agent(&config.user_agent)
57            .redirect(if config.follow_redirects {
58                reqwest::redirect::Policy::limited(config.max_redirects as usize)
59            } else {
60                reqwest::redirect::Policy::none()
61            });
62
63        if config.accept_invalid_certs {
64            builder = builder.danger_accept_invalid_certs(true);
65        }
66
67        if let Some(proxy) = &config.proxy {
68            if let Some(http) = &proxy.http {
69                builder = builder.proxy(
70                    reqwest::Proxy::http(http)
71                        .map_err(|e| HttpError::ClientBuild(format!("HTTP 代理配置错误: {}", e)))?
72                );
73            }
74            if let Some(https) = &proxy.https {
75                builder = builder.proxy(
76                    reqwest::Proxy::https(https)
77                        .map_err(|e| HttpError::ClientBuild(format!("HTTPS 代理配置错误: {}", e)))?
78                );
79            }
80        }
81
82        let inner = builder
83            .build()
84            .map_err(|e| HttpError::ClientBuild(e.to_string()))?;
85
86        Ok(Self { inner, config })
87    }
88
89    /// 创建配置构建器
90    pub fn builder() -> HttpClientConfigBuilder {
91        HttpClientConfigBuilder::new()
92    }
93
94    /// 获取当前配置
95    pub fn config(&self) -> &HttpClientConfig {
96        &self.config
97    }
98
99    /// 访问底层 reqwest::Client(完全控制)
100    ///
101    /// 当封装无法满足需求时,可以直接使用 reqwest 的所有高级功能
102    pub fn inner(&self) -> &reqwest::Client {
103        &self.inner
104    }
105
106    // ========================================================================
107    // 便捷请求方法
108    // ========================================================================
109
110    /// GET 请求
111    pub async fn get(&self, url: &str) -> Result<Response, HttpError> {
112        self.execute(RequestBuilder::new(Method::GET, url)).await
113    }
114
115    /// POST 请求(JSON body)
116    pub async fn post_json<T: serde::Serialize>(
117        &self,
118        url: &str,
119        body: &T,
120    ) -> Result<Response, HttpError> {
121        let builder = RequestBuilder::new(Method::POST, url).json(body)?;
122        self.execute(builder).await
123    }
124
125    /// POST 请求(表单 body)
126    pub async fn post_form<T: serde::Serialize>(
127        &self,
128        url: &str,
129        body: &T,
130    ) -> Result<Response, HttpError> {
131        let builder = RequestBuilder::new(Method::POST, url).form(body)?;
132        self.execute(builder).await
133    }
134
135    /// PUT 请求(JSON body)
136    pub async fn put_json<T: serde::Serialize>(
137        &self,
138        url: &str,
139        body: &T,
140    ) -> Result<Response, HttpError> {
141        let builder = RequestBuilder::new(Method::PUT, url).json(body)?;
142        self.execute(builder).await
143    }
144
145    /// DELETE 请求
146    pub async fn delete(&self, url: &str) -> Result<Response, HttpError> {
147        self.execute(RequestBuilder::new(Method::DELETE, url)).await
148    }
149
150    /// PATCH 请求(JSON body)
151    pub async fn patch_json<T: serde::Serialize>(
152        &self,
153        url: &str,
154        body: &T,
155    ) -> Result<Response, HttpError> {
156        let builder = RequestBuilder::new(Method::PATCH, url).json(body)?;
157        self.execute(builder).await
158    }
159
160    /// HEAD 请求
161    pub async fn head(&self, url: &str) -> Result<Response, HttpError> {
162        self.execute(RequestBuilder::new(Method::HEAD, url)).await
163    }
164
165    // ========================================================================
166    // 构建请求
167    // ========================================================================
168
169    /// 创建请求构建器
170    pub fn request(&self, method: Method, url: &str) -> RequestBuilder {
171        RequestBuilder::new(method, url)
172    }
173
174    /// 执行请求(带自动重试)
175    pub async fn execute(&self, builder: RequestBuilder) -> Result<Response, HttpError> {
176        self.execute_with_retry(builder).await
177    }
178
179    /// 执行可取消的请求
180    pub async fn execute_cancellable(
181        &self,
182        request: CancellableRequest,
183    ) -> Result<Response, HttpError> {
184        tokio::select! {
185            result = self.execute_with_retry(request.builder) => result,
186            _ = request.cancel_token.cancelled() => Err(HttpError::Cancelled),
187        }
188    }
189
190    // ========================================================================
191    // 内部实现
192    // ========================================================================
193
194    async fn execute_with_retry(&self, builder: RequestBuilder) -> Result<Response, HttpError> {
195        let max_retries = builder.max_retries.unwrap_or(self.config.max_retries);
196        let retry_strategy = builder
197            .retry_strategy
198            .clone()
199            .unwrap_or_else(|| self.config.retry_strategy.clone());
200
201        let mut attempts = 0;
202
203        loop {
204            attempts += 1;
205
206            match self.execute_once(&builder).await {
207                Ok(resp) => {
208                    // 5xx 错误也重试
209                    if resp.is_server_error() && attempts <= max_retries {
210                        // 继续重试循环
211                    } else {
212                        return Ok(resp);
213                    }
214                }
215                Err(e) if e.is_retryable() && attempts <= max_retries => {
216                    // 继续重试循环
217                }
218                Err(e) => return Err(e),
219            }
220
221            // 计算重试延迟
222            if let Some(delay) = retry_strategy.delay_for_attempt(attempts) {
223                tokio::time::sleep(delay).await;
224            } else {
225                // 已超过最大重试次数
226                return Err(HttpError::RequestTimeout);
227            }
228        }
229    }
230
231    async fn execute_once(&self, builder: &RequestBuilder) -> Result<Response, HttpError> {
232        let url = builder.build_url()?;
233
234        let method = match builder.method {
235            Method::GET => reqwest::Method::GET,
236            Method::POST => reqwest::Method::POST,
237            Method::PUT => reqwest::Method::PUT,
238            Method::DELETE => reqwest::Method::DELETE,
239            Method::PATCH => reqwest::Method::PATCH,
240            Method::HEAD => reqwest::Method::HEAD,
241            Method::OPTIONS => reqwest::Method::OPTIONS,
242        };
243
244        let mut req = self.inner.request(method, &url);
245
246        // 添加请求头
247        for (key, value) in &builder.headers {
248            req = req.header(key.as_str(), value.as_str());
249        }
250
251        // 设置超时
252        if let Some(timeout) = builder.timeout {
253            req = req.timeout(timeout);
254        }
255
256        // 设置请求体
257        match &builder.body {
258            RequestBody::None => {}
259            RequestBody::Json(bytes) => {
260                req = req.body(bytes.clone());
261            }
262            RequestBody::Form(encoded) => {
263                req = req.body(encoded.clone());
264            }
265            RequestBody::Bytes(bytes) => {
266                req = req.body(bytes.clone());
267            }
268            RequestBody::Text(text) => {
269                req = req.body(text.clone());
270            }
271        }
272
273        let resp = req.send().await?;
274        Response::from_reqwest(resp).await
275    }
276}
277
278impl Default for HttpClient {
279    fn default() -> Self {
280        Self::new().expect("创建默认 HTTP 客户端失败")
281    }
282}
283
284#[cfg(test)]
285mod tests {
286    use super::*;
287    use std::time::Duration;
288
289    #[test]
290    fn test_client_config() {
291        let config = HttpClientConfig::default();
292        assert_eq!(config.connect_timeout, Duration::from_secs(10));
293        assert_eq!(config.max_retries, 3);
294    }
295
296    #[test]
297    fn test_builder_methods() {
298        let builder = HttpClient::builder()
299            .timeout(Duration::from_secs(60))
300            .max_retries(5)
301            .no_retry()
302            .build();
303
304        assert_eq!(builder.request_timeout, Duration::from_secs(60));
305        assert_eq!(builder.max_retries, 0);
306    }
307}