Forked from
codecraft / WebServer
64 commits behind the upstream repository.
handlers.rs 4.46 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,
};
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(100);
loop {
let mut buffer = String::new();
let _ = buf_reader.read_line(&mut buffer).await.unwrap();
if buffer == "\r\n" {
break;
}
http_request.push(buffer);
}
println!("{:#?}", http_request);
let request_status_line = http_request.get(0);
if request_status_line == None {
return;
}
let request_status_line = request_status_line.unwrap();
let request = Request {
uri: &request_status_line.split(" ").nth(1).unwrap(),
headers: http_request.clone(),
method: request_status_line
.split(" ")
.next()
.unwrap()
.parse()
.unwrap(),
};
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.contains("Content-Length: "))
.clone()
.map(|header| {
let header = header.strip_prefix("Content-Length: ").unwrap();
header.trim().parse::<usize>()
})
.next()
{
if let Ok(size) = len {
size
} else {
0
}
} else {
0
};
let mut buffer: Vec<u8> = vec![];
buf_reader.read_buf(&mut buffer).await.unwrap();
if buffer.len() != length {
let respone = len_not_defined(Status::LengthRequired).await;
stream.write_all(respone.0.as_bytes()).await.unwrap();
stream.write(&respone.1).await.unwrap();
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 {
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())
+ &success.headers.join("\r\n")
+ "\r\n\r\n",
success.body.get_data(),
),
Outcome::Failure(error) => failure_handler(error).await,
Outcome::Forward(_) => failure_handler(Status::NotFound).await,
},
None => failure_handler(Status::NotFound).await,
};
stream.write_all(response.0.as_bytes()).await.unwrap();
stream.write(&response.1).await.unwrap();
}
async 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(status: Status) -> (String, Vec<u8>) {
let page_411 = NamedFile::open(PathBuf::from("411.html")).unwrap();
(
format!(
"HTTP/1.1 {status}\r\nContent-Length: {}\r\nContent-Type: {}\r\n\r\n",
page_411.get_len(),
page_411.get_mime()
),
page_411.get_data(),
)
}