Forked from
codecraft / WebServer
54 commits behind the upstream repository.
handlers.rs 5.99 KiB
use std::path::PathBuf;
use tokio::{io::{AsyncReadExt, BufReader, AsyncBufReadExt, AsyncWriteExt}, net::TcpStream};
use crate::handling::{
file_handlers::NamedFile,
request::Request,
response::{Outcome, Response, ResponseBody, Status},
routes::Data, methods::Method,
};
use crate::setup::MountPoint;
pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoint<'_>>) {
let mut buf_reader = BufReader::new(&mut stream);
let mut http_request: Vec<String> = Vec::with_capacity(30);
loop {
let mut buffer = String::new();
if let Err(_) = buf_reader.read_line(&mut buffer).await {
eprintln!("\x1b[31mAborting due to invalid UTF-8 in request header\x1b[0m");
return;
}
if buffer == "\r\n" {
break;
}
http_request.push(buffer);
}
// println!("{:#?}", http_request);
let request_status_line = if let Some(status_line) = http_request.get(0).cloned() {
status_line
} else {
eprintln!("\x1b[31mAborting due to missing headers\x1b[0m");
return;
};
let request = Request {
uri: if let Some(uri) = &request_status_line.split(" ").nth(1) {
*uri
} else {
eprintln!("\x1b[31mAborting due to invalid status line\x1b[0m");
return;
},
headers: http_request,
method: if let Some(method) = request_status_line
.split(" ")
.next() {
if let Ok(ok) = method.parse() {
ok
} else {
eprintln!("\x1b[31mAborting due to invalid request method\x1b[0m");
return;
}
} else {
eprintln!("\x1b[31mAborting due to invalid status line\x1b[0m");
return;
}
};
let mut data = Data {
is_complete: false,
buffer: vec![],
};
if request.can_have_body() {
let length = if let Some(len) = request
.headers
.iter()
.filter(|header| header.starts_with("Content-Length: "))
.map(|header| {
let header = header.strip_prefix("Content-Length: ").unwrap();
header.trim().parse::<usize>()
})
.next()
{
if let Ok(size) = len {
size
} else {
eprintln!("\x1b[31m`{}` must have a `Content-Length` header\x1b[0m", request.method);
len_not_defined(stream, Status::LengthRequired).await;
return;
}
} else {
if request.mandatory_body() {
eprintln!("\x1b[31m`{}` must have a `Content-Length` header\x1b[0m", request.method);
len_not_defined(stream, Status::LengthRequired).await;
return;
}
0
};
if length != 0 {
let mut buffer: Vec<u8> = vec![];
buf_reader.read_buf(&mut buffer).await.unwrap();
if buffer.len() != length {
len_not_defined(stream, Status::LengthRequired).await;
return;
}
data.is_complete = true;
data.buffer = buffer;
}
}
let mut handled_response: Option<Outcome<Response, Status, Data>> = None;
for mountpoint in mountpoints {
if !request.uri.starts_with(mountpoint.mountpoint) {
continue;
}
let mounted_request_uri = request.uri.strip_prefix(mountpoint.mountpoint).unwrap();
for route in mountpoint.routes {
if (route.method != request.method) &&
((route.method != Method::Get) && (request.method == Method::Head)) {
continue;
}
if !route.compare_uri(mounted_request_uri) {
continue;
}
handled_response = Some((route.handler)(
Request {
uri: mounted_request_uri,
..request.clone()
},
data.clone(),
));
if let Some(Outcome::Forward(_)) = handled_response {
continue;
}
break;
}
}
let response = match handled_response {
Some(val) => match val {
Outcome::Success(success) => (
format!("HTTP/1.1 {}\r\n", success.status.unwrap_or(Status::Ok))
+ &success.headers.join("\r\n")
+ "\r\n\r\n",
if request.method == Method::Head {
vec![]
} else {
success.body.get_data()
}
),
Outcome::Failure(error) => failure_handler(error),
Outcome::Forward(_) => failure_handler(Status::NotFound),
},
None => failure_handler(Status::NotFound),
};
let mut resp = response.0.as_bytes().to_vec();
resp.extend_from_slice(&response.1);
if let Err(e) = stream.write_all(&resp).await {
eprintln!("\x1b[31mError {e} occured when trying to write answer to TCP-Stream for Client, aborting\x1b[0m");
return;
}
return;
}
fn failure_handler(status: Status) -> (String, Vec<u8>) {
let page_404 = NamedFile::open(PathBuf::from("404.html")).unwrap();
(
format!(
"HTTP/1.1 {}\r\nContent-Length: {}\r\nContent-Type: {}\r\n\r\n",
status,
page_404.get_len(),
page_404.get_mime()
),
page_404.get_data(),
)
}
async fn len_not_defined(mut stream: TcpStream, status: Status) {
let page_411 = NamedFile::open(PathBuf::from("411.html")).unwrap();
let mut response = format!("HTTP/1.1 {}\r\nContent-Length: {}\r\nContent-Type: {}\r\n\r\n", status, page_411.get_len(), page_411.get_mime()).as_bytes().to_vec();
response.extend_from_slice(&page_411.get_data());
if let Err(e) = stream.write_all(&response).await {
eprintln!("\x1b[31mError {e} occured when trying to write answer to TCP-Stream for Client, aborting\x1b[0m");
}
}