Skip to main content

rust_web_server/request/
mod.rs

1#[cfg(test)]
2mod tests;
3#[cfg(test)]
4mod example;
5
6use std::collections::HashMap;
7use std::io;
8use std::io::{BufRead, Cursor, Read};
9use crate::header::Header;
10use crate::http::HTTP;
11use crate::symbol::SYMBOL;
12use crate::url::URL;
13
14/// A parsed HTTP request.
15#[derive(PartialEq, Eq, Clone, Debug)]
16pub struct Request {
17    /// HTTP method: `"GET"`, `"POST"`, etc. Compare with [`METHOD`] constants.
18    pub method: String,
19    /// Request URI including path and query string, e.g. `"/search?q=rust"`.
20    pub request_uri: String,
21    /// HTTP version string, e.g. `"HTTP/1.1"`. Compare with [`crate::http::VERSION`] constants.
22    pub http_version: String,
23    /// Request headers.
24    pub headers: Vec<Header>,
25    /// Raw request body bytes.
26    pub body: Vec<u8>,
27}
28
29#[derive(PartialEq, Eq, Clone, Debug)]
30pub struct Method {
31    pub get: &'static str,
32    pub head: &'static str,
33    pub post: &'static str,
34    pub put: &'static str,
35    pub delete: &'static str,
36    pub connect: &'static str,
37    pub options: &'static str,
38    pub trace: &'static str,
39    pub patch: &'static str,
40}
41
42pub const METHOD: Method = Method {
43    get: "GET",
44    head: "HEAD",
45    post: "POST",
46    put: "PUT",
47    delete: "DELETE",
48    connect: "CONNECT",
49    options: "OPTIONS",
50    trace: "TRACE",
51    patch: "PATCH",
52};
53
54impl Request {
55    pub const _ERROR_UNABLE_TO_PARSE_METHOD_AND_REQUEST_URI_AND_HTTP_VERSION: &'static str = "Unable to parse method, request uri and http version";
56
57    pub fn get_header(&self, name: String) -> Option<&Header> {
58        let header =  self.headers.iter().find(|x| x.name.to_lowercase() == name.to_lowercase());
59        header
60    }
61
62    pub fn get_domain(&self) -> Result<Option<String>, String> {
63        let boxed_host = &self.get_header(Header::_HOST.to_string());
64        if boxed_host.is_none() {
65            return Ok(None);
66        }
67
68        let host_header = boxed_host.unwrap();
69        let host = host_header.value.to_string();
70        if host.is_empty() {
71            return Ok(None); // `Host:` with no value names no domain
72        }
73
74        let boxed_host_port = host.split_once(&SYMBOL.colon.to_string());
75        if boxed_host_port.is_none() {
76            return Ok(Some(host))
77        }
78
79        let (domain, _port_str) = boxed_host_port.unwrap();
80
81        Ok(Some(domain.to_string()))
82    }
83
84    pub fn get_port(&self) -> Result<Option<i128>, String> {
85        let boxed_host = &self.get_header(Header::_HOST.to_string());
86        if boxed_host.is_none() {
87            return Ok(None);
88        }
89
90        let host_header = boxed_host.unwrap();
91        let host = host_header.value.to_string();
92
93        let boxed_host_port = host.split_once(&SYMBOL.colon.to_string());
94        if boxed_host_port.is_none() {
95            return Ok(None)
96        }
97
98        let (_domain, port_str) = boxed_host_port.unwrap();
99
100        let boxed_port_i128 = port_str.parse::<i128>();
101        if boxed_port_i128.is_err() {
102            let message = boxed_port_i128.err().unwrap().to_string();
103            return Err(message);
104        }
105
106        let port = boxed_port_i128.unwrap();
107
108        Ok(Some(port))
109    }
110
111    pub fn get_query(&self) -> Result<Option<HashMap<String, String>>, String> {
112        self.get_uri_query()
113    }
114
115    pub fn get_uri_query(&self) -> Result<Option<HashMap<String, String>>, String> {
116        // it will return an error if unable to parse url
117        // it will return None if there are no query params
118        // scheme and host required for the parse_url function
119        let url_array = ["http://", "localhost/", &self.request_uri];
120        let url = url_array.join(SYMBOL.empty_string);
121
122        let boxed_url_components = URL::parse(&url);
123        if boxed_url_components.is_err() {
124            let message = boxed_url_components.err().unwrap().to_string();
125            return Err(message)
126        }
127        Ok(boxed_url_components.unwrap().query)
128    }
129
130    pub fn get_path(&self) -> Result<String, String> {
131        self.get_uri_path()
132    }
133
134    pub fn get_uri_path(&self) -> Result<String, String> {
135        // scheme and host required for the parse_url function
136        let url = self.request_uri.to_string();
137
138        let boxed_url_components = crate::url::URL::parse_request_target(&url);
139        if boxed_url_components.is_err() {
140            let message = boxed_url_components.err().unwrap().to_string();
141            return Err(message)
142        }
143        Ok(boxed_url_components.unwrap().path)
144    }
145
146    /// `true` if `method` (any case) is one of [`Request::method_list`],
147    /// without building that list — it's checked on every request.
148    pub fn is_supported_method(method: &str) -> bool {
149        [METHOD.get, METHOD.head, METHOD.post, METHOD.put, METHOD.delete, METHOD.connect, METHOD.options, METHOD.trace, METHOD.patch]
150            .iter()
151            .any(|m| m.eq_ignore_ascii_case(method))
152    }
153
154    pub fn method_list() -> Vec<String> {
155        let method_get = METHOD.get.to_string();
156        let method_head = METHOD.head.to_string();
157        let method_post = METHOD.post.to_string();
158        let method_put = METHOD.put.to_string();
159        let method_delete = METHOD.delete.to_string();
160        let method_connect = METHOD.connect.to_string();
161        let method_options = METHOD.options.to_string();
162        let method_trace = METHOD.trace.to_string();
163        let method_patch = METHOD.patch.to_string();
164
165        let method_list = vec![
166            method_get,
167            method_head,
168            method_post,
169            method_put,
170            method_delete,
171            method_connect,
172            method_options,
173            method_trace,
174            method_patch,
175        ];
176
177        method_list
178    }
179
180    // instance method way to generate a request
181    pub fn generate(&self) -> Vec<u8> {
182        let clone = self.clone();
183        let mut request = Request::_generate_request(clone).as_bytes().to_vec();
184
185        let mut body = self.body.clone();
186        request.append(&mut body);
187
188        request
189    }
190
191    // same as _generate_request, not renamed original for backward compatability
192    pub fn generate_request(request: Request) -> String {
193        Request::_generate_request(request)
194    }
195
196    pub fn _generate_request(request: Request) -> String {
197        let status = [
198            request.method,
199            request.request_uri,
200            request.http_version,
201            SYMBOL.new_line_carriage_return.to_string()
202        ].join(SYMBOL.whitespace);
203
204        let mut headers = SYMBOL.empty_string.to_string();
205        for header in request.headers {
206            let mut header_string = SYMBOL.empty_string.to_string();
207            header_string.push_str(&header.name);
208            header_string.push_str(Header::NAME_VALUE_SEPARATOR);
209            header_string.push_str(&header.value);
210            header_string.push_str(SYMBOL.new_line_carriage_return);
211            headers.push_str(&header_string);
212        }
213
214        let request = format!(
215            "{}{}{}",
216            status,
217            headers,
218            SYMBOL.new_line_carriage_return
219        );
220
221
222        request
223    }
224
225    pub fn parse(request_vec_u8: &[u8]) ->  Result<Request, String> {
226        Request::parse_request(request_vec_u8)
227    }
228
229    pub fn parse_request(request_vec_u8: &[u8]) ->  Result<Request, String> {
230        let mut cursor = io::Cursor::new(request_vec_u8);
231
232        let mut request = Request {
233            method: "".to_string(),
234            request_uri: "".to_string(),
235            http_version: "".to_string(),
236            headers: vec![],
237            body: vec![],
238        };
239
240        let content_length: usize = 0;
241        let iteration_number : usize = 0;
242        return match Request::cursor_read(&mut cursor, iteration_number, &mut request, content_length) {
243            Ok(_) => {
244                Ok(request)
245            }
246            Err(error_message) => {
247                Err(error_message)
248            }
249        }
250
251    }
252
253
254    pub fn parse_method_and_request_uri_and_http_version_string(http_version_status_code_reason_phrase: &str) -> Result<(String, String, String), String> {
255        let lowercase_unparsed_method_and_request_uri_and_http_version = http_version_status_code_reason_phrase.trim();
256
257        let boxed_split_without_method = lowercase_unparsed_method_and_request_uri_and_http_version.split_once(SYMBOL.whitespace);
258        if boxed_split_without_method.is_none() {
259            return Err(Request::_ERROR_UNABLE_TO_PARSE_METHOD_AND_REQUEST_URI_AND_HTTP_VERSION.to_string())
260        }
261
262        let (method, without_method) = boxed_split_without_method.unwrap();
263        if !Request::is_supported_method(method) {
264            return Err(Request::_ERROR_UNABLE_TO_PARSE_METHOD_AND_REQUEST_URI_AND_HTTP_VERSION.to_string())
265        }
266
267        let boxed_without_method = without_method.split_once(SYMBOL.whitespace);
268        if boxed_without_method.is_none() {
269            return Err(Request::_ERROR_UNABLE_TO_PARSE_METHOD_AND_REQUEST_URI_AND_HTTP_VERSION.to_string())
270        }
271
272        let (request_uri, http_version) = boxed_without_method.unwrap();
273        let request_uri = Request::normalize_request_target(request_uri)
274            .ok_or_else(|| Request::_ERROR_UNABLE_TO_PARSE_METHOD_AND_REQUEST_URI_AND_HTTP_VERSION.to_string())?;
275
276
277        if !HTTP::is_supported_version(http_version) {
278            return Err(Request::_ERROR_UNABLE_TO_PARSE_METHOD_AND_REQUEST_URI_AND_HTTP_VERSION.to_string())
279        }
280
281        Ok((method.to_string(), request_uri, http_version.to_string()))
282
283    }
284
285    /// RFC 9112 §3.2 request targets: origin-form (`/path?query`) is kept,
286    /// absolute-form (`http://host/path?query`) is reduced to its
287    /// origin-form path, asterisk-form (`*`) is kept. Anything else —
288    /// authority-form (`host:443`, CONNECT is not served) or junk like `:x` —
289    /// is `None`, so the request is rejected before reaching a controller.
290    pub fn normalize_request_target(target: &str) -> Option<String> {
291        if target.starts_with('/') || target == "*" {
292            return Some(target.to_string());
293        }
294        let lower = target.to_ascii_lowercase();
295        let after_scheme = if lower.starts_with("http://") {
296            &target["http://".len()..]
297        } else if lower.starts_with("https://") {
298            &target["https://".len()..]
299        } else {
300            return None;
301        };
302        let path_start = after_scheme.find(|c| c == '/' || c == '?').unwrap_or(after_scheme.len());
303        if path_start == 0 {
304            return None; // no authority
305        }
306        let rest = &after_scheme[path_start..];
307        Some(if rest.starts_with('/') { rest.to_string() } else { format!("/{}", rest) })
308    }
309
310    /// `Name: value` → `Header`. The name ends at the first `:`; the value
311    /// is everything after it, without surrounding spaces/tabs (RFC 9112
312    /// §5.1). Splitting on every ": " used to cut a value that itself
313    /// contained ": " short, and `Name:value` (no space) had an empty value.
314    pub fn parse_http_request_header_string(header_string: &str) -> Header {
315        let line = header_string.trim_end_matches(['\r', '\n']);
316        match line.split_once(':') {
317            Some((name, value)) => Header {
318                name: name.to_string(),
319                value: value.trim_matches([' ', '\t']).to_string(),
320            },
321            None => Header { name: line.to_string(), value: String::new() },
322        }
323    }
324
325    /// Parse the request line, headers, and body from `cursor` into `request`.
326    ///
327    /// Iterative: this used to recurse once per header line, so a single
328    /// request with a few thousand short header lines overflowed the worker
329    /// thread's stack and aborted the whole process. Errors anywhere in the
330    /// head (bad UTF-8, an invalid Content-Length) are returned instead of
331    /// being logged and dropped.
332    pub fn cursor_read(cursor: &mut Cursor<&[u8]>, mut iteration_number: usize, request: &mut Request, content_length: usize) -> Result<bool, String> {
333        let _ = content_length;
334        loop {
335            let mut buf = vec![];
336            let bytes_offset = cursor.read_until(b'\n', &mut buf).map_err(|e| e.to_string())?;
337            let string = String::from_utf8(buf).map_err(|e| e.to_string())?;
338
339            let is_first_iteration = iteration_number == 0;
340            if is_first_iteration {
341                let (method, request_uri, http_version) =
342                    Request::parse_method_and_request_uri_and_http_version_string(&string)?;
343                request.method = method;
344                request.request_uri = request_uri;
345                request.http_version = http_version;
346            }
347
348            // Blank line (end of the head) or end of input.
349            if bytes_offset == 0 || string.trim().is_empty() {
350                break;
351            }
352
353            // The request line is not a header. (This used to push an empty
354            // `Header { name: "", value: "" }` for it, which the reverse proxy
355            // forwarded as a malformed ": " line — nginx answers 400.)
356            if !is_first_iteration {
357                let header = Request::parse_http_request_header_string(&string);
358                if header.name.eq_ignore_ascii_case(Header::_CONTENT_LENGTH) {
359                    // A client-supplied value: reject garbage (RFC 9112 §6.3) instead of panicking.
360                    let _: usize = header.value.trim().parse()
361                        .map_err(|_| format!("invalid Content-Length: {:?}", header.value))?;
362                }
363                request.headers.push(header);
364            }
365            iteration_number += 1;
366        }
367
368        // remaining part is request body
369        let mut body = vec![];
370        cursor.read_to_end(&mut body).map_err(|e| e.to_string())?;
371        request.body.append(&mut body);
372
373        Ok(true)
374    }
375
376}
377
378impl std::fmt::Display for Request {
379    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
380        write!(fmt, "Request method {} and request uri {} and http_version {}", self.method, self.request_uri, self.http_version)
381    }
382}