zlsrs 0.1.6

Rust 标准库扩展工具集,提供更便捷的使用方式
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
//! HTTP 客户端的核心类型定义
//!
//! 这个模块包含了 HTTP 客户端所需的所有核心类型定义,包括:
//! - HTTP 请求方法
//! - 请求体格式
//! - HTTP 响应
//! - 客户端配置
//! - 错误处理

use crate::zfile;
use crate::zfile::ZPath;
use std::collections::HashMap;
use std::time::Duration;

/// HTTP 请求方法
///
/// 支持标准的 HTTP 方法,包括 GET、POST、PUT、DELETE 等。
///
/// # 示例
/// ```
/// use zlsrs::zhttp::Method;
///
/// let method = Method::GET;
/// assert_eq!(method.as_str(), "GET");
/// ```
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum Method {
    /// HTTP GET 方法,用于获取资源
    GET,
    /// HTTP POST 方法,用于创建资源
    POST,
    /// HTTP PUT 方法,用于更新资源
    PUT,
    /// HTTP DELETE 方法,用于删除资源
    DELETE,
    /// HTTP HEAD 方法,类似 GET 但只返回头部
    HEAD,
    /// HTTP OPTIONS 方法,用于获取服务器支持的方法
    OPTIONS,
    /// HTTP PATCH 方法,用于部分更新资源
    PATCH,
}

impl Method {
    pub(crate) fn as_str(&self) -> &str {
        match self {
            Method::GET => "GET",
            Method::POST => "POST",
            Method::PUT => "PUT",
            Method::DELETE => "DELETE",
            Method::HEAD => "HEAD",
            Method::OPTIONS => "OPTIONS",
            Method::PATCH => "PATCH",
        }
    }
}

/// 请求体格式定义
///
/// 支持多种常见的请求体格式,包括表单、JSON、纯文本等。
///
/// # 示例
/// ```
/// use zlsrs::zhttp::BodyFormat;
///
/// let format = BodyFormat::Json;
/// assert_eq!(format.content_type(), "application/json");
/// ```
#[derive(Debug, Clone, PartialEq)]
pub enum BodyFormat {
    /// application/x-www-form-urlencoded 格式
    /// 用于普通的表单提交
    FormUrlEncoded,
    /// application/json 格式
    /// 用于 JSON 数据传输
    Json,
    /// text/plain 格式
    /// 用于纯文本数据
    Text,
    /// multipart/form-data 格式
    /// 用于文件上传等场景,需要指定 boundary
    FormData(String),
    /// 自定义 Content-Type 格式
    /// 用于不在预定义范围内的格式
    Custom(String),
}

impl BodyFormat {
    pub(crate) fn content_type(&self) -> String {
        match self {
            BodyFormat::FormUrlEncoded => "application/x-www-form-urlencoded".to_string(),
            BodyFormat::Json => "application/json".to_string(),
            BodyFormat::Text => "text/plain".to_string(),
            BodyFormat::FormData(boundary) => format!("multipart/form-data; boundary={}", boundary),
            BodyFormat::Custom(content_type) => content_type.clone(),
        }
    }
}

/// HTTP 响应结构
///
/// 包含了 HTTP 响应的完整信息,包括状态码、响应头和响应体。
/// 提供了多种方法来访问和解析响应内容。
///
/// # 示例
/// ```
/// use zlsrs::zhttp::{Client, Response};
///
/// async fn example() -> Result<()> {
///     let client = Client::new();
///     let response = client.get("https://api.example.com").send().await?;
///     
///     println!("Status: {}", response.status);
///     println!("Body: {}", response.text()?);
///     Ok(())
/// }
/// ```
#[derive(Debug)]
pub struct Response {
    /// HTTP 状态码
    pub status: u16,
    /// 响应头
    pub(crate) headers: HashMap<String, String>,
    /// 响应体
    pub(crate) body: Vec<u8>,
}

impl Response {
    /// 获取响应体的文本内容
    pub fn text(&self) -> Result<String, Error> {
        String::from_utf8(self.body.clone()).map_err(|e| Error::Custom(e.to_string()))
    }

    /// 尝试将响应体解析为 JSON
    #[cfg(feature = "json")]
    pub fn json<T: serde::de::DeserializeOwned>(&self) -> Result<T, Error> {
        serde_json::from_slice(&self.body).map_err(|e| Error::Custom(e.to_string()))
    }

    /// 获取响应体的原始字节
    pub fn bytes(&self) -> &[u8] {
        &self.body
    }

    /// 获取指定响应头的值
    pub fn header(&self, name: &str) -> Option<&String> {
        // 不区分大小写的头部查找
        self.headers.iter().find_map(|(key, value)| {
            if key.eq_ignore_ascii_case(name) {
                Some(value)
            } else {
                None
            }
        })
    }

    /// 获取所有响应头
    pub fn headers(&self) -> &HashMap<String, String> {
        &self.headers
    }

    /// 检查响应是否成功 (2xx)
    pub fn is_success(&self) -> bool {
        (200..300).contains(&self.status)
    }

    /// 检查是否为重定向响应 (3xx)
    pub fn is_redirect(&self) -> bool {
        (300..400).contains(&self.status)
    }

    /// 检查是否为客户端错误 (4xx)
    pub fn is_client_error(&self) -> bool {
        (400..500).contains(&self.status)
    }

    /// 检查是否为服务器错误 (5xx)
    pub fn is_server_error(&self) -> bool {
        (500..600).contains(&self.status)
    }

    /// 获取响应体的长度
    pub fn content_length(&self) -> usize {
        self.body.len()
    }

    /// 获取响应的内容类型
    pub fn content_type(&self) -> Option<&String> {
        self.header("Content-Type")
    }

    /// 将响应内容保存到文件
    ///
    /// # 参数
    ///
    /// * `path` - 文件保存路径,支持字符串或 PathBuf
    ///
    /// # 示例
    ///
    /// ```rust
    /// use zlsrs::zhttp::Client;
    ///
    /// async fn example() {
    ///     let client = Client::new();
    ///     let response = client
    ///         .get("https://example.com/file.pdf")
    ///         .send()
    ///         .unwrap();
    ///
    ///     // 保存到指定路径
    ///     response.save_to_file("downloads/file.pdf");
    /// }
    /// ```
    #[cfg(feature = "zfile")]
    pub fn save_to_file<P: AsRef<std::path::Path>>(&self, path: P) -> Result<ZPath, Error> {
        zfile::write_file(
            &path.as_ref().to_string_lossy(),
            &String::from_utf8_lossy(&self.body),
            false,
        )
        .map_err(|e| Error::Custom(format!("保存文件失败: {}", e)))
        .map(|path| ZPath::new(&path.as_ref().to_string_lossy()))
    }

    /// 将响应内容保存到文件,并自动从 Content-Disposition 或 URL 推断文件名
    ///
    /// # 参数
    ///
    /// * `dir` - 保存目录的路径
    ///
    /// # 返回值
    ///
    /// 返回保存的文件路径
    ///
    /// # 示例
    ///
    /// ```rust
    /// use zlsrs::zhttp::Client;
    ///
    /// async fn example() -> Result<(), Box<dyn std::error::Error>> {
    ///     let client = Client::new();
    ///     let response = client
    ///         .get("https://example.com/file.pdf")
    ///         .send()
    ///         .await?;
    ///
    ///     // 自动保存到 downloads 目录,文件名从响应中推断
    ///     let saved_path = response.save_to_dir("downloads")?;
    ///     println!("文件已保存到: {}", saved_path.display());
    ///     Ok(())
    /// }
    /// ```
    #[cfg(feature = "zfile")]
    pub fn save_to_dir<P: AsRef<std::path::Path>>(
        &self,
        dir: P,
    ) -> Result<std::path::PathBuf, Error> {
        use crate::zfile;
        use std::path::PathBuf;

        // 创建目录(如果不存在)
        zfile::create_dir(&dir.as_ref().to_string_lossy())
            .map_err(|e| Error::Custom(format!("创建目录失败: {}", e)))?;

        // 尝试从 Content-Disposition 获取文件名
        let filename = self
            .get_filename_from_disposition()
            .or_else(|| self.get_filename_from_url())
            .unwrap_or_else(|| "downloaded_file".to_string());

        let path = PathBuf::from(dir.as_ref()).join(filename);

        // 写入文件
        zfile::write_file(
            &path.to_string_lossy(),
            &String::from_utf8_lossy(&self.body),
            false,
        )
        .map_err(|e| Error::Custom(format!("保存文件失败: {}", e)))?;

        Ok(path)
    }

    /// 从 Content-Disposition 头部获取文件名
    fn get_filename_from_disposition(&self) -> Option<String> {
        self.header("Content-Disposition").and_then(|cd| {
            cd.split(';')
                .find(|part| part.trim().starts_with("filename="))
                .and_then(|filename_part| {
                    filename_part
                        .trim()
                        .strip_prefix("filename=")
                        .map(|filename| filename.trim_matches('"').trim_matches('\'').to_string())
                })
        })
    }

    /// 从 URL 获取文件名
    fn get_filename_from_url(&self) -> Option<String> {
        self.header("Location")
            .or_else(|| self.header("X-Original-URL"))
            .and_then(|url| {
                url.split('/')
                    .last()
                    .map(|s| s.split('?').next().unwrap_or(s).to_string())
            })
    }
}

/// HTTP 客户端配置
///
/// 用于自定义 HTTP 客户端的行为,包括超时设置、重试策略等。
///
/// # 示例
/// ```
/// use zlsrs::zhttp::{Client, ClientConfig};
/// use std::time::Duration;
///
/// let config = ClientConfig {
///     timeout: Duration::from_secs(60),
///     max_retries: 5,
///     ..Default::default()
/// };
/// let client = Client::with_config(config);
/// ```
#[derive(Debug, Clone)]
pub struct ClientConfig {
    /// 请求超时时间
    pub timeout: Duration,
    /// 自定义 User-Agent 字符串
    pub user_agent: String,
    /// 请求失败时的最大重试次数
    pub max_retries: u32,
    /// 重试之间的等待时间
    pub retry_interval: Duration,
    /// 是否自动处理重定向
    pub allow_redirects: bool,
    /// 最大允许的重定向次数
    pub max_redirects: u32,
    /// 是否验证 SSL 证书
    pub verify_ssl: bool,
    /// 是否启用调试模式
    pub debug: bool,
}

impl Default for ClientConfig {
    fn default() -> Self {
        Self {
            timeout: Duration::from_secs(30),
            user_agent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36".to_string(),
            max_retries: 3,
            retry_interval: Duration::from_secs(1),
            allow_redirects: true,
            max_redirects: 10,
            verify_ssl: true,
            debug: false,
        }
    }
}

/// HTTP 错误类型
///
/// 定义了在 HTTP 请求过程中可能遇到的各种错误。
///
/// # 示例
/// ```
/// use zlsrs::zhttp::Error;
///
/// fn handle_error(err: Error) {
///     match err {
///         Error::Timeout => println!("请求超时"),
///         Error::Network(e) => println!("网络错误: {}", e),
///         _ => println!("其他错误: {}", err),
///     }
/// }
/// ```
#[derive(Debug, thiserror::Error)]
pub enum Error {
    /// 请求超时错误
    #[error("请求超时")]
    Timeout,
    /// 网络相关错误
    #[error("网络错误: {0}")]
    Network(#[from] std::io::Error),
    /// URL 解析错误
    #[error("URL 解析错误: {0}")]
    UrlParse(#[from] url::ParseError),
    /// 响应解析错误
    #[error("响应解析错误: {0}")]
    ResponseParse(String),
    /// 重定向次数超过限制
    #[error("重定向次数过多")]
    TooManyRedirects,
    /// SSL 证书验证失败
    #[error("SSL 证书验证失败")]
    SslVerification,
    /// 无效的 HTTP 状态码
    #[error("无效的状态码")]
    InvalidStatus,
    /// 自定义错误
    #[error("自定义错误: {0}")]
    Custom(String),
}

impl From<serde_json::Error> for Error {
    fn from(err: serde_json::Error) -> Self {
        Error::Custom(err.to_string())
    }
}