impl http request + fix few tests and scope issues
This commit is contained in:
		
							parent
							
								
									6cadabb483
								
							
						
					
					
						commit
						0c7b0bec12
					
				| @ -8,4 +8,5 @@ edition = "2021" | |||||||
| [dependencies] | [dependencies] | ||||||
| json = "0.12.4" | json = "0.12.4" | ||||||
| lazy_static = "1.4.0" | lazy_static = "1.4.0" | ||||||
|  | log = "0.4.17" | ||||||
| regex = "1" | regex = "1" | ||||||
|  | |||||||
| @ -1,3 +1,5 @@ | |||||||
|  | use json; | ||||||
|  | 
 | ||||||
| const NULL_CHAR: &'static str = "\0"; | const NULL_CHAR: &'static str = "\0"; | ||||||
| 
 | 
 | ||||||
| #[derive(Debug)] | #[derive(Debug)] | ||||||
|  | |||||||
| @ -5,9 +5,11 @@ | |||||||
| //! NOTE: only few parts of the specification has been implemented
 | //! NOTE: only few parts of the specification has been implemented
 | ||||||
| 
 | 
 | ||||||
| mod body; | mod body; | ||||||
|  | mod request; | ||||||
| mod start_line; | mod start_line; | ||||||
| mod version; | mod version; | ||||||
| 
 | 
 | ||||||
| pub use body::HTTPBody; | pub use body::HTTPBody; | ||||||
|  | pub use request::HTTPRequest; | ||||||
| pub use start_line::HTTPStartLine; | pub use start_line::HTTPStartLine; | ||||||
| pub use version::HTTPVersion; | pub use version::HTTPVersion; | ||||||
|  | |||||||
							
								
								
									
										203
									
								
								src/request.rs
									
									
									
									
									
								
							
							
						
						
									
										203
									
								
								src/request.rs
									
									
									
									
									
								
							| @ -1,6 +1,205 @@ | |||||||
| use std::collections::VecDeque; | use std::collections::VecDeque; | ||||||
| 
 | 
 | ||||||
| type RequestParts = (String, VecDeque<String>, String); | use crate::{HTTPBody, HTTPStartLine}; | ||||||
|  | 
 | ||||||
|  | type RequestParts<'a> = (&'a str, VecDeque<&'a str>, &'a str); | ||||||
| 
 | 
 | ||||||
| const HTTP_REQUEST_SEPARATOR: &'static str = "\r\n"; | const HTTP_REQUEST_SEPARATOR: &'static str = "\r\n"; | ||||||
| const NULL_CHAR: &'static str = "\0"; | 
 | ||||||
|  | /// HTTPRequest represents an HTTP request (headers are not parsed)
 | ||||||
|  | #[derive(Debug)] | ||||||
|  | pub struct HTTPRequest<'a> { | ||||||
|  |     pub start_line: HTTPStartLine<'a>, | ||||||
|  |     pub body: Option<HTTPBody>, | ||||||
|  |     // includes the client IP + port (should be in the headers)
 | ||||||
|  |     pub addr: String, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<'a> HTTPRequest<'a> { | ||||||
|  |     /// get_request_parts splits correctly the incoming request in order to get:
 | ||||||
|  |     /// * start_line
 | ||||||
|  |     /// * headers
 | ||||||
|  |     /// * data (if exists)
 | ||||||
|  |     fn get_request_parts(request: &str) -> Result<RequestParts, &str> { | ||||||
|  |         let mut request_parts: VecDeque<&str> = request.split(HTTP_REQUEST_SEPARATOR).collect(); | ||||||
|  | 
 | ||||||
|  |         if request_parts.len() < 3 { | ||||||
|  |             return Err("request has no enough informations to be correctly parsed"); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         let start_line = request_parts.pop_front().unwrap(); | ||||||
|  |         let body = request_parts.pop_back().unwrap(); | ||||||
|  | 
 | ||||||
|  |         Ok((start_line, request_parts, body)) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn parse(request: &str) -> Result<HTTPRequest, String> { | ||||||
|  |         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) => log::error!("while parsing start_line details={}", e), | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 let body = HTTPBody::try_from(rp.2); | ||||||
|  |                 match body { | ||||||
|  |                     Ok(v) => request.body = Some(v), | ||||||
|  |                     Err(e) => log::warn!("{}", e), | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 return Ok(request); | ||||||
|  |             } | ||||||
|  |             Err(e) => Err(e.to_string()), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<'a> Default for HTTPRequest<'a> { | ||||||
|  |     fn default() -> Self { | ||||||
|  |         HTTPRequest { | ||||||
|  |             start_line: HTTPStartLine::default(), | ||||||
|  |             body: None, | ||||||
|  |             addr: "".to_string(), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<'a> From<&'a str> for HTTPRequest<'a> { | ||||||
|  |     fn from(request: &'a str) -> Self { | ||||||
|  |         match Self::parse(request) { | ||||||
|  |             Ok(v) => v, | ||||||
|  |             Err(e) => { | ||||||
|  |                 log::error!("{}", e); | ||||||
|  |                 return HTTPRequest::default(); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[test] | ||||||
|  | fn test_request() { | ||||||
|  |     struct Expect<'a> { | ||||||
|  |         start_line: &'a str, | ||||||
|  |         body: Option<&'a str>, | ||||||
|  |         is_valid: bool, | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let test_cases: [(&str, Expect); 12] = [ | ||||||
|  |         ( | ||||||
|  |             "POST /get/ HTTP/1.1\r\n\r\n", | ||||||
|  |             Expect { | ||||||
|  |                 start_line: "POST /get/ HTTP/1.1", | ||||||
|  |                 body: None, | ||||||
|  |                 is_valid: true, | ||||||
|  |             }, | ||||||
|  |         ), | ||||||
|  |         ( | ||||||
|  |             "POST /refresh/ HTTP/2\r\n\r\n", | ||||||
|  |             Expect { | ||||||
|  |                 start_line: "POST /refresh/ HTTP/2", | ||||||
|  |                 body: None, | ||||||
|  |                 is_valid: true, | ||||||
|  |             }, | ||||||
|  |         ), | ||||||
|  |         ( | ||||||
|  |             "POST /validate/ HTTP/1.0\r\n\r\n", | ||||||
|  |             Expect { | ||||||
|  |                 start_line: "POST /validate/ HTTP/1.0", | ||||||
|  |                 body: None, | ||||||
|  |                 is_valid: true, | ||||||
|  |             }, | ||||||
|  |         ), | ||||||
|  |         ( | ||||||
|  |             "GET / HTTP/1.1\r\n\r\n", | ||||||
|  |             Expect { | ||||||
|  |                 start_line: "GET / HTTP/1.1", | ||||||
|  |                 body: None, | ||||||
|  |                 is_valid: true, | ||||||
|  |             }, | ||||||
|  |         ), | ||||||
|  |         // intentionally add HTTP with no version number
 | ||||||
|  |         ( | ||||||
|  |             "OPTIONS /admin/2 HTTP/\r\nContent-Type: application/json\r\n", | ||||||
|  |             Expect { | ||||||
|  |                 start_line: "  UNKNOWN", | ||||||
|  |                 body: None, | ||||||
|  |                 is_valid: false, | ||||||
|  |             }, | ||||||
|  |         ), | ||||||
|  |         ( | ||||||
|  |             "POST HTTP", | ||||||
|  |             Expect { | ||||||
|  |                 start_line: "  UNKNOWN", | ||||||
|  |                 body: None, | ||||||
|  |                 is_valid: false, | ||||||
|  |             } | ||||||
|  |         ), | ||||||
|  |         ( | ||||||
|  |             "", | ||||||
|  |             Expect { | ||||||
|  |                 start_line: "  UNKNOWN", | ||||||
|  |                 body: None, | ||||||
|  |                 is_valid: false, | ||||||
|  |             } | ||||||
|  |         ), | ||||||
|  |         ( | ||||||
|  |             "fjlqskjd /oks?id=65 HTTP/2\r\n\r\n", | ||||||
|  |             Expect { | ||||||
|  |                 start_line: "fjlqskjd /oks?id=65 HTTP/2", | ||||||
|  |                 body: None, | ||||||
|  |                 is_valid: true, | ||||||
|  |             } | ||||||
|  |         ), | ||||||
|  |         ( | ||||||
|  |             "   ", | ||||||
|  |             Expect { | ||||||
|  |                 start_line: "  UNKNOWN", | ||||||
|  |                 body: None, | ||||||
|  |                 is_valid: false, | ||||||
|  |             } | ||||||
|  |         ), | ||||||
|  |         ( | ||||||
|  |             r#"lm //// skkss\r\ndkldklkdl\r\n"{"access_token":"AAAAAAAAAAAA.BBBBBBBBBB.CCCCCCCCCC","refresh_token": "DDDDDDDDDDD.EEEEEEEEEEE.FFFFF"}""#, | ||||||
|  |             Expect { | ||||||
|  |                 start_line: "  UNKNOWN", | ||||||
|  |                 body: Some(r#"{"access_token":"AAAAAAAAAAAA.BBBBBBBBBB.CCCCCCCCCC","refresh_token": "DDDDDDDDDDD.EEEEEEEEEEE.FFFFF"}"#), | ||||||
|  |                 is_valid: false, | ||||||
|  |             } | ||||||
|  |         ), | ||||||
|  |         ( | ||||||
|  |             "POST /refresh/ HTTP/1.1\r\nuselessheaders\r\n\"{\"access_token\": \"toto\", \"refresh_token\": \"tutu\"}", | ||||||
|  |             Expect { | ||||||
|  |                 start_line: "POST /refresh/ HTTP/1.1", | ||||||
|  |                 body: Some(r#"{"access_token":"toto","refresh_token":"tutu"}"#), | ||||||
|  |                 is_valid: true, | ||||||
|  |             } | ||||||
|  |         ), | ||||||
|  |         ( | ||||||
|  |             "POST /get/ HTTP/1.1\r\nuselessheaders\r\n\"{\"token\": \"toto\", \"refresh_token\": \"tutu\"}", | ||||||
|  |             Expect { | ||||||
|  |                 start_line: "POST /get/ HTTP/1.1", | ||||||
|  |                 body: Some(r#"{"token":"toto","refresh_token":"tutu"}"#), | ||||||
|  |                 is_valid: true, | ||||||
|  |             } | ||||||
|  |         ), | ||||||
|  |     ]; | ||||||
|  | 
 | ||||||
|  |     for (request, expect) in test_cases { | ||||||
|  |         let http_request = HTTPRequest::from(request); | ||||||
|  |         println!("{}", request); | ||||||
|  |         assert_eq!(expect.is_valid, http_request.start_line.is_valid()); | ||||||
|  | 
 | ||||||
|  |         let start_line: String = http_request.start_line.into(); | ||||||
|  |         assert_eq!(expect.start_line, start_line); | ||||||
|  | 
 | ||||||
|  |         match http_request.body { | ||||||
|  |             Some(v) => { | ||||||
|  |                 assert_eq!(expect.body.unwrap(), v.get_data().dump()) | ||||||
|  |             } | ||||||
|  |             None => continue, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| use lazy_static::lazy_static; | use lazy_static::lazy_static; | ||||||
| use regex::Regex; | use regex::Regex; | ||||||
| 
 | 
 | ||||||
| use crate::version::HTTPVersion; | use crate::HTTPVersion; | ||||||
| 
 | 
 | ||||||
| lazy_static! { | lazy_static! { | ||||||
|     static ref HTTP_VERSION_REGEX: Regex = Regex::new("^HTTP/(1.1|1.0|2)$").unwrap(); |     static ref HTTP_VERSION_REGEX: Regex = Regex::new("^HTTP/(1.1|1.0|2)$").unwrap(); | ||||||
| @ -23,7 +23,7 @@ impl<'a> HTTPStartLine<'a> { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fn parse(start_line: &'a str) -> Result<Self, &str> { |     pub fn parse(start_line: &'a str) -> Result<Self, &str> { | ||||||
|         let parts: Vec<&str> = start_line.split(" ").collect(); |         let parts: Vec<&str> = start_line.split(" ").collect(); | ||||||
|         if parts.len() != 3 { |         if parts.len() != 3 { | ||||||
|             return Err("unable to parse the start correctly"); |             return Err("unable to parse the start correctly"); | ||||||
| @ -42,11 +42,25 @@ impl<'a> HTTPStartLine<'a> { | |||||||
|         )) |         )) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     pub fn is_valid(&self) -> bool { | ||||||
|  |         return self.method != "" && self.target != "" && self.version != HTTPVersion::Unknown; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     fn check_version(version: &str) -> bool { |     fn check_version(version: &str) -> bool { | ||||||
|         HTTP_VERSION_REGEX.is_match(version) |         HTTP_VERSION_REGEX.is_match(version) | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | impl<'a> Default for HTTPStartLine<'a> { | ||||||
|  |     fn default() -> Self { | ||||||
|  |         HTTPStartLine { | ||||||
|  |             method: "", | ||||||
|  |             target: "", | ||||||
|  |             version: HTTPVersion::Unknown, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| impl<'a> Into<String> for HTTPStartLine<'a> { | impl<'a> Into<String> for HTTPStartLine<'a> { | ||||||
|     fn into(self) -> String { |     fn into(self) -> String { | ||||||
|         let version: String = self.version.into(); |         let version: String = self.version.into(); | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user