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(100); 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 { 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); let mut response = respone.0.as_bytes().to_vec(); response.extend_from_slice(&respone.1); 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"); return; } 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(), ) } 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(), ) }