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]
|
||||
json = "0.12.4"
|
||||
lazy_static = "1.4.0"
|
||||
log = "0.4.17"
|
||||
regex = "1"
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
use json;
|
||||
|
||||
const NULL_CHAR: &'static str = "\0";
|
||||
|
||||
#[derive(Debug)]
|
||||
|
||||
@ -5,9 +5,11 @@
|
||||
//! NOTE: only few parts of the specification has been implemented
|
||||
|
||||
mod body;
|
||||
mod request;
|
||||
mod start_line;
|
||||
mod version;
|
||||
|
||||
pub use body::HTTPBody;
|
||||
pub use request::HTTPRequest;
|
||||
pub use start_line::HTTPStartLine;
|
||||
pub use version::HTTPVersion;
|
||||
|
||||
203
src/request.rs
203
src/request.rs
@ -1,6 +1,205 @@
|
||||
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 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 regex::Regex;
|
||||
|
||||
use crate::version::HTTPVersion;
|
||||
use crate::HTTPVersion;
|
||||
|
||||
lazy_static! {
|
||||
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();
|
||||
if parts.len() != 3 {
|
||||
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 {
|
||||
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> {
|
||||
fn into(self) -> String {
|
||||
let version: String = self.version.into();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user