Skip to main content

torus_http/
request.rs

1//! This module handles parsing the client's request into a simple to work with data structure
2use std::{collections::HashMap, str::FromStr};
3
4use crate::method::HttpMethod;
5
6#[derive(Clone, Debug, PartialEq, Eq)]
7/// The incoming request
8pub struct HttpRequest {
9    /// i.e. Get, Post, etc...
10    pub method: HttpMethod,
11    /// Hath, currently including query parameters in the string
12    pub path: String,
13    pub headers: HashMap<String, String>,
14    pub body: Option<String>,
15    /// Parameter map after '?' in a request
16    pub query: Option<HashMap<String, String>>,
17}
18
19impl FromStr for HttpRequest {
20    type Err = std::io::Error;
21
22    fn from_str(input: &str) -> Result<Self, Self::Err> {
23        let mut first_line = input
24            .lines()
25            .next()
26            .ok_or({
27                std::io::Error::new(
28                    std::io::ErrorKind::InvalidInput,
29                    "failed getting first http line",
30                )
31            })?
32            .split_whitespace();
33
34        let (Some(method_str), Some(path), Some(_version)) =
35            (first_line.next(), first_line.next(), first_line.next())
36        else {
37            return Err(std::io::Error::new(
38                std::io::ErrorKind::InvalidInput,
39                "Invalid http top header",
40            ));
41        };
42
43        let method = HttpMethod::from_str_val(method_str);
44
45        let (path, query): (&str, Option<HashMap<String, String>>) = match path.split_once('?') {
46            Some((path, query)) => {
47                let query_map: HashMap<String, String> = query
48                    .split('&')
49                    .filter_map(|q| q.split_once('='))
50                    .map(|(k, v)| (k.to_owned(), v.to_owned()))
51                    .collect();
52                (path, Some(query_map))
53            }
54            None => (path, None),
55        };
56
57        let headers: HashMap<String, String> = input
58            .lines()
59            .take_while(|line| !line.is_empty())
60            .skip(1)
61            .filter_map(|line| {
62                line.split_once(':')
63                    .map(|(k, v)| (k.trim().to_owned(), v.trim().to_owned()))
64            })
65            .collect();
66
67        let body = if method == HttpMethod::Get {
68            None
69        } else {
70            input
71                .split_once("\r\n\r\n")
72                .map(|head_body_split| head_body_split.1.to_owned())
73                .filter(|body| !body.is_empty())
74        };
75
76        let path = path.to_owned();
77
78        let req: HttpRequest = HttpRequest {
79            method,
80            path,
81            headers,
82            body,
83            query,
84        };
85        Ok(req)
86    }
87}