From ce7e49af479aba3ad0372c5a87ff7ec50826cab3 Mon Sep 17 00:00:00 2001 From: landrigun Date: Fri, 9 Sep 2022 16:58:10 +0100 Subject: [PATCH] feat: #1 rework on http request parser and include HTTPBody in HTTPRequest --- src/handlers/request.rs | 110 ++++++++++++++++++++++++++++++---------- 1 file changed, 83 insertions(+), 27 deletions(-) diff --git a/src/handlers/request.rs b/src/handlers/request.rs index 1889967..1185284 100644 --- a/src/handlers/request.rs +++ b/src/handlers/request.rs @@ -5,6 +5,8 @@ use json; +type RequestParts = (String, String, String); + #[derive(Debug)] pub enum HTTPVersion { Http1, @@ -76,7 +78,8 @@ impl Into for HTTPStartLine { } /// HTTPBody represents http request body -/// for simplicity, only json body are accepted +/// for simplicity, only json body is accepted +#[derive(Debug)] pub struct HTTPBody { data: json::JsonValue, } @@ -87,10 +90,10 @@ impl HTTPBody { } } -impl TryFrom<&str> for HTTPBody { +impl TryFrom for HTTPBody { type Error = String; - fn try_from(body: &str) -> Result { - match json::parse(body) { + fn try_from(body: String) -> Result { + match json::parse(&body) { Ok(v) => Ok(HTTPBody::new(v)), Err(e) => Err(format!( "error occurred during request body parsing err={}", @@ -104,35 +107,86 @@ impl TryFrom<&str> for HTTPBody { #[derive(Debug)] pub struct HTTPRequest { pub start_line: HTTPStartLine, + pub body: Option, } impl HTTPRequest { // associated function to build a new HTTPRequest - fn new(start_line: HTTPStartLine) -> Self { - HTTPRequest { start_line } + fn new(start_line: HTTPStartLine, body: Option) -> Self { + HTTPRequest { start_line, body } + } + + /// get mandatory request informations : + /// * start_line + /// * headers + fn get_request_mandats(request_parts: Vec<&str>) -> Result<(String, String), String> { + let headers_sline: Vec<&str> = request_parts[0].split("\r\n").collect(); + match headers_sline.len() { + 0 => return Err("request does not contain start_line or headers".to_string()), + 1 => return Ok((headers_sline[0].to_string(), "".to_string())), + // TODO: check if in the spec it must be 2 or 3 ! + 2 | 3 => return Ok((headers_sline[0].to_string(), headers_sline[1].to_string())), + _ => return Err("bad start_line headers parsing".to_string()), + } + } + + /// split correctly the incoming request in order to get : + /// * start_line + /// * headers + /// * data (if exists) + fn get_request_parts(request: &str) -> Result { + // separate the body part from the start_line and the headers + let request_parts: Vec<&str> = request.split("\r\n\r\n").collect(); + if request_parts.len() == 0 { + return Err("request has no enough informations to be correctly parsed".to_string()); + } + + match request_parts.len() { + 0 => { + return Err("request has no enough informations to be correctly parsed".to_string()) + } + 1 => match HTTPRequest::get_request_mandats(request_parts) { + Ok(v) => return Ok((v.0, v.1, "".to_string())), + Err(e) => return Err(e), + }, + 2 => { + let body = request_parts[1]; + match HTTPRequest::get_request_mandats(request_parts) { + Ok(v) => return Ok((v.0, v.1, body.to_string())), + Err(e) => return Err(e), + } + } + _ => return Err("bad incoming request, impossible to parse".to_string()), + } } /// parse parses the request by spliting the incoming request with the separator `\r\n` - fn parse(request: &str) -> Result { + 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("\r\n").collect(); - println!("request part : {:?}", request_parts); - // TODO: increase the check to 3 to match the request (for now only the start_line) - if request_parts.len() < 1 { - return Err("unable to parse the request correctly"); + match HTTPRequest::get_request_parts(&request) { + Ok(rp) => { + let mut request = HTTPRequest::default(); + + let start_line = HTTPStartLine::parse(&rp.0); + match start_line { + Ok(v) => request.start_line = v, + Err(e) => eprintln!("error occurred while parsing start_line err={}", e), + } + + let body = HTTPBody::try_from(rp.2); + match body { + Ok(v) => request.body = Some(v), + Err(e) => eprintln!("error occurred during body parsing err={}", e), + } + + return Ok(request); + } + Err(e) => { + return Err(format!("error occurred getting request parts err={}", e)); + } } - - let mut request = HTTPRequest::default(); - - let start_line = HTTPStartLine::parse(request_parts[0]); - match start_line { - Ok(v) => request.start_line = v, - Err(e) => eprintln!("error occured while parsing start_line err={}", e), - } - - return Ok(request); } fn is_valid(self) -> bool { @@ -144,6 +198,7 @@ impl Default for HTTPRequest { fn default() -> Self { HTTPRequest { start_line: HTTPStartLine::default(), + body: None, } } } @@ -166,16 +221,17 @@ pub fn handle_request(request: &str) -> HTTPRequest { #[test] fn test_handle_request() { - let test_cases: [(&str, bool); 6] = [ - ("GET / HTTP/1.1", true), + let test_cases: [(&str, bool); 7] = [ + ("GET / HTTP/1.1\r\n\r\n", true), ( - "OPTIONS /admin/2 HTTP/\r\nContent-Type: application/json", + "OPTIONS /admin/2 HTTP/\r\nContent-Type: application/json\r\n", true, ), // intentionally add HTTP with no version number ("POST HTTP/1.1", false), ("", false), - ("fjlqskjd /oks?id=65 HTTP/2", true), + ("fjlqskjd /oks?id=65 HTTP/2\r\n\r\n", true), (" ", false), + ("lm //// skkss\r\ndkldklkdl\r\n", true), ]; for (request, is_valid) in test_cases { @@ -197,7 +253,7 @@ fn test_http_body() { ]; for (body, is_valid) in test_cases { - match HTTPBody::try_from(body) { + match HTTPBody::try_from(body.to_string()) { Ok(_) => assert!(is_valid), Err(_) => assert!(!is_valid), }