pub mod handler;
pub mod encoding;
mod status;
mod method;
mod parse;
use parse::parse_request;
pub use method::RequestMethod;
use std::{collections::HashMap, env, ffi::OsStr, io::{BufReader, Read, Write}, net::TcpStream, path::Path};
use crate::Result;
use crate::request::encoding::Chunked;
pub struct HttpRequest {
method: RequestMethod,
url: String,
headers: HashMap<String,String>,
params: HashMap<String,String>,
response_headers: HashMap<String,String>,
version: f32,
stream: BufReader<TcpStream>,
status: u16,
}
impl HttpRequest {
pub fn parse(stream: TcpStream) -> Result<Self> {
let stream = BufReader::new(stream);
parse_request(stream)
}
#[inline]
pub fn keep_alive(self) -> Result<Self> {
let mut req = parse_request(self.stream)?;
req.set_header("Connection", "keep-alive");
Ok(req)
}
#[inline]
pub fn stream(&self) -> &TcpStream { self.stream.get_ref() }
#[inline]
pub fn url(&self) -> &str { &self.url }
#[inline]
pub fn set_url(&mut self, url: String) { self.url = url; }
#[inline]
pub fn params(&self) -> &HashMap<String,String> { &self.params }
#[inline]
pub fn param(&self, key: &str) -> Option<&str> { self.params.get(key).map(|s| s.as_str()) }
pub fn filename(&self) -> Result<String> {
let mut cwd = env::current_dir()?;
cwd.push(
Path::new(
OsStr::new(&self.url[1..])
)
);
let cwd = cwd.to_str().ok_or_else(|| "Error getting cwd")?;
Ok(cwd.to_owned())
}
#[inline]
pub fn method(&self) -> &RequestMethod { &self.method }
#[inline]
pub fn status(&self) -> u16 { self.status }
#[inline]
pub fn set_status(&mut self, status: u16) -> &mut Self {
self.status = status;
self
}
#[inline]
pub fn version(&self) -> f32 { self.version }
pub fn content_length(&self) -> usize {
match self.headers.get("Content-Length") {
Some(l) => l.parse().unwrap_or(0),
None => 0,
}
}
#[inline]
pub fn header(&self, key: &str) -> Option<&str> {
self.headers.get(key).map(|s| s.as_str())
}
#[inline]
pub fn headers(&self) -> &HashMap<String,String> { &self.headers }
#[inline]
pub fn set_header<V: ToString>(&mut self, key: &str, value: V) {
self.response_headers.insert(key.to_string(), value.to_string());
}
pub fn data(&mut self) -> Vec<u8> {
let len = self.content_length();
let mut buf:Vec<u8> = Vec::with_capacity(len);
buf.resize(len, 0);
self.stream.read_exact(&mut buf).unwrap();
buf
}
pub fn read_data(&mut self, writer: &mut dyn Write) -> Result<()> {
const CHUNK_SIZE:usize = 1024 * 1024;
let mut buf:[u8;CHUNK_SIZE] = [0;CHUNK_SIZE];
let len = self.content_length();
let n = len / CHUNK_SIZE;
let remainder = len % CHUNK_SIZE;
for _ in 0..n {
self.stream.read_exact(&mut buf)?;
writer.write_all(&buf)?;
}
if remainder > 0 {
self.stream.read_exact(&mut buf[0..remainder])?;
writer.write_all(&buf[0..remainder])?;
}
Ok(())
}
pub fn respond(&mut self) -> Result<()> {
let response_line = format!("HTTP/{} {} {}\r\n", self.version, self.status, self.status_msg());
self.stream.get_mut().write_all(response_line.as_bytes())?;
let stream = self.stream.get_mut();
for (k,v) in &self.response_headers {
stream.write_all(k.as_bytes())?;
stream.write_all(b": ")?;
stream.write_all(v.as_bytes())?;
stream.write_all(b"\r\n")?;
}
stream.write_all(b"\r\n")?;
Ok(())
}
pub fn respond_buf(&mut self, mut buf: &[u8]) -> Result<()> {
self.set_header("Content-Length", buf.len());
self.respond_reader(&mut buf)
}
pub fn respond_str(&mut self, text: &str) -> Result<()> {
self.respond_buf(text.as_bytes())
}
pub fn respond_reader(&mut self, reader: &mut dyn Read) -> Result<()> {
self.respond()?;
const CHUNK_SIZE: usize = 1024 * 1024;
let mut buf: [u8; CHUNK_SIZE] = [0; CHUNK_SIZE];
let stream = self.stream.get_mut();
while let Ok(n) = reader.read(&mut buf) {
if n == 0 { break; }
stream.write_all(&buf[0..n])?;
}
Ok(())
}
pub fn respond_chunked(&mut self, reader: &mut dyn Read) -> Result<()> {
self.set_header("Transfer-Encoding", "chunked");
let mut reader = Chunked::new(reader);
self.respond_reader(&mut reader)
}
#[inline]
pub fn respond_error_page(&mut self) -> Result<()> {
let mut buf = self.error_page();
self.respond_buf(&mut buf)
}
#[inline]
pub fn ok(&mut self) -> Result<()> {
self.set_status(200).respond()
}
#[inline]
pub fn forbidden(&mut self) -> Result<()> {
self.set_status(403).respond_error_page()
}
#[inline]
pub fn unauthorized(&mut self) -> Result<()> {
self.set_status(401).respond_error_page()
}
#[inline]
pub fn not_found(&mut self) -> Result<()> {
self.set_status(404).respond_error_page()
}
#[inline]
pub fn server_error(&mut self) -> Result<()> {
self.set_status(500).respond_error_page()
}
pub fn error_page(&mut self) -> Vec<u8> {
let code = self.status;
let msg = self.status_msg();
format!(
"<!DOCTYPE html>
<html lang=\"en\">
<head>
<meta charset=\"utf-8\">
<title>{code} {msg}</title>
</head>
<body>
<h1>{code} {msg}</h1>
</body>
</html>").as_bytes().to_vec()
}
}
#[cfg(test)]
mod test {
use std::str::FromStr;
use crate::request::RequestMethod::{self,*};
#[test]
fn parse_method() {
assert!(RequestMethod::from_str("unknown").is_err());
let strs = vec!["GET","POST","PUT","DELETE"];
let methods = vec![GET,POST,PUT,DELETE];
let res:Vec<RequestMethod> =
strs.iter()
.map(|m| RequestMethod::from_str(m))
.map(Result::unwrap).collect();
assert_eq!(methods,res);
}
}