haro/http/
request.rs

1use std::collections::HashMap;
2
3use cookie::Cookie;
4use http::{
5    header::{CONTENT_LENGTH, CONTENT_TYPE, COOKIE},
6    HeaderMap, HeaderValue, Request as HttpRequest, Version,
7};
8use log::warn;
9
10use crate::http::{
11    conn::Conn,
12    utils::{parse_json_body, parse_query, read_headers},
13};
14
15/// HTTP Request
16#[derive(Debug)]
17pub struct Request {
18    req: HttpRequest<Vec<u8>>,
19    pub args: HashMap<String, String>,
20    pub data: HashMap<String, String>,
21    pub params: HashMap<String, String>,
22}
23
24impl Request {
25    /// Create a new `Request`
26    /// # Example
27    /// ```
28    /// use std::collections::HashMap;
29    /// use haro::Request;
30    ///
31    /// let headers = HashMap::new();
32    /// let body = &Vec::new();
33    /// let mut req = Request::new("get", "/", headers, body);
34    /// ```
35    pub fn new(method: &str, uri: &str, headers: HashMap<String, String>, body: &[u8]) -> Self {
36        let mut builder = HttpRequest::builder().method(method).uri(uri);
37        let content_length = body.len();
38        let mut content_type = String::new();
39        for (key, value) in headers {
40            if key.to_lowercase() == CONTENT_TYPE.as_str().to_lowercase() {
41                content_type = value.clone();
42            }
43            builder = builder.header(key, value);
44        }
45
46        let req = builder
47            .header(CONTENT_LENGTH, body.len())
48            .body(body.to_vec())
49            .unwrap();
50
51        let args = parse_query(req.uri().query());
52        let mut data = HashMap::new();
53        if content_length > 0 {
54            data = match content_type.as_str() {
55                "application/json" => parse_json_body(req.body()),
56                _ => {
57                    warn!("unsupported content type {}", content_type);
58                    HashMap::new()
59                }
60            };
61        }
62        Self {
63            req,
64            args,
65            data,
66            params: HashMap::new(),
67        }
68    }
69    /// Create a new `Request` from a TCP connectio
70    pub fn from(conn: &mut Conn) -> Self {
71        // parse method, uri and version
72        let mut buf = String::new();
73        conn.read_line(&mut buf);
74        let line: Vec<&str> = buf.trim().split(' ').collect();
75        let (method, uri, version) = (line[0], line[1], line[2]);
76        let version = match version {
77            "HTTP/2.0" => Version::HTTP_2,
78            "HTTP/3.0" => Version::HTTP_3,
79            _ => Version::HTTP_11,
80        };
81        let mut builder = HttpRequest::builder()
82            .method(method)
83            .uri(uri)
84            .version(version);
85
86        // parse headers
87        let mut content_length = 0;
88        let mut content_type = String::new();
89        for (key, value) in read_headers(conn) {
90            if key.to_lowercase() == CONTENT_LENGTH.as_str().to_lowercase() {
91                content_length = value.parse().unwrap();
92            } else if key.to_lowercase() == CONTENT_TYPE.as_str().to_lowercase() {
93                content_type = value.clone();
94            }
95            builder = builder.header(key, value);
96        }
97
98        // parse body
99        let mut body = Vec::<u8>::new();
100        body.resize(content_length, 0);
101        conn.read_exact(&mut body);
102        let req = builder.body(body).unwrap();
103
104        let args = parse_query(req.uri().query());
105        let mut data = HashMap::new();
106        if content_length > 0 {
107            data = match content_type.as_str() {
108                "application/json" => parse_json_body(req.body()),
109                _ => {
110                    // TODO: support more content types
111                    warn!("unsupported content type {}", content_type);
112                    HashMap::new()
113                }
114            };
115        }
116
117        Self {
118            req,
119            args,
120            data,
121            params: HashMap::new(),
122        }
123    }
124
125    /// HTTP method for current `Request`
126    pub fn method(&self) -> &str {
127        self.req.method().as_str()
128    }
129
130    /// HTTP path for current `Request`
131    pub fn path(&self) -> &str {
132        self.req.uri().path()
133    }
134
135    /// HTTP full path with query args
136    pub fn full_path(&self) -> &str {
137        if let Some(full_path) = self.req.uri().path_and_query() {
138            return full_path.as_str();
139        }
140        ""
141    }
142
143    /// HTTP headers for current `Request`
144    pub fn headers(&self) -> &HeaderMap<HeaderValue> {
145        self.req.headers()
146    }
147
148    /// HTTP cookies for current `Request`
149    pub fn cookies(&self) -> HashMap<String, String> {
150        let headers = self.headers();
151        let cookies = headers
152            .get(COOKIE)
153            .map(|v| Cookie::split_parse(v.to_str().unwrap()));
154
155        let mut cookies_map = HashMap::new();
156        for cookie in cookies.into_iter().flatten().flatten() {
157            cookies_map.insert(cookie.name().to_string(), cookie.value().to_string());
158        }
159
160        cookies_map
161    }
162}