use std::{
io::{prelude::*, BufReader},
net::TcpStream,
};
use crate::{router::HttpRoute, LineOrError};
use super::{response::HttpResponse, shared::HttpHeaderBody};
#[derive(Hash, PartialEq, Eq, Debug, Clone)]
pub enum HttpRequestMethod {
Get,
Post,
Put,
Delete,
Patch,
BadRequest,
}
impl std::fmt::Display for HttpRequestMethod {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
HttpRequestMethod::Get => write!(f, "GET"),
HttpRequestMethod::Put => write!(f, "PUT"),
HttpRequestMethod::Patch => write!(f, "PATCH"),
HttpRequestMethod::Post => write!(f, "POST"),
HttpRequestMethod::Delete => write!(f, "DELETE"),
HttpRequestMethod::BadRequest => write!(f, "BAD_REQUEST"),
}
}
}
pub struct HttpRequestParser;
impl HttpRequestParser {
pub fn method(raw: &Vec<LineOrError>) -> HttpRequestMethod {
match raw.first() {
Some(method) => match method {
LineOrError::Line(line) => Self::determine_method(line),
LineOrError::Error(_) => HttpRequestMethod::BadRequest,
},
None => HttpRequestMethod::BadRequest,
}
}
pub fn path(raw: &Vec<LineOrError>) -> String {
match raw.first() {
Some(method) => match method {
LineOrError::Line(line) => Self::determine_path(line),
LineOrError::Error(_) => "/".to_string(),
},
None => "/".to_string(),
}
}
pub fn determine_path(line: &str) -> String {
match line.split_whitespace().nth(1) {
Some(path) => path.to_string(),
None => "/".to_string(),
}
}
fn determine_method(line: &String) -> HttpRequestMethod {
match line {
_ if line.as_str().starts_with("GET") => HttpRequestMethod::Get,
_ if line.as_str().starts_with("POST") => HttpRequestMethod::Post,
_ if line.as_str().starts_with("PUT") => HttpRequestMethod::Put,
_ if line.as_str().starts_with("PATCH") => HttpRequestMethod::Patch,
_ if line.as_str().starts_with("DELETE") => HttpRequestMethod::Delete,
_ => {
log::debug!("Unidentified HTTP Request \"{}\"", line);
HttpRequestMethod::BadRequest
}
}
}
}
pub struct HttpRequestFailure {
pub tcp_stream: TcpStream,
pub fail_reason: String,
}
impl HttpRequestFailure {
pub fn respond(&mut self, http_res: HttpResponse) {
match self.tcp_stream.write_all(http_res.response.as_bytes()) {
Ok(_) => (),
Err(e) => {
log::error!("Failed to write to TcpStream in respond!\n\t{}", e);
}
}
}
pub fn respond_with_body(&mut self, http_res: &HttpResponse, body: &str) {
let mut res_with_body: String = String::new();
res_with_body.push_str(&http_res.response);
res_with_body.push_str(body);
match self.tcp_stream.write_all(res_with_body.as_bytes()) {
Ok(_) => (),
Err(e) => {
log::error!("Failed to write to TcpStream in respond with body!\n\t{}", e);
}
};
}
}
pub struct HttpRequest {
pub tcp_stream: TcpStream,
pub route: HttpRoute,
pub peer_addr: Option<String>,
pub body: HttpHeaderBody,
responded: bool,
}
impl HttpRequest {
pub fn new(stream: TcpStream) -> Result<HttpRequest, HttpRequestFailure> {
let h_body = Self::gen_raw_req(stream);
match h_body {
Ok((header_body, stream)) => {
let route: HttpRoute = HttpRoute {
method: HttpRequestParser::method(&header_body.lines),
path: HttpRequestParser::path(&header_body.lines),
};
let peer_addr: Option<String> = match &stream.peer_addr() {
Ok(addr) => Some(addr.ip().to_string()),
Err(e) => {
log::error!("Socket Address for peer failed! \n\t{}", e);
None
}
};
Ok(HttpRequest {
tcp_stream: stream,
route,
peer_addr,
body: header_body,
responded: false,
})
},
Err((reason_str, stream)) => {
Err(
HttpRequestFailure {
tcp_stream: stream,
fail_reason: reason_str,
}
)
}
}
}
pub fn responded(&self) -> bool {
self.responded
}
pub fn println_req(&self) {
let mut route_str: String = "".to_string();
route_str.push_str(self.route.to_string().as_str());
log::info!("{}", route_str);
}
pub fn respond(&mut self, http_res: HttpResponse) {
if self.responded {
log::warn!("Attempted to respond to request twice!");
return;
}
match self.tcp_stream.write_all(http_res.response.as_bytes()) {
Ok(_) => (),
Err(e) => {
log::error!("Failed to write to TcpStream in respond!\n\t{}", e);
}
}
match self.tcp_stream.shutdown(std::net::Shutdown::Both) {
Ok(_) => (),
Err(e) => {
log::error!("Failed to shutdown TcpStream in respond!\n\t{}", e);
}
}
self.responded = true;
}
pub fn respond_with_body(&mut self, http_res: &HttpResponse, body: &str) {
if self.responded {
log::warn!("Attempted to respond to request twice!");
return;
}
let mut res_with_body: String = String::new();
res_with_body.push_str(&http_res.response);
res_with_body.push_str(body);
match self.tcp_stream.write_all(res_with_body.as_bytes()) {
Ok(_) => (),
Err(e) => {
log::error!("Failed to write to TcpStream in respond with body!\n\t{}", e);
}
};
match self.tcp_stream.shutdown(std::net::Shutdown::Both) {
Ok(_) => (),
Err(e) => {
log::error!("Failed to shutdown TcpStream in respond!\n\t{}", e);
}
}
self.responded = true;
}
fn gen_raw_req(mut stream: TcpStream) -> Result<(HttpHeaderBody, TcpStream), (String, TcpStream)> {
let mut buf_reader = BufReader::new(&mut stream);
let mut content_length: usize = 0;
let mut got_content_length = false;
let length_str = "Content-Length:";
let http_request: Vec<LineOrError> = buf_reader
.by_ref()
.lines()
.map(|result| match result {
Ok(res) => {
if !got_content_length && res.starts_with(length_str) {
content_length = match &res[(length_str.len())..(res.len())].replace(' ', "").parse::<usize>() {
Ok(len) => len.clone(),
Err(e) => {
log::error!("{} Header Len {}", e, res);
0
}
};
got_content_length = true;
}
LineOrError::Line(res)
}
Err(error) => {
log::error!("Error reading line:\n\t{}", error);
LineOrError::Error(error.to_string())
}
})
.take_while(|line| match line {
LineOrError::Line(line) => !line.is_empty(),
LineOrError::Error(_) => true,
})
.collect();
let body_o = HttpHeaderBody::new(http_request, buf_reader, content_length);
match body_o {
Ok(body) => Ok((body, stream)),
Err(reason) => Err((reason, stream)),
}
}
}