use std::collections::HashMap;
use crate::{Error, http::Method};
use url::Url;
#[derive(Clone)]
pub struct Request {
pub(crate) method: Method,
pub(crate) url: Url,
pub(crate) variable_indices: Vec<usize>,
pub(crate) depth: usize,
pub headers: HashMap<String, Vec<String>>,
pub(crate) header_size: usize,
pub(crate) addr: std::net::SocketAddr,
pub(crate) content: Vec<u8>
}
impl Request {
pub fn method(&self) -> &Method {
&self.method
}
pub fn url(&self) -> &Url {
&self.url
}
pub fn address(&self) -> std::net::SocketAddr {
self.addr
}
pub fn body(&self) -> &Vec<u8> {
&self.content
}
pub(crate) fn parse(mut source: Vec<u8>, addr: std::net::SocketAddr) -> Result<Request, Error> {
let (one, two) = (source.iter(), source.iter().skip(2));
let mut split_index = None;
for (idx, (a, b)) in one.zip(two).enumerate() {
if a==b && b==&b'\n' && idx > 0 && source[idx-1] == b'\r' && source[idx+1] == b'\r' {
split_index = Some(idx);
break;
}
}
let split_index = split_index.ok_or(Error::Parse(format!("no end of header was found")))?;
let mut content: Vec<_> = source.drain((split_index - 1)..).collect();
content.drain(..4);
let header_size = source.len() + 4;
let request_string = String::from_utf8(source).map_err(|e| Error::Parse(format!("{}", e)))?;
let mut lines = request_string.split("\r\n");
let first_line = lines.next().ok_or(Error::Parse("request has no first line".into()))?;
let tokens = first_line.split(" ").collect::<Vec<_>>();
let (method, path, version) = if tokens.len() != 3 {
return Err(Error::Parse("request's first has incorrect format".into()));
} else {
(
tokens[0].into(), tokens[1],
tokens[2]
)
};
let mut headers = HashMap::new();
for line in lines {
let idx = line.find(":").ok_or(Error::Parse(format!("corrupted header missing colon")))?;
let (key, value) = line.split_at(idx);
let (key, value) = (key.to_string(), value.trim_start_matches(": ").trim_end().to_string());
headers.entry(key).or_insert_with(|| Vec::new()).push(value);
}
if !version.starts_with("HTTP") {
return Err(Error::Parse("unsupported protocol".into()))
}
let host = headers.get("Host").map(|h| h.get(0).map(|v| &v[..])).flatten().unwrap_or_else(|| "missing.host");
let url = Url::parse(&format!("http://{}{}", host, path)).map_err(Error::Url)?;
Ok(Request {
method,
url,
variable_indices: vec![],
depth: 0,
headers,
header_size,
addr,
content
})
}
}
pub struct BasicRequest {
method: Method,
url: Url,
headers: HashMap<String, Vec<String>>,
content: Option<Vec<u8>>
}
impl BasicRequest {
pub fn new<A: AsRef<str>>(method: Method, url: A) -> Result<BasicRequest, Error> {
Ok(BasicRequest {
method,
url: Url::parse(url.as_ref()).map_err(Error::Url)?,
headers: HashMap::new(),
content: None
})
}
pub fn header<A: Into<String>, B: Into<String>>(mut self, key: A, value: B) -> Self {
self.headers.entry(key.into()).or_insert_with(|| Vec::new()).push(value.into());
self
}
pub fn content<A: Into<Vec<u8>>>(mut self, content: A) -> Self {
self.content = Some(content.into());
self
}
pub fn serialize(&self) -> Vec<u8> {
let mut content = String::new();
let mut path_with_query = self.url.path().to_string();
if let Some(query) = self.url.query() {
path_with_query += &format!("?{}", query);
}
content += &format!("{} {} HTTP/1.1\r\n", self.method, path_with_query);
for (header_name, header_contents) in &self.headers {
for header_content in header_contents {
content += &format!("{}: {}\r\n", header_name, header_content);
}
}
content += "\r\n";
let mut content = content.into_bytes();
if let Some(final_content) = &self.content {
content.extend_from_slice(final_content);
}
content
}
}