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