pub trait HttpHead {
fn version(&self) -> &str;
fn headers(&self) -> &[(String, String)];
fn get_header(&self, name: &str) -> Option<&str> {
self.headers()
.iter()
.find(|(n, _)| n.eq_ignore_ascii_case(name))
.map(|(_, v)| v.as_str())
}
fn get_headers(&self, name: &str) -> Vec<&str> {
self.headers()
.iter()
.filter(|(n, _)| n.eq_ignore_ascii_case(name))
.map(|(_, v)| v.as_str())
.collect()
}
fn has_header(&self, name: &str) -> bool {
self.headers()
.iter()
.any(|(n, _)| n.eq_ignore_ascii_case(name))
}
fn connection(&self) -> Option<&str> {
self.get_header("Connection")
}
fn is_keep_alive(&self) -> bool {
let connection_headers = self.get_headers("Connection");
let mut has_keep_alive = false;
for conn in connection_headers {
for token in conn.split(',') {
let token = token.trim();
if token.eq_ignore_ascii_case("close") {
return false;
}
if token.eq_ignore_ascii_case("keep-alive") {
has_keep_alive = true;
}
}
}
if has_keep_alive {
return true;
}
self.version().ends_with("/1.1")
}
fn content_length(&self) -> Option<usize> {
self.get_header("Content-Length")
.and_then(|v| v.parse().ok())
}
fn is_chunked(&self) -> bool {
let mut last_token: Option<&str> = None;
for te in self.get_headers("Transfer-Encoding") {
for token in te.split(',') {
let token = token.trim();
if !token.is_empty() {
last_token = Some(token);
}
}
}
last_token.is_some_and(|t| t.eq_ignore_ascii_case("chunked"))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RequestHead {
pub method: String,
pub uri: String,
pub version: String,
pub headers: Vec<(String, String)>,
}
impl HttpHead for RequestHead {
fn version(&self) -> &str {
&self.version
}
fn headers(&self) -> &[(String, String)] {
&self.headers
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ResponseHead {
pub version: String,
pub status_code: u16,
pub reason_phrase: String,
pub headers: Vec<(String, String)>,
}
impl HttpHead for ResponseHead {
fn version(&self) -> &str {
&self.version
}
fn headers(&self) -> &[(String, String)] {
&self.headers
}
}
impl ResponseHead {
pub fn is_success(&self) -> bool {
(200..300).contains(&self.status_code)
}
pub fn is_redirect(&self) -> bool {
(300..400).contains(&self.status_code)
}
pub fn is_client_error(&self) -> bool {
(400..500).contains(&self.status_code)
}
pub fn is_server_error(&self) -> bool {
(500..600).contains(&self.status_code)
}
pub fn is_informational(&self) -> bool {
(100..200).contains(&self.status_code)
}
}