use bytes::Bytes;
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct HttpRequest {
pub method: String,
pub path: String,
pub version: HttpVersion,
pub headers: Vec<(String, Vec<u8>)>,
pub body: Bytes,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct HttpResponse {
pub status: u16,
pub reason: String,
pub version: HttpVersion,
pub headers: Vec<(String, Vec<u8>)>,
pub body: Bytes,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
pub enum HttpVersion {
Http1_0,
Http1_1,
}
impl HttpRequest {
pub fn host(&self) -> Option<&str> {
self.header_str("host")
}
pub fn user_agent(&self) -> Option<&str> {
self.header_str("user-agent")
}
pub fn cookie(&self) -> Option<&str> {
self.header_str("cookie")
}
pub fn referer(&self) -> Option<&str> {
self.header_str("referer")
}
pub fn accept(&self) -> Option<&str> {
self.header_str("accept")
}
pub fn content_type(&self) -> Option<&str> {
self.header_str("content-type")
}
pub fn content_length(&self) -> Option<u64> {
self.header_str("content-length")
.and_then(|v| v.trim().parse().ok())
}
pub fn header(&self, name: &str) -> Option<&[u8]> {
header_lookup(&self.headers, name).next()
}
pub fn headers_all<'a>(&'a self, name: &'a str) -> impl Iterator<Item = &'a [u8]> + 'a {
header_lookup(&self.headers, name)
}
fn header_str(&self, name: &str) -> Option<&str> {
self.header(name).and_then(|v| std::str::from_utf8(v).ok())
}
}
impl HttpResponse {
pub fn content_type(&self) -> Option<&str> {
self.header_str("content-type")
}
pub fn content_length(&self) -> Option<u64> {
self.header_str("content-length")
.and_then(|v| v.trim().parse().ok())
}
pub fn set_cookie(&self) -> impl Iterator<Item = &str> + '_ {
self.headers_all("set-cookie")
.filter_map(|v| std::str::from_utf8(v).ok())
}
pub fn status_class(&self) -> Option<u8> {
let cls = self.status / 100;
if (1..=5).contains(&cls) {
Some(cls as u8)
} else {
None
}
}
pub fn is_success(&self) -> bool {
self.status_class() == Some(2)
}
pub fn is_redirect(&self) -> bool {
self.status_class() == Some(3)
}
pub fn is_client_error(&self) -> bool {
self.status_class() == Some(4)
}
pub fn is_server_error(&self) -> bool {
self.status_class() == Some(5)
}
pub fn header(&self, name: &str) -> Option<&[u8]> {
header_lookup(&self.headers, name).next()
}
pub fn headers_all<'a>(&'a self, name: &'a str) -> impl Iterator<Item = &'a [u8]> + 'a {
header_lookup(&self.headers, name)
}
fn header_str(&self, name: &str) -> Option<&str> {
self.header(name).and_then(|v| std::str::from_utf8(v).ok())
}
}
fn header_lookup<'h, 'n>(
headers: &'h [(String, Vec<u8>)],
name: &'n str,
) -> impl Iterator<Item = &'h [u8]> + use<'h, 'n> {
headers
.iter()
.filter(move |(k, _)| k.eq_ignore_ascii_case(name))
.map(|(_, v)| v.as_slice())
}
#[derive(Debug, Clone)]
pub struct HttpConfig {
pub max_buffer: usize,
pub max_headers: usize,
}
impl Default for HttpConfig {
fn default() -> Self {
Self {
max_buffer: 1024 * 1024, max_headers: 64,
}
}
}