use std::{io, path::PathBuf, ops::Deref}; use tokio::io::{AsyncBufReadExt, AsyncReadExt, BufReader, AsyncWrite, AsyncRead}; use crate::{handling::{ file_handlers::NamedFile, methods::Method, request::Request, response::{Outcome, Response, ResponseBody, Status}, routes::{Body, Data}, }, setup::MountPoint}; /// The Maximal size of the Body of an HTTP-Message in bytes static MAX_HTTP_MESSAGE_SIZE: u16 = 4196; /// Function which handles a TCP Connection according to http-Standards by taking in a /// [tokio::net::TcpStream] and a [`Vec<MountPoint<'_>>`]. /// /// Firstly validates the headers and body, Aborts with error messages if it finds faults. /// /// Secondly matches the request with a route in the mountpoint Vector and executes its handler /// function. If it fails, checks for another, if nothing is found writes back a /// [Status::NotFound]. If the handler function could respond it uses the [Response] of the handler /// /// # Panics /// No Panics pub async fn handle_connection<T: AsyncRead + AsyncWrite + std::marker::Unpin>(mut stream: T, mountpoints: Vec<MountPoint>) { let mut buf_reader = BufReader::new(&mut stream); let mut http_request: Vec<String> = Vec::with_capacity(10); loop { let mut buffer = String::new(); if buf_reader.read_line(&mut buffer).await.is_err() { eprintln!("\x1b[31mAborting due to invalid UTF-8 in request header\x1b[0m"); return; } if buffer == "\r\n" { break; } http_request.push(buffer); } 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 mut request = Request { uri: if let Some(uri) = &request_status_line.split(' ').nth(1) { if let Ok(uri) = uri.deref().try_into() { uri } else { eprintln!("\x1b[31mAborting due to invalid uri\x1b[0m"); return; } } else { eprintln!("\x1b[31mAborting due to invalid status line\x1b[0m"); return; }, cookies: Request::extract_cookies_from_vec(&mut http_request), headers: http_request, mime_type: None, 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 ); if let Err(e) = len_not_defined(stream, Status::LengthRequired).await { error_occured_when_writing(e) }; return; } } else { if request.mandatory_body() { eprintln!( "\x1b[31m`{}` must have a `Content-Length` header\x1b[0m", request.method ); if let Err(e) = len_not_defined(stream, Status::LengthRequired).await { error_occured_when_writing(e) }; return; } 0 }; if length != 0 { request.mime_from_headers(); let mut buffer = vec![0u8; MAX_HTTP_MESSAGE_SIZE.into()]; let read = buf_reader.read(&mut buffer).await.unwrap(); if read != length { if let Err(e) = len_not_defined(stream, Status::LengthRequired).await { error_occured_when_writing(e) }; return; } data.is_complete = true; data.buffer = buffer; } } let mut handled_response: Option<Outcome<Response, Status, Data>> = None; for mountpoint in mountpoints { if !mountpoint.compare_with_uri(&mut request.uri) { continue; } for route in mountpoint.routes { if (route.method != request.method) && ((route.method != Method::Get) && (request.method == Method::Head)) { continue; } if !route.uri.compare_uri(&request.uri) { continue; } handled_response = Some((route.handler)(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) => success, Outcome::Failure(error) => failure_handler(error), Outcome::Forward(_) => failure_handler(Status::NotFound), }, None => failure_handler(Status::NotFound), }; if let Err(e) = response.write(stream, Some(request)).await { eprintln!("\x1b[31mError {e} occured when trying to write answer to TCP-Stream for Client, aborting\x1b[0m"); } } /// Dumb function that renders a 404 page from any status given. but still writes that status code. fn failure_handler(status: Status) -> Response { let page_404 = NamedFile::open(PathBuf::from("404.html")).unwrap(); Response { cookies: None, headers: vec![], status: Some(status), body: Box::new(Body::new(page_404.get_data(), page_404.get_mime())), } } /// Handler for len_not_defined errors. writes back directly async fn len_not_defined<T: AsyncRead + AsyncWrite + std::marker::Unpin>(stream: T, status: Status) -> io::Result<()> { let page_411 = NamedFile::open(PathBuf::from("411.html")).unwrap(); Response { cookies: None, headers: vec![], status: Some(status), body: Box::new(Body::new(page_411.get_data(), page_411.get_mime())), } .write(stream, None) .await?; Ok(()) } /// takes in an io error and writes it back in the server console to the user if writing failed fn error_occured_when_writing(e: io::Error) { eprintln!("\x1b[31mError {e} occured when trying to write answer to TCP-Stream for Client, aborting\x1b[0m"); }