1use super::config::{HttpClientConfig, HttpClientConfigBuilder};
9use super::error::HttpError;
10use super::request::{CancellableRequest, Method, RequestBody, RequestBuilder};
11use super::response::Response;
12
13#[derive(Debug, Clone)]
40pub struct HttpClient {
41 inner: reqwest::Client,
42 config: HttpClientConfig,
43}
44
45impl HttpClient {
46 pub fn new() -> Result<Self, HttpError> {
48 Self::with_config(HttpClientConfig::default())
49 }
50
51 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 pub fn builder() -> HttpClientConfigBuilder {
91 HttpClientConfigBuilder::new()
92 }
93
94 pub fn config(&self) -> &HttpClientConfig {
96 &self.config
97 }
98
99 pub fn inner(&self) -> &reqwest::Client {
103 &self.inner
104 }
105
106 pub async fn get(&self, url: &str) -> Result<Response, HttpError> {
112 self.execute(RequestBuilder::new(Method::GET, url)).await
113 }
114
115 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 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 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 pub async fn delete(&self, url: &str) -> Result<Response, HttpError> {
147 self.execute(RequestBuilder::new(Method::DELETE, url)).await
148 }
149
150 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 pub async fn head(&self, url: &str) -> Result<Response, HttpError> {
162 self.execute(RequestBuilder::new(Method::HEAD, url)).await
163 }
164
165 pub fn request(&self, method: Method, url: &str) -> RequestBuilder {
171 RequestBuilder::new(method, url)
172 }
173
174 pub async fn execute(&self, builder: RequestBuilder) -> Result<Response, HttpError> {
176 self.execute_with_retry(builder).await
177 }
178
179 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 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 if resp.is_server_error() && attempts <= max_retries {
210 } else {
212 return Ok(resp);
213 }
214 }
215 Err(e) if e.is_retryable() && attempts <= max_retries => {
216 }
218 Err(e) => return Err(e),
219 }
220
221 if let Some(delay) = retry_strategy.delay_for_attempt(attempts) {
223 tokio::time::sleep(delay).await;
224 } else {
225 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 for (key, value) in &builder.headers {
248 req = req.header(key.as_str(), value.as_str());
249 }
250
251 if let Some(timeout) = builder.timeout {
253 req = req.timeout(timeout);
254 }
255
256 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}