use {
crate::error::RequestError,
std::{
convert::TryFrom,
fmt,
io::{BufRead, BufReader, Read},
net::{IpAddr, TcpStream},
},
};
#[derive(Clone)]
pub struct Request {
pub host: String,
pub path: String,
pub query: Option<String>,
pub client_ip: IpAddr,
pub length: usize,
pub content: Option<Vec<u8>>,
}
impl fmt::Display for Request {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Request: {{ host: {}; path: {}; query: {}; client_ip: {}; length: {}; }}",
&self.host,
&self.path,
self.query
.as_ref()
.map_or("none", std::string::String::as_str),
self.client_ip,
self.length,
)
}
}
impl TryFrom<&TcpStream> for Request {
type Error = RequestError;
fn try_from(stream: &TcpStream) -> Result<Self, Self::Error> {
let mut reader = BufReader::new(stream);
let mut request_header = String::new();
reader.read_line(&mut request_header)?;
let parts: Vec<&str> = request_header.split_whitespace().collect();
match parts.len() {
1 => Err(RequestError::MissingSeparator),
2 => Err(RequestError::MissingField),
3 => {
let length: usize = match parts[2].parse() {
Ok(l) => l,
Err(_) => return Err(RequestError::InvalidContentLength),
};
let content = match length {
0 => None,
length => {
let mut buf = vec![0; length];
reader.read_exact(&mut buf)?;
Some(buf)
}
};
let url = urlencoding::decode(parts[1])?;
let (mut path, query) = if let Some((p, q)) = url.split_once('?') {
(p.to_string(), Some(q.to_string()))
} else {
(url.to_string(), None)
};
if path.is_empty() {
path.push('/');
}
let client_ip = stream.peer_addr()?.ip();
Ok(Self {
host: parts[0].to_string(),
path,
query,
client_ip,
length,
content,
})
}
_ => Err(RequestError::ExtraField),
}
}
}