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},
};
#[derive(PartialEq, Eq, Debug)]
enum RequestMethods {
NULL = -1, // This is only to initialise the struct
GET,
HEAD,
}
#[derive(Debug)]
struct StartLine {
method: String,
struct RequestLine {
method: RequestMethods,
target: String,
version: String,
}
impl StartLine {
impl RequestLine {
pub fn new() -> Self {
Self {
method: String::new(),
method: RequestMethods::NULL,
target: String::new(),
version: String::new(),
}
}
// https://datatracker.ietf.org/doc/html/rfc9110#name-methods
pub fn is_valid_method(method: &String) -> bool {
if method.trim().is_empty() {
return false;
}
let tmp = method.as_str();
// Only GET and HEAD are required, the rest is optional
if [
"GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE",
"GET", "HEAD", /*, "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE",*/
]
.contains(&tmp)
.contains(&method.trim())
{
return true;
} else {
@ -38,6 +46,7 @@ impl StartLine {
}
// 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 {
if target.trim().is_empty() {
return false;
@ -88,7 +97,7 @@ impl StartLine {
return false;
}
};
let minor = match version_numbers[2].parse::<u8>() {
let minor = match version_numbers[1].parse::<u8>() {
Ok(val) => val,
Err(_) => {
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(" ") {
return None;
}
let mut start_line = StartLine::new();
let mut start_line = RequestLine::new();
let vec = input.trim().split(" ").collect::<Vec<&str>>();
if vec.len() != 3 {
return None;
}
// start_line.method will remain RequestMethods::NULL if it is not supported.
let method = String::from(vec[0]);
if StartLine::is_valid_method(&method) {
start_line.method = method;
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;
}
}
let target = String::from(vec[1]);
if StartLine::is_valid_target(&target) {
if RequestLine::is_valid_target(&target) {
start_line.target = target;
}
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" {
start_line.version = version;
}
@ -179,6 +196,62 @@ fn parse_field_lines(reader: &mut BufReader<&mut TcpStream>) -> Option<HashMap<S
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) {
let mut line = String::new();
let mut reader = BufReader::new(&mut stream);
@ -199,7 +272,7 @@ fn handle_request(mut stream: TcpStream) {
return;
}
// I will not support ignoring the CR
// All lines MUST end with a CRLF
if !line.ends_with("\r\n") {
stream
.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);
if start_line.method != "GET" {
if start_line.method == RequestMethods::NULL {
stream
.write_all(b"HTTP/1.1 501 Not Implemented\r\n\r\nServer currently only supports GET")
.unwrap();
@ -244,13 +317,29 @@ fn handle_request(mut stream: TcpStream) {
};
dbg!(&field_lines);
// TODO: Read the body
// let mut body: Vec<u8> = vec![];
// reader.read_to_end(&mut body).unwrap();
// dbg!(&body);
stream
.write_all(b"HTTP/1.1 200 OK\r\n\r\nHello, World!\r\n")
.unwrap();
// TODO: Act upon the request
// 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() {