diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs new file mode 100644 index 0000000..7bb6422 --- /dev/null +++ b/src/handlers/mod.rs @@ -0,0 +1,3 @@ +pub mod request; + +pub use request::handle_request; diff --git a/src/handlers/request.rs b/src/handlers/request.rs new file mode 100644 index 0000000..2e37cff --- /dev/null +++ b/src/handlers/request.rs @@ -0,0 +1,108 @@ +//! request handles properly the incoming request +//! it will parse the request according to the HTTP message specifications +//! see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Messages + +#[derive(Debug)] +pub enum HTTPVersion { + HTTP1, + HTTP2, +} + +impl Into for HTTPVersion { + fn into(self) -> String { + match self { + HTTP1 => "HTTP/1.1".to_string(), + HTTP2 => "HTTP/2.2".to_string(), + _ => "UNKNOWN".to_string(), + } + } +} + +/// Request defined the HTTP request +// TODO: method, target, version must be set in new struct : `HTTPStartLine` +#[derive(Debug)] +pub struct HTTPRequest { + pub method: String, + pub target: String, + pub version: HTTPVersion, +} + +impl HTTPRequest { + // associated function to build a new HTTPRequest + fn new(method: String, target: String, version: HTTPVersion) -> Self { + HTTPRequest { + method, + target, + version, + } + } + + fn parse(request: &str) -> Result { + // declare a new `request` var to borrow to &str `request` + let request = request.to_string(); + + let request_parts: Vec<&str> = request.split(" ").collect(); + if request_parts.len() < 3 { + return Err("unable to parse the request correctly"); + } + + Ok(HTTPRequest::new( + request_parts[0].to_string(), + request_parts[1].to_string(), + HTTPVersion::HTTP1, + )) + } + + fn is_valid(self) -> bool { + return self.method != "" && self.target != ""; + } + + // TODO: to be tested + pub fn start_line(self) -> String { + let version: String = self.version.into(); + return format!("{} {} {}", self.method, self.target, version); + } +} + +impl Default for HTTPRequest { + fn default() -> Self { + HTTPRequest { + method: "".to_string(), + target: "".to_string(), + version: HTTPVersion::HTTP1, + } + } +} + +impl From<&str> for HTTPRequest { + fn from(request: &str) -> Self { + match Self::parse(request) { + Ok(v) => v, + Err(v) => { + eprintln!("{}", format!("[ERR]: {v}")); + return HTTPRequest::default(); + } + } + } +} + +pub fn handle_request(request: &str) -> HTTPRequest { + return HTTPRequest::from(request); +} + +#[test] +fn test_handle_request() { + let test_cases: [(&str, bool); 5] = [ + ("GET / HTTP/1.1", true), + ("POST HTTP/1.1", false), + ("", false), + ("fjlqskjd /oks?id=65 HTTP/2", true), + (" ", false), + ]; + + for (request, is_valid) in test_cases { + let http_request = HTTPRequest::from(request); + println!("{:?}", http_request); + assert_eq!(http_request.is_valid(), is_valid); + } +} diff --git a/src/main.rs b/src/main.rs index 4f88beb..9dc5ba4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +mod handlers; + use std::fs; use std::io::prelude::*; use std::net::TcpListener; @@ -6,6 +8,8 @@ use std::net::TcpStream; use async_std::task; use std::time::Duration; +use handlers::handle_request; + // TODO: must be set in a conf file const SERVER_URL: &str = "127.0.0.1:9000"; @@ -20,15 +24,18 @@ async fn main() { } } -// TODO: must properly handle the request async fn handle_connection(mut stream: TcpStream) { let mut buffer = [0; 1024]; stream.read(&mut buffer).unwrap(); let get = b"GET / HTTP/1.1\r\n"; - // TODO: request musy be parsed correctly - let status_line = if buffer.starts_with(get) { + // transform buffer bytes array into `String` + let buffer_string = String::from_utf8_lossy(&buffer); + let request = handle_request(&buffer_string); + + // TODO: `Response` struct must be implemented + let status_line = if request.target == "/".to_string() { "HTTP/1.1 200 OK\r\n" } else { "HTTP/1.1 404 NOT FOUND\r\n"