From 046c8d17849b8cdfe9267f7c367b487e23108517 Mon Sep 17 00:00:00 2001 From: AustrianToast Date: Thu, 20 Mar 2025 18:42:32 +0100 Subject: [PATCH] some refactoring --- src/main.rs | 303 +++++++++++++++++++++++----------------------------- 1 file changed, 134 insertions(+), 169 deletions(-) diff --git a/src/main.rs b/src/main.rs index 932f5bf..4a24ad0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,7 @@ use std::{ collections::HashMap, error::Error, fs::{self}, - io::{BufRead, BufReader, Write}, + io::{BufRead, BufReader, Read, Write}, net::{TcpListener, TcpStream}, path::PathBuf, process::exit, @@ -18,13 +18,13 @@ enum RequestMethods { } #[derive(Debug)] -struct RequestLine { +struct StartLine { method: RequestMethods, target: String, version: String, } -impl RequestLine { +impl StartLine { pub fn new() -> Self { Self { method: RequestMethods::NULL, @@ -113,41 +113,20 @@ impl RequestLine { } } -fn parse_start_line(input: &str) -> Result> { +fn parse_start_line(input: &str) -> Result> { let mut response_field_lines: HashMap = HashMap::new(); let mut response_body: Vec = vec![]; - - if input.ends_with(" ") { - b"There is whitespace between the start-line and the first field-line" - .iter() - .for_each(|byte| response_body.push(*byte)); - - response_field_lines.insert( - String::from("Content-Length"), - response_body.len().to_string(), - ); - response_field_lines.insert(String::from("Content-Type"), String::from("text/plain")); - - return Err(response_builder( - RequestMethods::GET, - "HTTP/1.1 400 Bad Request", - response_field_lines, - Some(response_body), - )); - } - - let mut start_line = RequestLine::new(); + let mut start_line = StartLine::new(); let vec = input.trim().split_ascii_whitespace().collect::>(); - let body = format!( - "The start-line has an incorrect amount of items. Got the value: {}", - vec.len() - ); - if vec.len() != 3 { - body.as_bytes() - .iter() - .for_each(|byte| response_body.push(*byte)); + format!( + "The start-line has an incorrect amount of items. Got the value: {}", + vec.len() + ) + .as_bytes() + .iter() + .for_each(|byte| response_body.push(*byte)); response_field_lines.insert( String::from("Content-Length"), @@ -157,55 +136,42 @@ fn parse_start_line(input: &str) -> Result> { return Err(response_builder( RequestMethods::GET, - "HTTP/1.1 400 Bad Request", - response_field_lines, + "HTTP/1.1 400 ", + Some(response_field_lines), Some(response_body), )); } - // start_line.method will remain RequestMethods::NULL if it is not supported. let method = vec[0]; - if RequestLine::is_valid_method(&method) { - // TODO: Change to a switch-case if I ever support more methods - - if method == String::from("GET") { - start_line.method = RequestMethods::GET; - } - if method == String::from("HEAD") { - start_line.method = RequestMethods::HEAD; - } - } else { - b"Server only supports GET and HEAD" - .iter() - .for_each(|byte| response_body.push(*byte)); - - response_field_lines.insert( - String::from("Content-Length"), - response_body.len().to_string(), - ); - response_field_lines.insert(String::from("Content-Type"), String::from("text/plain")); - - return Err(response_builder( - RequestMethods::GET, - "HTTP/1.1 501 Not Implemented", - response_field_lines, - Some(response_body), - )); - } - let target = vec[1]; - if RequestLine::is_valid_target(&target) { - start_line.target = target.to_string(); - } - let version = vec[2]; - if RequestLine::is_valid_version(&version) { + + if StartLine::is_valid_method(&method) + && StartLine::is_valid_target(&target) + && StartLine::is_valid_version(&version) + { + // start_line.method will remain RequestMethods::NULL if it is not supported. + match method { + "GET" => start_line.method = RequestMethods::GET, + "HEAD" => start_line.method = RequestMethods::HEAD, + _ => start_line.method = RequestMethods::NULL, + } + + start_line.target = target.to_string(); + if version == "HTTP/1.1" || version == "HTTP/1.0" { start_line.version = version.to_string(); } + } else { + return Err(response_builder( + RequestMethods::HEAD, + "HTTP/1.1 400 ", + None, + None, + )); } - return Ok(start_line); + Ok(start_line) } fn parse_field_lines( @@ -233,8 +199,8 @@ fn parse_field_lines( return Err(response_builder( RequestMethods::GET, - "HTTP/1.1 400 Bad Request", - response_field_lines, + "HTTP/1.1 400 ", + Some(response_field_lines), Some(response_body), )); } @@ -259,8 +225,8 @@ fn parse_field_lines( return Err(response_builder( RequestMethods::GET, - "HTTP/1.1 400 Bad Request", - response_field_lines, + "HTTP/1.1 400 ", + Some(response_field_lines), Some(response_body), )); } @@ -282,8 +248,8 @@ fn parse_field_lines( return Err(response_builder( RequestMethods::GET, - "HTTP/1.1 400 Bad Request", - response_field_lines, + "HTTP/1.1 400 ", + Some(response_field_lines), Some(response_body), )); } @@ -294,7 +260,7 @@ fn parse_field_lines( fn response_builder( method: RequestMethods, status_line: &str, - field_lines: HashMap, + field_lines: Option>, body: Option>, ) -> Vec { let mut response: Vec = vec![]; @@ -306,26 +272,29 @@ fn response_builder( response.push(b'\r'); response.push(b'\n'); - if !field_lines.is_empty() { - for field_line in field_lines.iter() { - field_line - .0 - .as_bytes() - .iter() - .for_each(|byte| response.push(*byte)); + match field_lines { + Some(val) => { + for field_line in val.iter() { + field_line + .0 + .as_bytes() + .iter() + .for_each(|byte| response.push(*byte)); - response.push(b':'); - response.push(b' '); + response.push(b':'); + response.push(b' '); - field_line - .1 - .as_bytes() - .iter() - .for_each(|byte| response.push(*byte)); + field_line + .1 + .as_bytes() + .iter() + .for_each(|byte| response.push(*byte)); - response.push(b'\r'); - response.push(b'\n'); + response.push(b'\r'); + response.push(b'\n'); + } } + None => (), } // Mandatory empty line between header and body @@ -346,89 +315,68 @@ fn response_builder( return response; } -fn act_upon_request(start_line: &RequestLine) -> Result, Box> { +fn act_upon_request( + start_line: StartLine, + _field_lines: HashMap, + _request_body: Vec, +) -> Result, Box> { let mut response_field_lines: HashMap = HashMap::new(); let mut response_body: Vec = vec![]; let response: Vec; + let special_paths = ["/server-health", "/server-stats", "/server-info"]; - if start_line.target == "/" { - let file = match fs::read("./www/index.html") { - Ok(val) => val, - Err(_) => { - b"The is no index.html, only you and me." - .iter() - .for_each(|byte| response_body.push(*byte)); - - response_field_lines.insert( - String::from("Content-Length"), - response_body.len().to_string(), - ); - response_field_lines - .insert(String::from("Content-Type"), String::from("text/plain")); - - response = response_builder( - start_line.method, - "HTTP/1.1 200 OK", - response_field_lines, - Some(response_body), - ); - - return Ok(response); + if special_paths.contains(&start_line.target.as_str()) { + match start_line.target.as_str() { + "/server-health" => { + response = response_builder(start_line.method, "HTTP/1.1 200 ", None, None); } - }; - - file.iter().for_each(|byte| response_body.push(*byte)); - - response_field_lines.insert(String::from("Content-Length"), file.len().to_string()); - response_field_lines.insert(String::from("Content-Type"), String::from("text/html")); - - response = response_builder( - start_line.method, - "HTTP/1.1 200 OK", - response_field_lines, - Some(response_body), - ); - } else { - let path: PathBuf = PathBuf::from(format!("./www{}", start_line.target)); - - match fs::read(&path) { - Ok(val) => { - val.iter().for_each(|byte| response_body.push(*byte)); - - response_field_lines.insert( - String::from("Content-Length"), - response_body.len().to_string(), - ); - // TODO: get mime-type of file and use that here - let mime_type = mime_guess::from_path(&path) - .first_raw() - .expect("Could not guess mime-type from path"); - response_field_lines.insert(String::from("Content-Type"), mime_type.to_string()); - - response = response_builder( - start_line.method, - "HTTP/1.1 200 OK", - response_field_lines, - Some(response_body), - ); + _ => { + response = response_builder(start_line.method, "HTTP/1.1 404 ", None, None); } - Err(_) => { - response = response_builder( - start_line.method, - "HTTP/1.1 404 Not Found", - response_field_lines, - None, - ); - } - }; + } + + return Ok(response); } + let path: PathBuf = match start_line.target.as_str() { + "/" => PathBuf::from("/www/index.html"), + _ => PathBuf::from(format!("/www{}", start_line.target)), + }; + + match fs::read(&path) { + Ok(val) => { + val.iter().for_each(|byte| response_body.push(*byte)); + + response_field_lines.insert( + String::from("Content-Length"), + response_body.len().to_string(), + ); + // TODO: get mime-type of file and use that here + let mime_type = mime_guess::from_path(&path) + .first_raw() + .expect("Could not guess mime-type from path"); + response_field_lines.insert(String::from("Content-Type"), mime_type.to_string()); + + response = response_builder( + start_line.method, + "HTTP/1.1 200 ", + Some(response_field_lines), + Some(response_body), + ); + } + Err(_) => { + response = response_builder(start_line.method, "HTTP/1.1 404 ", None, None); + } + }; + Ok(response) } fn handle_request(mut stream: TcpStream) -> Result<(), Box> { let mut line = String::new(); let mut reader = BufReader::new(&mut stream); + let mut response_field_lines: HashMap = HashMap::new(); + let mut response_body: Vec = vec![]; // Request can have one or many empty lines preceding the start-line and I will ignore these loop { @@ -437,6 +385,27 @@ fn handle_request(mut stream: TcpStream) -> Result<(), Box> { } } + if line.ends_with(" ") { + b"There is whitespace between the start-line and the first field-line" + .iter() + .for_each(|byte| response_body.push(*byte)); + + response_field_lines.insert( + String::from("Content-Length"), + response_body.len().to_string(), + ); + response_field_lines.insert(String::from("Content-Type"), String::from("text/plain")); + + let response = response_builder( + RequestMethods::GET, + "HTTP/1.1 400 ", + Some(response_field_lines), + Some(response_body), + ); + stream.write_all(&response)?; + return Ok(()); + } + let start_line = match parse_start_line(&line) { Ok(val) => val, Err(response) => { @@ -453,15 +422,11 @@ fn handle_request(mut stream: TcpStream) -> Result<(), Box> { return Ok(()); } }; - dbg!(&field_lines); - // TODO: Read the body - // let mut body: Vec = vec![]; - // reader.read_to_end(&mut body)?; - // dbg!(&body); + let mut body: Vec = vec![]; + reader.read_to_end(&mut body)?; - // TODO: Act upon the request - let response = act_upon_request(&start_line)?; + let response = act_upon_request(start_line, field_lines, body)?; stream.write_all(&response)?; Ok(())