create a response builder and other stuff

This commit is contained in:
AustrianToast 2025-02-23 16:19:25 +01:00
parent b7cd456c5e
commit 4e1ae2bfa1
Signed by: AustrianToast
GPG Key ID: FA59D02DC1947418

View File

@ -4,32 +4,40 @@ use std::{
net::{TcpListener, TcpStream}, net::{TcpListener, TcpStream},
}; };
#[derive(PartialEq, Eq, Debug)]
enum RequestMethods {
NULL = -1, // This is only to initialise the struct
GET,
HEAD,
}
#[derive(Debug)] #[derive(Debug)]
struct StartLine { struct RequestLine {
method: String, method: RequestMethods,
target: String, target: String,
version: String, version: String,
} }
impl StartLine { impl RequestLine {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
method: String::new(), method: RequestMethods::NULL,
target: String::new(), target: String::new(),
version: String::new(), version: String::new(),
} }
} }
// https://datatracker.ietf.org/doc/html/rfc9110#name-methods
pub fn is_valid_method(method: &String) -> bool { pub fn is_valid_method(method: &String) -> bool {
if method.trim().is_empty() { if method.trim().is_empty() {
return false; return false;
} }
let tmp = method.as_str();
// Only GET and HEAD are required, the rest is optional
if [ if [
"GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE", "GET", "HEAD", /*, "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE",*/
] ]
.contains(&tmp) .contains(&method.trim())
{ {
return true; return true;
} else { } else {
@ -38,6 +46,7 @@ impl StartLine {
} }
// TODO: make the checks less shit and actually correct // TODO: make the checks less shit and actually correct
// https://datatracker.ietf.org/doc/html/rfc9112#name-request-target
pub fn is_valid_target(target: &String) -> bool { pub fn is_valid_target(target: &String) -> bool {
if target.trim().is_empty() { if target.trim().is_empty() {
return false; return false;
@ -88,7 +97,7 @@ impl StartLine {
return false; return false;
} }
}; };
let minor = match version_numbers[2].parse::<u8>() { let minor = match version_numbers[1].parse::<u8>() {
Ok(val) => val, Ok(val) => val,
Err(_) => { Err(_) => {
return false; return false;
@ -106,30 +115,38 @@ impl StartLine {
} }
} }
fn parse_start_line(input: String) -> Option<StartLine> { fn parse_start_line(input: String) -> Option<RequestLine> {
if input.ends_with(" ") { if input.ends_with(" ") {
return None; return None;
} }
let mut start_line = StartLine::new(); let mut start_line = RequestLine::new();
let vec = input.trim().split(" ").collect::<Vec<&str>>(); let vec = input.trim().split(" ").collect::<Vec<&str>>();
if vec.len() != 3 { if vec.len() != 3 {
return None; return None;
} }
// start_line.method will remain RequestMethods::NULL if it is not supported.
let method = String::from(vec[0]); let method = String::from(vec[0]);
if StartLine::is_valid_method(&method) { if RequestLine::is_valid_method(&method) {
start_line.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;
}
} }
let target = String::from(vec[1]); let target = String::from(vec[1]);
if StartLine::is_valid_target(&target) { if RequestLine::is_valid_target(&target) {
start_line.target = target; start_line.target = target;
} }
let version = String::from(vec[2]); let version = String::from(vec[2]);
if StartLine::is_valid_version(&version) { if RequestLine::is_valid_version(&version) {
if version.trim() == "HTTP/1.1" || version.trim() == "HTTP/1.0" { if version.trim() == "HTTP/1.1" || version.trim() == "HTTP/1.0" {
start_line.version = version; start_line.version = version;
} }
@ -179,6 +196,62 @@ fn parse_field_lines(reader: &mut BufReader<&mut TcpStream>) -> Option<HashMap<S
return Some(field_lines); return Some(field_lines);
} }
fn response_builder(
method: RequestMethods,
status_line: String,
field_lines: HashMap<String, String>,
body: Vec<u8>,
) -> Vec<u8> {
let mut response: Vec<u8> = vec![];
// TODO: replaces with eiter Option<T> or Result<T, T>
if status_line.is_empty() {
return response;
}
for byte in status_line.as_bytes().iter() {
response.push(*byte);
}
response.push(b'\r');
response.push(b'\n');
if !field_lines.is_empty() {
for field_line in field_lines.iter() {
for byte in field_line.0.as_bytes().iter() {
response.push(*byte);
}
response.push(b':');
response.push(b' ');
for byte in field_line.1.as_bytes().iter() {
response.push(*byte);
}
response.push(b'\r');
response.push(b'\n');
}
}
// Mandatory empty line between header and body
response.push(b'\r');
response.push(b'\n');
if body.is_empty() {
return response;
}
if method != RequestMethods::HEAD && method != RequestMethods::NULL {
for byte in body.iter() {
response.push(*byte);
}
response.push(b'\r');
response.push(b'\n');
}
return response;
}
fn handle_request(mut stream: TcpStream) { fn handle_request(mut stream: TcpStream) {
let mut line = String::new(); let mut line = String::new();
let mut reader = BufReader::new(&mut stream); let mut reader = BufReader::new(&mut stream);
@ -199,7 +272,7 @@ fn handle_request(mut stream: TcpStream) {
return; return;
} }
// I will not support ignoring the CR // All lines MUST end with a CRLF
if !line.ends_with("\r\n") { if !line.ends_with("\r\n") {
stream stream
.write_all(b"HTTP/1.1 400 Bad Request\r\n\r\nLines must end with CRLF") .write_all(b"HTTP/1.1 400 Bad Request\r\n\r\nLines must end with CRLF")
@ -226,7 +299,7 @@ fn handle_request(mut stream: TcpStream) {
}; };
dbg!(&start_line); dbg!(&start_line);
if start_line.method != "GET" { if start_line.method == RequestMethods::NULL {
stream stream
.write_all(b"HTTP/1.1 501 Not Implemented\r\n\r\nServer currently only supports GET") .write_all(b"HTTP/1.1 501 Not Implemented\r\n\r\nServer currently only supports GET")
.unwrap(); .unwrap();
@ -244,13 +317,29 @@ fn handle_request(mut stream: TcpStream) {
}; };
dbg!(&field_lines); dbg!(&field_lines);
// TODO: Read the body
// let mut body: Vec<u8> = vec![]; // let mut body: Vec<u8> = vec![];
// reader.read_to_end(&mut body).unwrap(); // reader.read_to_end(&mut body).unwrap();
// dbg!(&body); // dbg!(&body);
stream // TODO: Act upon the request
.write_all(b"HTTP/1.1 200 OK\r\n\r\nHello, World!\r\n")
.unwrap(); // TODO: Figure out why this doesn't work'
let header: HashMap<String, String> = HashMap::new();
let body: Vec<u8> = vec![];
let response = response_builder(
start_line.method,
String::from("HTTP/1.1 200 OK"),
header,
body,
);
stream.write_all(&response).unwrap();
// stream
// .write_all(b"HTTP/1.1 200 OK\r\n\r\nHello, World!\r\n")
// .unwrap();
} }
fn main() { fn main() {