Skip to main content

aria2_core/http/
request_response.rs

1//! HTTP request building and response parsing module
2//!
3//! Provides HTTP/1.1 request building, response parsing, and authentication.
4//! Supports fluent API for building HTTP requests with automatic standard headers.
5
6use base64::{Engine, engine::general_purpose};
7use std::collections::HashMap;
8use url::Url;
9
10use crate::error::{Aria2Error, Result};
11
12/// HTTP 请求方法枚举
13#[derive(Debug, Clone, PartialEq)]
14pub enum HttpMethod {
15    /// GET 请求方法
16    Get,
17    /// POST 请求方法
18    Post,
19    /// HEAD 请求方法
20    Head,
21    /// PUT 请求方法
22    Put,
23    /// DELETE 请求方法
24    Delete,
25}
26
27impl std::fmt::Display for HttpMethod {
28    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29        match self {
30            HttpMethod::Get => write!(f, "GET"),
31            HttpMethod::Post => write!(f, "POST"),
32            HttpMethod::Head => write!(f, "HEAD"),
33            HttpMethod::Put => write!(f, "PUT"),
34            HttpMethod::Delete => write!(f, "DELETE"),
35        }
36    }
37}
38
39/// HTTP 请求结构体
40///
41/// 表示一个完整的 HTTP/1.1 请求,包含方法、URL、headers 和可选的 body。
42#[derive(Debug, Clone)]
43pub struct HttpRequest {
44    /// HTTP 请求方法
45    pub method: HttpMethod,
46    /// 请求 URL
47    pub url: Url,
48    /// 请求 headers (支持多值)
49    pub headers: HashMap<String, String>,
50    /// 可选的请求体
51    pub body: Option<Vec<u8>>,
52}
53
54impl HttpRequest {
55    /// 将 HTTP 请求序列化为原始字节
56    ///
57    /// 按照 HTTP/1.1 规范将请求序列化为可发送的字节序列。
58    /// 格式: `METHOD PATH VERSION\r\nHeaders\r\n\r\nBody`
59    ///
60    /// # Returns
61    ///
62    /// 序列化后的字节数组
63    pub fn to_bytes(&self) -> Vec<u8> {
64        let mut result = String::new();
65
66        // 请求行: METHOD /path HTTP/1.1
67        let path = self.url.path();
68        let query = self.url.query();
69        if let Some(q) = query {
70            result.push_str(&format!("{} {}?{} HTTP/1.1\r\n", self.method, path, q));
71        } else {
72            result.push_str(&format!("{} {} HTTP/1.1\r\n", self.method, path));
73        }
74
75        // Headers
76        for (key, value) in &self.headers {
77            result.push_str(&format!("{}: {}\r\n", key, value));
78        }
79
80        // 空行分隔 header 和 body
81        result.push_str("\r\n");
82
83        let mut bytes = result.into_bytes();
84
85        // Body
86        if let Some(ref body) = self.body {
87            bytes.extend_from_slice(body);
88        }
89
90        bytes
91    }
92}
93
94/// HTTP 请求构建器 (Fluent API)
95///
96/// 使用流式 API 构建完整的 HTTP 请求,自动添加标准 headers。
97///
98/// # Examples
99///
100/// ```rust
101/// use url::Url;
102/// use aria2_core::http::request_response::{HttpRequestBuilder, HttpMethod};
103///
104/// let url = Url::parse("http://example.com/api").unwrap();
105/// let request = HttpRequestBuilder::new(HttpMethod::Get, url)
106///     .header("Accept", "application/json")
107///     .build()
108///     .unwrap();
109/// ```
110pub struct HttpRequestBuilder {
111    /// HTTP 方法
112    method: HttpMethod,
113    /// 目标 URL
114    url: Url,
115    /// 自定义 headers
116    headers: HashMap<String, String>,
117    /// 可选的请求体
118    body: Option<Vec<u8>>,
119}
120
121impl HttpRequestBuilder {
122    /// 创建新的 HTTP 请求构建器
123    ///
124    /// # Arguments
125    ///
126    /// * `method` - HTTP 请求方法 (GET/POST/HEAD/PUT/DELETE)
127    /// * `url` - 目标 URL
128    ///
129    /// # Returns
130    ///
131    /// 新的 HttpRequestBuilder 实例
132    pub fn new(method: HttpMethod, url: Url) -> Self {
133        Self {
134            method,
135            url,
136            headers: HashMap::new(),
137            body: None,
138        }
139    }
140
141    /// 添加单个 header
142    ///
143    /// 如果已存在相同 key 的 header,将被覆盖。
144    ///
145    /// # Arguments
146    ///
147    /// * `key` - header 名称
148    /// * `value` - header 值
149    ///
150    /// # Returns
151    ///
152    /// Self,支持链式调用
153    pub fn header(mut self, key: &str, value: &str) -> Self {
154        self.headers.insert(key.to_string(), value.to_string());
155        self
156    }
157
158    /// 批量设置 headers
159    ///
160    /// 将传入的所有 headers 合并到现有 headers 中。
161    /// 如果存在重复 key,新值会覆盖旧值。
162    ///
163    /// # Arguments
164    ///
165    /// * `headers` - 要添加的 headers 集合
166    ///
167    /// # Returns
168    ///
169    /// Self,支持链式调用
170    pub fn headers(mut self, headers: HashMap<String, String>) -> Self {
171        self.headers.extend(headers);
172        self
173    }
174
175    /// 设置请求体
176    ///
177    /// # Arguments
178    ///
179    /// * `body` - 请求体的字节数据
180    ///
181    /// # Returns
182    ///
183    /// Self,支持链式调用
184    pub fn body(mut self, body: Vec<u8>) -> Self {
185        self.body = Some(body);
186        self
187    }
188
189    /// 构建最终的 HTTP 请求
190    ///
191    /// 自动添加以下标准 headers:
192    /// - Host: 从 URL 中提取
193    /// - User-Agent: aria2-rust/1.0
194    /// - Accept: */*
195    /// - Connection: close
196    /// - Content-Length: 如果有 body
197    ///
198    /// # Returns
199    ///
200    /// 构建完成的 HttpRequest,或错误信息
201    pub fn build(self) -> Result<HttpRequest> {
202        let mut final_headers = self.headers;
203
204        // 自动添加标准 headers (如果用户未手动设置)
205        // Host header
206        if !final_headers.contains_key("Host") {
207            let host = self.url.host_str().unwrap_or("");
208            if let Some(port) = self.url.port() {
209                final_headers.insert("Host".to_string(), format!("{}:{}", host, port));
210            } else {
211                final_headers.insert("Host".to_string(), host.to_string());
212            }
213        }
214
215        // User-Agent header
216        if !final_headers.contains_key("User-Agent") {
217            final_headers.insert("User-Agent".to_string(), "aria2-rust/1.0".to_string());
218        }
219
220        // Accept header
221        if !final_headers.contains_key("Accept") {
222            final_headers.insert("Accept".to_string(), "*/*".to_string());
223        }
224
225        // Connection header
226        if !final_headers.contains_key("Connection") {
227            final_headers.insert("Connection".to_string(), "close".to_string());
228        }
229
230        // Content-Length header (如果有 body)
231        if let Some(body) = &self.body
232            && !final_headers.contains_key("Content-Length")
233        {
234            let len = body.len();
235            final_headers.insert("Content-Length".to_string(), len.to_string());
236        }
237
238        Ok(HttpRequest {
239            method: self.method,
240            url: self.url,
241            headers: final_headers,
242            body: self.body,
243        })
244    }
245}
246
247/// HTTP 响应结构体
248///
249/// 表示一个完整的 HTTP 响应,包含状态码、reason phrase、版本、headers 和可选的 body。
250/// 支持多值 headers (如 Set-Cookie)。
251#[derive(Debug, Clone)]
252pub struct HttpResponse {
253    /// 状态码 (如 200, 404, 301)
254    pub status_code: u16,
255    /// 原因短语 (如 OK, Not Found, Moved Permanently)
256    pub reason_phrase: String,
257    /// HTTP 版本 (如 "HTTP/1.1")
258    pub version: String,
259    /// 响应 headers (支持多值)
260    pub headers: HashMap<String, Vec<String>>,
261    /// 可选的响应体
262    pub body: Option<Vec<u8>>,
263}
264
265impl HttpResponse {
266    /// 从原始字节解析 HTTP 响应
267    ///
268    /// 解析符合 HTTP/1.1 规范的响应数据,包括状态行、headers 和 body。
269    /// 支持多值 headers (通过逗号分隔或多个同名 header)。
270    ///
271    /// # Arguments
272    ///
273    /// * `data` - 原始 HTTP 响应字节
274    ///
275    /// # Returns
276    ///
277    /// 解析后的 HttpResponse,或错误信息
278    ///
279    /// # Errors
280    ///
281    /// 返回错误如果响应格式无效或无法解析
282    pub fn from_bytes(data: &[u8]) -> Result<Self> {
283        let response_str = String::from_utf8(data.to_vec())
284            .map_err(|e| Aria2Error::Parse(format!("Invalid UTF-8 in HTTP response: {}", e)))?;
285
286        // 分离 headers 和 body
287        let (header_part, body_part) = match response_str.find("\r\n\r\n") {
288            Some(pos) => (&response_str[..pos], &response_str[pos + 4..]),
289            None => (response_str.as_str(), ""),
290        };
291
292        // 解析状态行
293        let mut lines = header_part.split("\r\n");
294        let status_line = lines
295            .next()
296            .ok_or_else(|| Aria2Error::Parse("Empty HTTP response".to_string()))?;
297
298        // 解析 version, status_code, reason_phrase
299        let parts: Vec<&str> = status_line.split_whitespace().collect();
300        if parts.len() < 2 {
301            return Err(Aria2Error::Parse(
302                "Invalid HTTP status line format".to_string(),
303            ));
304        }
305
306        let version = parts[0].to_string();
307        let status_code: u16 = parts[1]
308            .parse()
309            .map_err(|e| Aria2Error::Parse(format!("Invalid status code: {}", e)))?;
310        let reason_phrase = if parts.len() > 2 {
311            parts[2..].join(" ")
312        } else {
313            String::new()
314        };
315
316        // 解析 headers (支持多值)
317        let mut headers: HashMap<String, Vec<String>> = HashMap::new();
318        for line in lines {
319            if line.is_empty() {
320                continue;
321            }
322            if let Some((key, value)) = line.split_once(':') {
323                let key = key.trim().to_string();
324                let value = value.trim().to_string();
325                headers.entry(key).or_default().push(value);
326            }
327        }
328
329        // 处理 body
330        let body = if body_part.is_empty() {
331            None
332        } else {
333            Some(body_part.as_bytes().to_vec())
334        };
335
336        Ok(HttpResponse {
337            status_code,
338            reason_phrase,
339            version,
340            headers,
341            body,
342        })
343    }
344
345    /// 获取指定 header 的第一个值
346    ///
347    /// # Arguments
348    ///
349    /// * `name` - header 名称 (不区分大小写)
350    ///
351    /// # Returns
352    ///
353    /// 第一个 header 值的引用,如果不存在则返回 None
354    pub fn header(&self, name: &str) -> Option<&String> {
355        let name_lower = name.to_lowercase();
356        for (key, values) in &self.headers {
357            if key.to_lowercase() == name_lower {
358                return values.first();
359            }
360        }
361        None
362    }
363
364    /// 获取指定 header 的所有值
365    ///
366    /// 对于 Set-Cookie 等可能多次出现的 header 特别有用。
367    ///
368    /// # Arguments
369    ///
370    /// * `name` - header 名称 (不区分大小写)
371    ///
372    /// # Returns
373    ///
374    /// 包含所有匹配值的向量
375    pub fn header_all(&self, name: &str) -> Vec<String> {
376        let name_lower = name.to_lowercase();
377        for (key, values) in &self.headers {
378            if key.to_lowercase() == name_lower {
379                return values.clone();
380            }
381        }
382        Vec::new()
383    }
384
385    /// 获取 Content-Length header 的值
386    ///
387    /// # Returns
388    ///
389    /// 内容长度 (u64),如果不存在或解析失败则返回 None
390    pub fn content_length(&self) -> Option<u64> {
391        self.header("Content-Length")
392            .and_then(|v| v.parse::<u64>().ok())
393    }
394
395    /// 检查是否为重定向响应 (3xx)
396    ///
397    /// # Returns
398    ///
399    /// 如果状态码在 300-399 范围内返回 true
400    pub fn is_redirect(&self) -> bool {
401        (300..400).contains(&self.status_code)
402    }
403
404    /// 获取 Location header 并解析为 URL
405    ///
406    /// 对于重定向响应特别有用。如果是相对 URL,会基于当前请求 URL 进行解析。
407    ///
408    /// # Returns
409    ///
410    /// 解析后的绝对 URL,如果不存在或解析失败则返回 None
411    pub fn location(&self) -> Option<Url> {
412        self.header("Location").and_then(|loc| Url::parse(loc).ok())
413    }
414
415    /// 使用流式解码器获取解码后的 body
416    ///
417    /// 根据 HTTP 响应的 Content-Encoding 和 Transfer-Encoding headers 自动选择合适的解码器,
418    /// 对响应体进行解码。支持 GZip、Chunked、BZip2 等编码格式。
419    ///
420    /// 遵循 RFC 7230 Section 3.3.1 规范:Transfer-Encoding 优先于 Content-Encoding。
421    ///
422    /// # Returns
423    ///
424    /// 解码后的原始数据,或错误信息。如果没有 body 则返回空向量。
425    ///
426    /// # Errors
427    ///
428    /// - 如果编码格式无效或数据损坏
429    /// - 如果解码过程中发生 I/O 错误
430    ///
431    /// # Examples
432    ///
433    /// ```rust,ignore
434    /// let response = /* HTTP response with Content-Encoding: gzip */;
435    /// let decoded = response.decoded_body()?;
436    /// // decoded 包含解压后的原始数据
437    /// ```
438    pub fn decoded_body(&self) -> Result<Vec<u8>> {
439        use crate::http::stream_filter::{AutoFilterSelector, process_filters};
440
441        let encoding = self.header("Content-Encoding").map(|s| s.as_str());
442        let transfer_enc = self.header("Transfer-Encoding").map(|s| s.as_str());
443
444        let mut filters = AutoFilterSelector::select_filters(encoding, transfer_enc);
445
446        match &self.body {
447            Some(raw_data) => process_filters(&mut filters, raw_data),
448            None => Ok(Vec::new()),
449        }
450    }
451}
452
453/// Generate Basic Auth header value
454///
455/// Encodes username:password as Base64 and returns `Basic <credentials>`.
456///
457/// # Examples
458///
459/// ```
460/// use aria2_core::http::request_response::basic_auth;
461///
462/// let auth_header = basic_auth("user", "pass");
463/// assert_eq!(auth_header, "Basic dXNlcjpwYXNz");
464/// ```
465pub fn basic_auth(username: &str, password: &str) -> String {
466    let credentials = format!("{}:{}", username, password);
467    let encoded = general_purpose::STANDARD.encode(credentials.as_bytes());
468    format!("Basic {}", encoded)
469}
470
471/// Generate Bearer Token header value
472///
473/// # Arguments
474///
475/// * `token` - Bearer token
476///
477/// # Returns
478///
479/// Complete Authorization header value (e.g., `Bearer my-token`)
480pub fn bearer_token(token: &str) -> String {
481    format!("Bearer {}", token)
482}
483
484// Base64 编码已通过 use base64::{engine::general_purpose, Engine} 导入
485
486#[cfg(test)]
487mod tests {
488    use super::*;
489
490    #[test]
491    fn test_request_builder_fluent_api() {
492        let url = Url::parse("http://example.com/api/test").unwrap();
493        let request = HttpRequestBuilder::new(HttpMethod::Post, url.clone())
494            .header("Content-Type", "application/json")
495            .header("Accept", "application/json")
496            .body(b"{\"key\":\"value\"}".to_vec())
497            .build()
498            .unwrap();
499
500        assert_eq!(request.method, HttpMethod::Post);
501        assert_eq!(request.url, url);
502        assert_eq!(
503            request.headers.get("Content-Type").unwrap(),
504            "application/json"
505        );
506        assert_eq!(request.headers.get("Accept").unwrap(), "application/json");
507        assert!(request.body.is_some());
508        assert_eq!(request.body.unwrap(), b"{\"key\":\"value\"}");
509    }
510
511    #[test]
512    fn test_request_auto_headers_generation() {
513        let url = Url::parse("http://example.com:8080/path").unwrap();
514        let request = HttpRequestBuilder::new(HttpMethod::Get, url)
515            .build()
516            .unwrap();
517
518        // 检查自动生成的 Host header (包含端口)
519        assert_eq!(request.headers.get("Host").unwrap(), "example.com:8080");
520
521        // 检查自动生成的 User-Agent
522        assert_eq!(request.headers.get("User-Agent").unwrap(), "aria2-rust/1.0");
523
524        // 检查自动生成的 Accept
525        assert_eq!(request.headers.get("Accept").unwrap(), "*/*");
526
527        // 检查自动生成的 Connection
528        assert_eq!(request.headers.get("Connection").unwrap(), "close");
529    }
530
531    #[test]
532    fn test_request_auto_content_length() {
533        let url = Url::parse("http://example.com/api").unwrap();
534        let body = b"test body data";
535        let request = HttpRequestBuilder::new(HttpMethod::Post, url)
536            .body(body.to_vec())
537            .build()
538            .unwrap();
539
540        assert_eq!(
541            request.headers.get("Content-Length").unwrap(),
542            &body.len().to_string()
543        );
544    }
545
546    #[test]
547    fn test_request_custom_host_not_overridden() {
548        let url = Url::parse("http://example.com/api").unwrap();
549        let request = HttpRequestBuilder::new(HttpMethod::Get, url)
550            .header("Host", "custom-host.com")
551            .build()
552            .unwrap();
553
554        // 用户自定义的 Host 应该保留
555        assert_eq!(request.headers.get("Host").unwrap(), "custom-host.com");
556    }
557
558    #[test]
559    fn test_request_to_bytes() {
560        let url = Url::parse("http://example.com/path?q=1").unwrap();
561        let request = HttpRequestBuilder::new(HttpMethod::Get, url)
562            .header("Custom-Header", "test-value")
563            .build()
564            .unwrap();
565
566        let bytes = request.to_bytes();
567        let request_str = String::from_utf8(bytes).unwrap();
568
569        // 验证请求行
570        assert!(request_str.starts_with("GET /path?q=1 HTTP/1.1\r\n"));
571
572        // 验证自定义 header
573        assert!(request_str.contains("Custom-Header: test-value"));
574
575        // 验证标准 headers
576        assert!(request_str.contains("Host: example.com"));
577        assert!(request_str.contains("User-Agent: aria2-rust/1.0"));
578    }
579
580    #[test]
581    fn test_request_to_bytes_with_body() {
582        let url = Url::parse("http://example.com/api").unwrap();
583        let request = HttpRequestBuilder::new(HttpMethod::Post, url)
584            .header("Content-Type", "text/plain")
585            .body(b"Hello, World!".to_vec())
586            .build()
587            .unwrap();
588
589        let bytes = request.to_bytes();
590        let request_str = String::from_utf8_lossy(&bytes);
591
592        assert!(request_str.contains("POST /api HTTP/1.1"));
593        assert!(request_str.contains("Content-Length: 13"));
594        assert!(request_str.ends_with("Hello, World!"));
595    }
596
597    #[test]
598    fn test_response_status_parsing() {
599        // 测试 200 OK
600        let response_200 = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n<body>";
601        let resp = HttpResponse::from_bytes(response_200.as_bytes()).unwrap();
602        assert_eq!(resp.status_code, 200);
603        assert_eq!(resp.reason_phrase, "OK");
604        assert_eq!(resp.version, "HTTP/1.1");
605
606        // 测试 404 Not Found
607        let response_404 = "HTTP/1.1 404 Not Found\r\nContent-Type: text/html\r\n\r\nNot Found";
608        let resp = HttpResponse::from_bytes(response_404.as_bytes()).unwrap();
609        assert_eq!(resp.status_code, 404);
610        assert_eq!(resp.reason_phrase, "Not Found");
611
612        // 测试 301 Moved Permanently
613        let response_301 = "HTTP/1.1 301 Moved Permanently\r\nLocation: /new-url\r\n\r\n";
614        let resp = HttpResponse::from_bytes(response_301.as_bytes()).unwrap();
615        assert_eq!(resp.status_code, 301);
616        assert_eq!(resp.reason_phrase, "Moved Permanently");
617    }
618
619    #[test]
620    fn test_response_multi_value_headers() {
621        let response = "HTTP/1.1 200 OK\r\n\
622                       Set-Cookie: session=abc123; Path=/\r\n\
623                       Set-Cookie: user=john; Domain=example.com\r\n\
624                       Content-Type: text/html\r\n\r\n<body>";
625
626        let resp = HttpResponse::from_bytes(response.as_bytes()).unwrap();
627
628        // 测试获取所有 Set-Cookie 值
629        let all_cookies = resp.header_all("Set-Cookie");
630        assert_eq!(all_cookies.len(), 2);
631        assert!(all_cookies.contains(&"session=abc123; Path=/".to_string()));
632        assert!(all_cookies.contains(&"user=john; Domain=example.com".to_string()));
633
634        // 测试获取第一个值
635        let first_cookie = resp.header("Set-Cookie").unwrap();
636        assert_eq!(first_cookie, "session=abc123; Path=/");
637    }
638
639    #[test]
640    fn test_response_content_length() {
641        let response = "HTTP/1.1 200 OK\r\nContent-Length: 1024\r\n\r\n";
642        let resp = HttpResponse::from_bytes(response.as_bytes()).unwrap();
643
644        assert_eq!(resp.content_length(), Some(1024));
645
646        // 无 Content-Length
647        let response_no_cl = "HTTP/1.1 200 OK\r\n\r\n";
648        let resp_no_cl = HttpResponse::from_bytes(response_no_cl.as_bytes()).unwrap();
649        assert_eq!(resp_no_cl.content_length(), None);
650    }
651
652    #[test]
653    fn test_response_is_redirect() {
654        // 重定向状态码
655        let redirect_resp = HttpResponse::from_bytes(
656            "HTTP/1.1 301 Moved Permanently\r\nLocation: /new\r\n\r\n".as_bytes(),
657        )
658        .unwrap();
659        assert!(redirect_resp.is_redirect());
660
661        let redirect_302 =
662            HttpResponse::from_bytes("HTTP/1.1 302 Found\r\n\r\n".as_bytes()).unwrap();
663        assert!(redirect_302.is_redirect());
664
665        // 非重定向状态码
666        let ok_resp = HttpResponse::from_bytes("HTTP/1.1 200 OK\r\n\r\n".as_bytes()).unwrap();
667        assert!(!ok_resp.is_redirect());
668
669        let error_resp =
670            HttpResponse::from_bytes("HTTP/1.1 500 Internal Server Error\r\n\r\n".as_bytes())
671                .unwrap();
672        assert!(!error_resp.is_redirect());
673    }
674
675    #[test]
676    fn test_response_location() {
677        let response =
678            "HTTP/1.1 301 Moved Permanently\r\nLocation: https://example.com/new-page\r\n\r\n";
679        let resp = HttpResponse::from_bytes(response.as_bytes()).unwrap();
680
681        let location = resp.location().unwrap();
682        assert_eq!(location.as_str(), "https://example.com/new-page");
683    }
684
685    #[test]
686    fn test_response_body_parsing() {
687        let response =
688            "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n{\"status\":\"success\"}";
689        let resp = HttpResponse::from_bytes(response.as_bytes()).unwrap();
690
691        assert!(resp.body.is_some());
692        assert_eq!(resp.body.unwrap(), b"{\"status\":\"success\"}");
693
694        // 无 body
695        let response_no_body = "HTTP/1.1 204 No Content\r\n\r\n";
696        let resp_no_body = HttpResponse::from_bytes(response_no_body.as_bytes()).unwrap();
697        assert!(resp_no_body.body.is_none());
698    }
699
700    #[test]
701    fn test_basic_auth_header_generation() {
702        // Test basic Base64 encoding
703        let auth = basic_auth("user", "pass");
704        assert_eq!(auth, "Basic dXNlcjpwYXNz");
705
706        // Test special characters
707        let auth_special = basic_auth("admin@email.com", "p@ssw0rd!");
708        // 验证格式正确
709        assert!(auth_special.starts_with("Basic "));
710        // 验证可以解码回原始凭证
711        let encoded = &auth_special["Basic ".len()..];
712        let decoded = String::from_utf8(
713            general_purpose::STANDARD
714                .decode(encoded)
715                .unwrap_or_default(),
716        )
717        .unwrap_or_default();
718        assert_eq!(decoded, "admin@email.com:p@ssw0rd!");
719
720        // 测试空密码
721        let auth_empty_pass = basic_auth("user", "");
722        assert!(auth_empty_pass.starts_with("Basic "));
723    }
724
725    #[test]
726    fn test_bearer_token_generation() {
727        let token = bearer_token("my-access-token-12345");
728        assert_eq!(token, "Bearer my-access-token-12345");
729    }
730
731    #[test]
732    fn test_http_method_display() {
733        assert_eq!(HttpMethod::Get.to_string(), "GET");
734        assert_eq!(HttpMethod::Post.to_string(), "POST");
735        assert_eq!(HttpMethod::Head.to_string(), "HEAD");
736        assert_eq!(HttpMethod::Put.to_string(), "PUT");
737        assert_eq!(HttpMethod::Delete.to_string(), "DELETE");
738    }
739
740    #[test]
741    fn test_request_builder_batch_headers() {
742        let url = Url::parse("http://example.com/api").unwrap();
743        let mut custom_headers = HashMap::new();
744        custom_headers.insert("X-Custom-1".to_string(), "value1".to_string());
745        custom_headers.insert("X-Custom-2".to_string(), "value2".to_string());
746
747        let request = HttpRequestBuilder::new(HttpMethod::Get, url)
748            .headers(custom_headers.clone())
749            .build()
750            .unwrap();
751
752        assert_eq!(request.headers.get("X-Custom-1").unwrap(), "value1");
753        assert_eq!(request.headers.get("X-Custom-2").unwrap(), "value2");
754    }
755
756    #[test]
757    fn test_response_case_insensitive_headers() {
758        let response = "HTTP/1.1 200 OK\r\n\
759                       Content-Type: text/html\r\n\
760                       content-length: 100\r\n\r\n";
761
762        let resp = HttpResponse::from_bytes(response.as_bytes()).unwrap();
763
764        // 大小写不敏感查找
765        assert!(resp.header("content-type").is_some());
766        assert!(resp.header("CONTENT-TYPE").is_some());
767        assert!(resp.header("Content-Length").is_some());
768    }
769}