use std::fmt;
use smallvec::SmallVec;
pub type HeaderEntry = (Box<str>, Box<str>);
pub type HeaderList = SmallVec<[HeaderEntry; 16]>;
#[derive(Debug, Clone)]
pub struct Http1Config {
pub max_request_line_size: usize,
pub max_header_size: usize,
pub max_header_count: usize,
pub max_body_size: usize,
pub keep_alive: bool,
pub max_header_name_len: usize,
pub max_header_value_len: usize,
pub idle_timeout_ms: u64,
pub max_buffer_size: usize,
}
impl Http1Config {
#[inline]
pub fn new() -> Self {
Self {
max_request_line_size: 8192,
max_header_size: 8192,
max_header_count: 256,
max_body_size: 1_048_576,
keep_alive: true,
max_header_name_len: 64,
max_header_value_len: 8192,
idle_timeout_ms: 30_000,
max_buffer_size: 65_536,
}
}
#[inline]
pub fn with_max_header_size(mut self, size: usize) -> Self {
self.max_header_size = size;
self
}
#[inline]
pub fn with_max_body_size(mut self, size: usize) -> Self {
self.max_body_size = size;
self
}
#[inline]
pub fn with_max_header_count(mut self, n: usize) -> Self {
self.max_header_count = n;
self
}
#[inline]
pub fn with_idle_timeout_ms(mut self, ms: u64) -> Self {
self.idle_timeout_ms = ms;
self
}
#[inline]
pub fn with_max_buffer_size(mut self, size: usize) -> Self {
self.max_buffer_size = size;
self
}
}
impl Default for Http1Config {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Http1Error {
SyntaxError(String),
HeaderTooLarge,
RequestLineTooLong,
TooManyHeaders,
BodyTooLarge,
MissingHost,
UnsupportedMethod(String),
UnsupportedVersion(String),
SmugglingDetected(String),
ChunkedError(String),
ProtocolInconsistency(String),
ConnectionClosed,
NeedMoreData,
IdleTimeout,
BufferOverflow,
Internal(String),
}
impl fmt::Display for Http1Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::SyntaxError(m) => write!(f, "HTTP 语法错误: {m}"),
Self::HeaderTooLarge => write!(f, "HTTP 头部过大"),
Self::RequestLineTooLong => write!(f, "HTTP 请求行过长"),
Self::TooManyHeaders => write!(f, "HTTP 头部数量超限"),
Self::BodyTooLarge => write!(f, "HTTP 请求体过大"),
Self::MissingHost => write!(f, "HTTP 缺少 Host 头"),
Self::UnsupportedMethod(m) => write!(f, "HTTP 方法不支持: {m}"),
Self::UnsupportedVersion(v) => write!(f, "HTTP 版本不支持: {v}"),
Self::SmugglingDetected(m) => write!(f, "HTTP 请求走私: {m}"),
Self::ChunkedError(m) => write!(f, "HTTP 分块编码错误: {m}"),
Self::ProtocolInconsistency(m) => write!(f, "HTTP 协议不一致: {m}"),
Self::ConnectionClosed => write!(f, "HTTP 连接已关闭"),
Self::NeedMoreData => write!(f, "HTTP 数据不足"),
Self::IdleTimeout => write!(f, "HTTP 空闲超时 (Slowloris 防护)"),
Self::BufferOverflow => write!(f, "HTTP 缓冲区溢出"),
Self::Internal(m) => write!(f, "HTTP 内部错误: {m}"),
}
}
}
impl std::error::Error for Http1Error {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HttpRequestLine {
pub method: Box<str>,
pub target: Box<str>,
pub version: Box<str>,
}
#[derive(Debug, Clone)]
pub struct HttpRequest {
pub line: HttpRequestLine,
pub headers: HeaderList,
pub body: Vec<u8>,
pub content_length: Option<u64>,
pub chunked: bool,
pub keep_alive: bool,
}
impl HttpRequest {
#[inline]
pub fn new(method: Box<str>, target: Box<str>, version: Box<str>) -> Self {
let keep_alive = &*version != "HTTP/1.0";
Self {
line: HttpRequestLine {
method,
target,
version,
},
headers: SmallVec::new(),
body: Vec::new(),
content_length: None,
chunked: false,
keep_alive,
}
}
#[inline]
pub fn get_header<'a>(&'a self, name: &str) -> Option<&'a str> {
let name = name.to_ascii_lowercase();
self.headers
.iter()
.find(|(k, _)| k.as_ref() == name.as_str())
.map(|(_, v)| v.as_ref())
}
#[inline]
pub fn host(&self) -> Option<&str> {
self.get_header("host")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config_default() {
let c = Http1Config::new();
assert_eq!(c.max_header_size, 8192);
assert_eq!(c.max_body_size, 1_048_576);
assert_eq!(c.max_header_count, 256);
assert!(c.keep_alive);
}
#[test]
fn test_config_custom() {
let c = Http1Config::new()
.with_max_header_size(4096)
.with_max_body_size(524_288)
.with_max_header_count(128);
assert_eq!(c.max_header_size, 4096);
assert_eq!(c.max_body_size, 524_288);
assert_eq!(c.max_header_count, 128);
}
#[test]
fn test_error_display() {
assert_eq!(
Http1Error::MissingHost.to_string(),
"HTTP 缺少 Host 头"
);
assert!(matches!(Http1Error::SmugglingDetected("CL.TE".into()), Http1Error::SmugglingDetected(_)));
}
#[test]
fn test_request_get_header() {
let mut req = HttpRequest::new("GET".into(), "/".into(), "HTTP/1.1".into());
req.headers.push(("host".into(), "example.com".into()));
req.headers.push(("content-length".into(), "0".into()));
assert_eq!(req.get_header("host"), Some("example.com"));
assert_eq!(req.get_header("Content-Length"), Some("0"));
assert_eq!(req.get_header("missing"), None);
}
#[test]
fn test_http1_error_all_variants_display() {
let errors = vec![
Http1Error::SyntaxError("test".into()),
Http1Error::HeaderTooLarge,
Http1Error::RequestLineTooLong,
Http1Error::TooManyHeaders,
Http1Error::BodyTooLarge,
Http1Error::MissingHost,
Http1Error::UnsupportedMethod("FOO".into()),
Http1Error::UnsupportedVersion("HTTP/3.0".into()),
Http1Error::SmugglingDetected("test".into()),
Http1Error::ChunkedError("test".into()),
Http1Error::ProtocolInconsistency("test".into()),
Http1Error::ConnectionClosed,
Http1Error::NeedMoreData,
Http1Error::Internal("test".into()),
];
for e in errors {
let s = e.to_string();
assert!(!s.is_empty());
}
}
#[test]
fn test_http_request_new() {
let req = HttpRequest::new("POST".into(), "/api".into(), "HTTP/1.1".into());
assert_eq!(req.line.method.as_ref(), "POST");
assert_eq!(req.line.target.as_ref(), "/api");
assert_eq!(req.line.version.as_ref(), "HTTP/1.1");
assert!(req.headers.is_empty());
assert!(req.body.is_empty());
assert_eq!(req.content_length, None);
assert!(!req.chunked);
assert!(req.keep_alive);
}
#[test]
fn test_http_request_new_http10_default_close() {
let req = HttpRequest::new("GET".into(), "/".into(), "HTTP/1.0".into());
assert!(!req.keep_alive, "HTTP/1.0 默认 keep_alive 必须为 false");
}
#[test]
fn test_http_request_host() {
let mut req = HttpRequest::new("GET".into(), "/".into(), "HTTP/1.1".into());
assert_eq!(req.host(), None);
req.headers.push(("host".into(), "example.com".into()));
assert_eq!(req.host(), Some("example.com"));
}
#[test]
fn test_http_request_line_clone() {
let line = HttpRequestLine {
method: "GET".into(),
target: "/".into(),
version: "HTTP/1.1".into(),
};
let line2 = line.clone();
assert_eq!(line, line2);
assert_eq!(format!("{:?}", line), format!("{:?}", line2));
}
#[test]
fn test_config_default_trait() {
let c1 = Http1Config::new();
let c2 = Http1Config::default();
assert_eq!(c1.max_header_size, c2.max_header_size);
assert_eq!(c1.max_body_size, c2.max_body_size);
}
#[test]
fn test_error_debug_and_clone() {
let e = Http1Error::SyntaxError("test".into());
let e2 = e.clone();
assert_eq!(e, e2);
let _ = format!("{:?}", e);
}
}