nanofish_client/
request.rs1use crate::{error::Error, header::HttpHeader, method::HttpMethod};
2use heapless::Vec;
3
4pub const MAX_HEADERS: usize = 16;
6
7#[derive(Debug)]
9pub struct HttpRequest<'a> {
10 pub method: HttpMethod,
12 pub path: &'a str,
14 pub version: &'a str,
16 pub headers: Vec<HttpHeader<'a>, MAX_HEADERS>,
18 pub body: &'a [u8],
20}
21
22fn find_double_crlf(data: &[u8]) -> Option<usize> {
24 const DOUBLE_CRLF: &[u8] = b"\r\n\r\n";
25 (0..data.len().saturating_sub(3)).find(|&i| &data[i..i + 4] == DOUBLE_CRLF)
26}
27
28impl<'a> HttpRequest<'a> {
29 pub fn parse_from(headers_str: &'a str, body: &'a [u8]) -> Result<Self, Error> {
39 let mut lines = headers_str.lines();
40
41 let request_line = lines
43 .next()
44 .ok_or(Error::InvalidResponse("Missing request line"))?;
45 let mut parts = request_line.split_whitespace();
46
47 let method_str = parts
48 .next()
49 .ok_or(Error::InvalidResponse("Missing method"))?;
50 let path = parts.next().ok_or(Error::InvalidResponse("Missing path"))?;
51 let version = parts
52 .next()
53 .ok_or(Error::InvalidResponse("Missing version"))?;
54
55 let method = HttpMethod::try_from(method_str)
56 .map_err(|_| Error::InvalidResponse("Unknown HTTP method"))?;
57
58 let mut headers = Vec::new();
60 for line in lines {
61 if line.is_empty() {
62 break;
63 }
64
65 if let Some(colon_pos) = line.find(':') {
66 let name = line[..colon_pos].trim();
67 let value = line[colon_pos + 1..].trim();
68
69 let header = HttpHeader::new(name, value);
70 headers
71 .push(header)
72 .map_err(|_| Error::InvalidResponse("Too many headers"))?;
73 }
74 }
75
76 Ok(HttpRequest {
77 method,
78 path,
79 version,
80 headers,
81 body,
82 })
83 }
84}
85
86impl<'a> TryFrom<&'a [u8]> for HttpRequest<'a> {
87 type Error = Error;
88
89 fn try_from(buffer: &'a [u8]) -> Result<Self, Self::Error> {
90 let end_of_headers =
92 find_double_crlf(buffer).ok_or(Error::InvalidResponse("Incomplete request headers"))?;
93
94 let headers_str = core::str::from_utf8(&buffer[..end_of_headers])
96 .map_err(|_| Error::InvalidResponse("Invalid UTF-8 in request"))?;
97
98 let body = &buffer[end_of_headers + 4..];
100
101 Self::parse_from(headers_str, body)
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108 use crate::HttpMethod;
109
110 #[test]
111 fn test_parse_request_get() {
112 let request_str =
113 "GET /index.html HTTP/1.1\r\nHost: example.com\r\nUser-Agent: test\r\n\r\n";
114 let body = b"";
115
116 let request = HttpRequest::parse_from(request_str, body).unwrap();
117
118 assert_eq!(request.method, HttpMethod::GET);
119 assert_eq!(request.path, "/index.html");
120 assert_eq!(request.version, "HTTP/1.1");
121 assert_eq!(request.headers.len(), 2);
122 assert_eq!(request.body, b"");
123 }
124
125 #[test]
126 fn test_parse_request_post_with_body() {
127 let request_str = "POST /api/data HTTP/1.1\r\nContent-Type: application/json\r\nContent-Length: 13\r\n\r\n";
128 let body = b"{\"key\":\"value\"}";
129
130 let request = HttpRequest::parse_from(request_str, body).unwrap();
131
132 assert_eq!(request.method, HttpMethod::POST);
133 assert_eq!(request.path, "/api/data");
134 assert_eq!(request.version, "HTTP/1.1");
135 assert_eq!(request.headers.len(), 2);
136 assert_eq!(request.body, b"{\"key\":\"value\"}");
137
138 let content_type_header = request
140 .headers
141 .iter()
142 .find(|h| h.name == "Content-Type")
143 .unwrap();
144 assert_eq!(content_type_header.value, "application/json");
145 }
146
147 #[test]
148 fn test_parse_request_invalid_method() {
149 let request_str = "INVALID /path HTTP/1.1\r\n\r\n";
150 let body = b"";
151
152 let result = HttpRequest::parse_from(request_str, body);
153 assert!(result.is_err());
154 }
155
156 #[test]
157 fn test_parse_request_missing_parts() {
158 let request_str = "GET HTTP/1.1\r\n\r\n";
160 let body = b"";
161 let result = HttpRequest::parse_from(request_str, body);
162 assert!(result.is_err());
163
164 let request_str = "GET /path\r\n\r\n";
166 let result = HttpRequest::parse_from(request_str, body);
167 assert!(result.is_err());
168
169 let request_str = "";
171 let result = HttpRequest::parse_from(request_str, body);
172 assert!(result.is_err());
173 }
174
175 #[test]
176 fn test_parse_request_all_http_methods() {
177 let methods = [
178 ("GET", HttpMethod::GET),
179 ("POST", HttpMethod::POST),
180 ("PUT", HttpMethod::PUT),
181 ("DELETE", HttpMethod::DELETE),
182 ("PATCH", HttpMethod::PATCH),
183 ("HEAD", HttpMethod::HEAD),
184 ("OPTIONS", HttpMethod::OPTIONS),
185 ("TRACE", HttpMethod::TRACE),
186 ("CONNECT", HttpMethod::CONNECT),
187 ];
188
189 for (method_str, expected_method) in &methods {
190 let request_str = format!("{method_str} /path HTTP/1.1\r\n\r\n");
191 let request = HttpRequest::parse_from(&request_str, b"").unwrap();
192 assert_eq!(request.method, *expected_method);
193 }
194 }
195
196 #[test]
197 fn test_find_double_crlf() {
198 let data = b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\nBody";
200 assert_eq!(find_double_crlf(data), Some(33));
201
202 let data = b"\r\n\r\nBody";
204 assert_eq!(find_double_crlf(data), Some(0));
205
206 let data = b"Headers\r\n\r\n";
208 assert_eq!(find_double_crlf(data), Some(7));
209
210 let data = b"GET / HTTP/1.1\r\nHost: example.com\r\n";
212 assert_eq!(find_double_crlf(data), None);
213
214 let data = b"\r\n\r";
216 assert_eq!(find_double_crlf(data), None);
217
218 let data = b"";
220 assert_eq!(find_double_crlf(data), None);
221 }
222
223 #[test]
224 fn test_try_from_complete_request() {
225 let buffer = b"GET /index.html HTTP/1.1\r\nHost: example.com\r\nUser-Agent: test\r\n\r\n";
226
227 let request = HttpRequest::try_from(buffer.as_slice()).unwrap();
228
229 assert_eq!(request.method, HttpMethod::GET);
230 assert_eq!(request.path, "/index.html");
231 assert_eq!(request.version, "HTTP/1.1");
232 assert_eq!(request.headers.len(), 2);
233 assert_eq!(request.body, b"");
234 }
235
236 #[test]
237 fn test_try_from_request_with_body() {
238 let buffer =
239 b"POST /api/data HTTP/1.1\r\nContent-Type: application/json\r\n\r\n{\"key\":\"value\"}";
240
241 let request = HttpRequest::try_from(buffer.as_slice()).unwrap();
242
243 assert_eq!(request.method, HttpMethod::POST);
244 assert_eq!(request.path, "/api/data");
245 assert_eq!(request.version, "HTTP/1.1");
246 assert_eq!(request.headers.len(), 1);
247 assert_eq!(request.body, b"{\"key\":\"value\"}");
248 }
249
250 #[test]
251 fn test_try_from_incomplete_headers() {
252 let buffer = b"GET /index.html HTTP/1.1\r\nHost: example.com\r\n";
253
254 let result = HttpRequest::try_from(buffer.as_slice());
255 assert!(result.is_err());
256 }
257
258 #[test]
259 fn test_try_from_invalid_utf8() {
260 let mut buffer: Vec<u8, 128> = Vec::new();
262 let _ = buffer.extend_from_slice(b"GET /index.html HTTP/1.1\r\nHost: ");
263 let _ = buffer.push(0xFF); let _ = buffer.extend_from_slice(b"\r\n\r\n");
265
266 let result = HttpRequest::try_from(buffer.as_slice());
267 assert!(result.is_err());
268 }
269}