Skip to main content

zenith_http1/
types.rs

1//! HTTP/1.1 类型定义
2
3use std::fmt;
4
5use smallvec::SmallVec;
6
7/// 单个已解析头部:名称(小写规范化)与值,均为 `Box<str>`
8/// (§6.1.1:替代 `String` 节省 8 字节容量字段,无后续增长需求)。
9pub type HeaderEntry = (Box<str>, Box<str>);
10
11/// 已解析头部列表:`SmallVec` 栈内 16 条目(§6.1.1 热路径零堆分配:
12/// 绝大多数请求头部数 ≤16 时整个列表无堆分配)。
13pub type HeaderList = SmallVec<[HeaderEntry; 16]>;
14
15/// HTTP/1.1 解析器配置
16#[derive(Debug, Clone)]
17pub struct Http1Config {
18    /// 最大请求行大小(字节)
19    pub max_request_line_size: usize,
20    /// 最大请求头大小(字节)
21    pub max_header_size: usize,
22    /// 最大请求头数量
23    pub max_header_count: usize,
24    /// 最大请求体大小(字节)
25    pub max_body_size: usize,
26    /// 是否启用 keep-alive
27    pub keep_alive: bool,
28    /// 每个头部名称最大长度
29    pub max_header_name_len: usize,
30    /// 每个头部值最大长度
31    pub max_header_value_len: usize,
32    /// 请求空闲超时(毫秒),防止 Slowloris 慢速攻击
33    pub idle_timeout_ms: u64,
34    /// 解析器内部缓冲区最大容量(字节),累积未完成请求的上限
35    pub max_buffer_size: usize,
36}
37
38impl Http1Config {
39    /// 创建新的 HTTP/1.1 配置
40    #[inline]
41    pub fn new() -> Self {
42        Self {
43            max_request_line_size: 8192,
44            max_header_size: 8192,
45            max_header_count: 256,
46            max_body_size: 1_048_576,
47            keep_alive: true,
48            max_header_name_len: 64,
49            max_header_value_len: 8192,
50            idle_timeout_ms: 30_000,
51            max_buffer_size: 65_536,
52        }
53    }
54
55    /// 设置最大头部大小
56    #[inline]
57    pub fn with_max_header_size(mut self, size: usize) -> Self {
58        self.max_header_size = size;
59        self
60    }
61
62    /// 设置最大请求体大小
63    #[inline]
64    pub fn with_max_body_size(mut self, size: usize) -> Self {
65        self.max_body_size = size;
66        self
67    }
68
69    /// 设置最大头部数量
70    #[inline]
71    pub fn with_max_header_count(mut self, n: usize) -> Self {
72        self.max_header_count = n;
73        self
74    }
75
76    /// 设置空闲超时(毫秒),防止 Slowloris 慢速攻击
77    #[inline]
78    pub fn with_idle_timeout_ms(mut self, ms: u64) -> Self {
79        self.idle_timeout_ms = ms;
80        self
81    }
82
83    /// 设置解析器缓冲区最大容量(字节)
84    #[inline]
85    pub fn with_max_buffer_size(mut self, size: usize) -> Self {
86        self.max_buffer_size = size;
87        self
88    }
89}
90
91impl Default for Http1Config {
92    fn default() -> Self {
93        Self::new()
94    }
95}
96
97/// HTTP/1.1 错误类型
98#[derive(Debug, Clone, PartialEq, Eq)]
99pub enum Http1Error {
100    /// 语法错误
101    SyntaxError(String),
102    /// 头部过大
103    HeaderTooLarge,
104    /// 请求行过长(M-3:完整行与未完成行统一语义)
105    RequestLineTooLong,
106    /// 头部过多
107    TooManyHeaders,
108    /// 请求体过大
109    BodyTooLarge,
110    /// 缺少 Host 头
111    MissingHost,
112    /// 方法不支持
113    UnsupportedMethod(String),
114    /// 版本不支持
115    UnsupportedVersion(String),
116    /// 请求走私检测
117    SmugglingDetected(String),
118    /// 分块编码错误
119    ChunkedError(String),
120    /// 协议不一致(Content-Length / Transfer-Encoding 共存等)
121    ProtocolInconsistency(String),
122    /// 连接已关闭
123    ConnectionClosed,
124    /// 数据不足
125    NeedMoreData,
126    /// 空闲超时(Slowloris 防护)
127    IdleTimeout,
128    /// 缓冲区溢出(累积数据超过上限)
129    BufferOverflow,
130    /// 内部错误
131    Internal(String),
132}
133
134impl fmt::Display for Http1Error {
135    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136        match self {
137            Self::SyntaxError(m) => write!(f, "HTTP 语法错误: {m}"),
138            Self::HeaderTooLarge => write!(f, "HTTP 头部过大"),
139            Self::RequestLineTooLong => write!(f, "HTTP 请求行过长"),
140            Self::TooManyHeaders => write!(f, "HTTP 头部数量超限"),
141            Self::BodyTooLarge => write!(f, "HTTP 请求体过大"),
142            Self::MissingHost => write!(f, "HTTP 缺少 Host 头"),
143            Self::UnsupportedMethod(m) => write!(f, "HTTP 方法不支持: {m}"),
144            Self::UnsupportedVersion(v) => write!(f, "HTTP 版本不支持: {v}"),
145            Self::SmugglingDetected(m) => write!(f, "HTTP 请求走私: {m}"),
146            Self::ChunkedError(m) => write!(f, "HTTP 分块编码错误: {m}"),
147            Self::ProtocolInconsistency(m) => write!(f, "HTTP 协议不一致: {m}"),
148            Self::ConnectionClosed => write!(f, "HTTP 连接已关闭"),
149            Self::NeedMoreData => write!(f, "HTTP 数据不足"),
150            Self::IdleTimeout => write!(f, "HTTP 空闲超时 (Slowloris 防护)"),
151            Self::BufferOverflow => write!(f, "HTTP 缓冲区溢出"),
152            Self::Internal(m) => write!(f, "HTTP 内部错误: {m}"),
153        }
154    }
155}
156
157impl std::error::Error for Http1Error {}
158
159/// HTTP/1.1 请求行
160#[derive(Debug, Clone, PartialEq, Eq)]
161pub struct HttpRequestLine {
162    /// 方法(原始大写)
163    pub method: Box<str>,
164    /// 请求目标
165    pub target: Box<str>,
166    /// HTTP 版本
167    pub version: Box<str>,
168}
169
170/// HTTP/1.1 请求
171#[derive(Debug, Clone)]
172pub struct HttpRequest {
173    /// 请求行
174    pub line: HttpRequestLine,
175    /// 请求头(名称已规范化为小写)
176    pub headers: HeaderList,
177    /// 请求体(未解析原始字节)
178    pub body: Vec<u8>,
179    /// 长度已确认(Content-Length)
180    pub content_length: Option<u64>,
181    /// 是否 chunked 编码
182    pub chunked: bool,
183    /// 是否为 keep-alive
184    pub keep_alive: bool,
185}
186
187impl HttpRequest {
188    /// 创建新请求
189    ///
190    /// keep_alive 默认值按 RFC 7230 §6.3 / RFC 1945 §1.3:
191    /// - HTTP/1.0 默认关闭连接(仅显式 `Connection: keep-alive` 才保持)
192    /// - HTTP/1.1 默认保持(仅显式 `Connection: close` 才关闭)
193    #[inline]
194    pub fn new(method: Box<str>, target: Box<str>, version: Box<str>) -> Self {
195        let keep_alive = &*version != "HTTP/1.0";
196        Self {
197            line: HttpRequestLine {
198                method,
199                target,
200                version,
201            },
202            headers: SmallVec::new(),
203            body: Vec::new(),
204            content_length: None,
205            chunked: false,
206            keep_alive,
207        }
208    }
209
210    /// 查找头部值(按规范化小写名称)
211    #[inline]
212    pub fn get_header<'a>(&'a self, name: &str) -> Option<&'a str> {
213        let name = name.to_ascii_lowercase();
214        self.headers
215            .iter()
216            .find(|(k, _)| k.as_ref() == name.as_str())
217            .map(|(_, v)| v.as_ref())
218    }
219
220    /// 获取 Host
221    #[inline]
222    pub fn host(&self) -> Option<&str> {
223        self.get_header("host")
224    }
225}
226
227#[cfg(test)]
228mod tests {
229    use super::*;
230
231    #[test]
232    fn test_config_default() {
233        let c = Http1Config::new();
234        assert_eq!(c.max_header_size, 8192);
235        assert_eq!(c.max_body_size, 1_048_576);
236        assert_eq!(c.max_header_count, 256);
237        assert!(c.keep_alive);
238    }
239
240    #[test]
241    fn test_config_custom() {
242        let c = Http1Config::new()
243            .with_max_header_size(4096)
244            .with_max_body_size(524_288)
245            .with_max_header_count(128);
246        assert_eq!(c.max_header_size, 4096);
247        assert_eq!(c.max_body_size, 524_288);
248        assert_eq!(c.max_header_count, 128);
249    }
250
251    #[test]
252    fn test_error_display() {
253        assert_eq!(
254            Http1Error::MissingHost.to_string(),
255            "HTTP 缺少 Host 头"
256        );
257        assert!(matches!(Http1Error::SmugglingDetected("CL.TE".into()), Http1Error::SmugglingDetected(_)));
258    }
259
260    #[test]
261    fn test_request_get_header() {
262        let mut req = HttpRequest::new("GET".into(), "/".into(), "HTTP/1.1".into());
263        req.headers.push(("host".into(), "example.com".into()));
264        req.headers.push(("content-length".into(), "0".into()));
265        assert_eq!(req.get_header("host"), Some("example.com"));
266        assert_eq!(req.get_header("Content-Length"), Some("0"));
267        assert_eq!(req.get_header("missing"), None);
268    }
269
270    #[test]
271    fn test_http1_error_all_variants_display() {
272        let errors = vec![
273            Http1Error::SyntaxError("test".into()),
274            Http1Error::HeaderTooLarge,
275            Http1Error::RequestLineTooLong,
276            Http1Error::TooManyHeaders,
277            Http1Error::BodyTooLarge,
278            Http1Error::MissingHost,
279            Http1Error::UnsupportedMethod("FOO".into()),
280            Http1Error::UnsupportedVersion("HTTP/3.0".into()),
281            Http1Error::SmugglingDetected("test".into()),
282            Http1Error::ChunkedError("test".into()),
283            Http1Error::ProtocolInconsistency("test".into()),
284            Http1Error::ConnectionClosed,
285            Http1Error::NeedMoreData,
286            Http1Error::Internal("test".into()),
287        ];
288        for e in errors {
289            let s = e.to_string();
290            assert!(!s.is_empty());
291        }
292    }
293
294    #[test]
295    fn test_http_request_new() {
296        let req = HttpRequest::new("POST".into(), "/api".into(), "HTTP/1.1".into());
297        assert_eq!(req.line.method.as_ref(), "POST");
298        assert_eq!(req.line.target.as_ref(), "/api");
299        assert_eq!(req.line.version.as_ref(), "HTTP/1.1");
300        assert!(req.headers.is_empty());
301        assert!(req.body.is_empty());
302        assert_eq!(req.content_length, None);
303        assert!(!req.chunked);
304        assert!(req.keep_alive);
305    }
306
307    #[test]
308    fn test_http_request_new_http10_default_close() {
309        // RFC 1945:HTTP/1.0 默认关闭连接
310        let req = HttpRequest::new("GET".into(), "/".into(), "HTTP/1.0".into());
311        assert!(!req.keep_alive, "HTTP/1.0 默认 keep_alive 必须为 false");
312    }
313
314    #[test]
315    fn test_http_request_host() {
316        let mut req = HttpRequest::new("GET".into(), "/".into(), "HTTP/1.1".into());
317        assert_eq!(req.host(), None);
318        req.headers.push(("host".into(), "example.com".into()));
319        assert_eq!(req.host(), Some("example.com"));
320    }
321
322    #[test]
323    fn test_http_request_line_clone() {
324        let line = HttpRequestLine {
325            method: "GET".into(),
326            target: "/".into(),
327            version: "HTTP/1.1".into(),
328        };
329        let line2 = line.clone();
330        assert_eq!(line, line2);
331        assert_eq!(format!("{:?}", line), format!("{:?}", line2));
332    }
333
334    #[test]
335    fn test_config_default_trait() {
336        let c1 = Http1Config::new();
337        let c2 = Http1Config::default();
338        assert_eq!(c1.max_header_size, c2.max_header_size);
339        assert_eq!(c1.max_body_size, c2.max_body_size);
340    }
341
342    #[test]
343    fn test_error_debug_and_clone() {
344        let e = Http1Error::SyntaxError("test".into());
345        let e2 = e.clone();
346        assert_eq!(e, e2);
347        let _ = format!("{:?}", e);
348    }
349}