ilmen_http/http/requests/
http_request.rs1use std::collections::HashMap;
2use std::str::FromStr;
3use std::usize;
4use std::str;
5use log::trace;
6
7use crate::http::errors::http_errors::HttpError;
8use crate::http::header::Headers;
9use crate::Verb;
10
11#[derive(Clone)]
12pub struct HTTPRequest {
13 pub protocol: Protocol,
14 pub verb: Verb,
15 pub resource: Resource,
16 pub query_params: Option<QueryParams>,
17 pub headers: Option<Headers>,
18 pub body: Option<Body>
19}
20
21pub type Resource = String;
22pub type Protocol = String;
23pub type QueryParams = HashMap<String, String>;
24type Body = String;
25
26impl Default for HTTPRequest {
27 fn default() -> Self {
28 Self { protocol: Default::default(), verb: Verb::GET, resource: "/".to_string(), query_params: Default::default(), headers: Default::default(), body: Default::default() }
29 }
30}
31
32impl TryFrom<Vec<u8>> for HTTPRequest {
33 type Error = HttpError;
34 fn try_from(value: Vec<u8>) -> Result<Self, HttpError> {
35 str::from_utf8(&value)
36 .map_err(|error|
37 HttpError::BadRequest(error.to_string()))
38 .map(HTTPRequest::try_from)?
39 }
40}
41
42impl TryFrom<Vec<String>> for Verb {
43 type Error = HttpError;
44
45 fn try_from(value: Vec<String>) -> Result<Self, HttpError> {
46 value.first()
47 .ok_or(HttpError::BadRequest("Missing verb".to_string()))
48 .and_then(|verb| Verb::from_str(verb))
49 }
50}
51
52
53impl TryFrom<&str> for HTTPRequest {
54 type Error = HttpError;
55 fn try_from(buffer: &str) -> Result<Self, HttpError> {
56 let parsed_request = parse(buffer);
57
58 let decomposed_start_line = parsed_request.first()
59 .ok_or(HttpError::BadRequest("Mising ressources".to_string()))?
60 .split(' ')
61 .take(3)
62 .map(String::from)
63 .collect::<Vec<String>>();
64
65 let verb = Verb::try_from(decomposed_start_line.clone())?;
66
67 let protocol = decomposed_start_line
68 .get(2)
69 .ok_or(HttpError::BadRequest("Missing protocol".to_string()))?
70 .to_string();
71
72
73 let requested_resource = decomposed_start_line
74 .get(1)
75 .ok_or(HttpError::BadRequest("No ressource".to_string()))
76 .map(|str| str.split("?").collect::<Vec<&str>>())?;
77
78 let resource = requested_resource.first().ok_or(HttpError::BadRequest("Missing path".to_string()))?;
79
80 let query_params = requested_resource.get(1)
81 .map(|params| params.split("&").collect::<Vec<&str>>())
82 .map(|vec_params|
83 vec_params.iter()
84 .map(|couple| couple
85 .split_once("=")
86 .unwrap_or((couple, "")))
87 .map(|(a,b)|(a.to_string(), b.to_string()))
88 .collect::<QueryParams>());
89
90
91 let headers = extract_headers(parsed_request.clone());
92 trace!("Headers: {:?}", headers);
93
94 let body = get_header(&headers, "Content-Length")
95 .map(|(_, length)| length.parse::<usize>())
96 .transpose()
97 .map_err(|_| HttpError::BadRequest("Content Length not a number".to_string()))?
98 .map(|length| extract_body(buffer, length));
99
100 Ok (HTTPRequest {protocol,
101 verb,
102 query_params,
103 headers: Some(headers),
104 body,
105 resource: resource.to_string()})
106 }
107}
108
109
110fn extract_body(request : &str, content_length: usize) -> Body {
111 return request.split_once("\r\n\r\n")
112 .map(|(_, b)| b[0..content_length].to_string())
113 .unwrap_or_default();
114}
115
116
117fn parse(buffer : &str) -> Vec<String> {
118 return buffer
119 .trim_matches(char::from(0))
120 .split("\r\n")
121 .map(|str| str.to_string())
122 .collect::<Vec<String>>();
123}
124
125impl HTTPRequest {
126 pub fn get_header(&self, key: &str) -> Option<(String, String)> {
127 self.headers.clone().unwrap_or_default()
128 .iter()
129 .find(|(header, _)| header.to_lowercase().starts_with(&key.to_lowercase())).cloned()
130 }
131}
132
133fn get_header(headers: &Headers, key: &str) -> Option<(String, String)> {
134 headers
135 .iter()
136 .find(|(header, _)| header.to_lowercase().starts_with(&key.to_lowercase()))
137 .cloned()
138}
139
140fn extract_headers(request : Vec<String>) -> Headers {
141 request.iter()
142 .skip(1)
143 .take_while(|&str| str.trim() != "")
144 .map(|header| header.split_once(':'))
145 .map(|spliterator| spliterator.unwrap_or_default())
146 .map(|(cle, value)| (cle.trim().to_owned(), value.trim().to_owned()))
147 .collect::<Headers>()
148}
149
150
151
152#[cfg(test)]
154mod tests {
155 use std::vec;
156 use crate::Verb;
157
158 use super::*;
159
160 #[test]
161 fn request_try_from_ok() {
162 let buffer = "POST rappel/1?moi=toi&toi=moi HTTP/1.1\r\nContent-Length: 10\r\n\r\ntoto\r\ntata";
163
164 let mut expected_query_params = HashMap::new();
165 expected_query_params.insert("moi".to_string(), "toi".to_string());
166 expected_query_params.insert("toi".to_string(),"moi".to_string());
167
168 let request = HTTPRequest::try_from(buffer);
169
170 let http_request = request.unwrap();
171 assert_eq!(http_request.verb, Verb::POST);
172 assert_eq!(http_request.resource, "rappel/1");
173 assert_eq!(http_request.query_params, Some(expected_query_params));
174 assert_eq!(http_request.body, Some("toto\r\ntata".to_string()));
175 assert_eq!(http_request.headers, Some(vec![("Content-Length".to_string(), "10".to_string())]))
176 }
177
178 #[test]
179 fn request_try_from_ko() {
180 let buffer = "POST rappel/1 HTTP/1.1\r\nContent-Length: 4\r\n\r\ntoto";
181
182 let request = HTTPRequest::try_from(buffer);
183
184 assert!(request.is_ok());
185 }
186
187
188 #[test]
189 fn extract_headers_ok() {
190 let request = vec!["ressource".to_string(),"Content-Length: 1".to_string(),"Content-type: x and y".to_string(), "".to_string()];
191
192 let request = extract_headers(request);
193
194 assert_eq!(request, vec![("Content-Length".to_string(), "1".to_string()),("Content-type".to_string(), "x and y".to_string())]);
195 }
196}
197