express_rs/
http.rs

1use std::collections::hash_map::HashMap;
2
3/// Common HTTP Methods
4///
5/// If a method is needed, which is not specified here,
6/// `Method::UNKNOWN(String)` can be used
7#[derive(Debug, PartialEq)]
8pub enum Method {
9    GET,
10    POST,
11    PUT,
12    PATCH,
13    DELETE,
14    UNKNOWN(String),
15}
16
17/// Represents a HTTP request
18#[derive(Debug, PartialEq)]
19pub struct Request {
20    pub method: Method,
21    pub path: String,
22    pub version: String,
23    pub body: Option<String>,
24    pub headers: HashMap<String, String>,
25}
26
27impl Request {
28    /// takes a request as string and parses all relevant fields
29    pub fn from_string(s: String) -> Result<Self, &'static str> {
30        let fields = s.split_whitespace().collect::<Vec<_>>();
31        Ok(Request {
32            method: parse_method(&fields)?,
33            path: parse_path(&fields)?,
34            version: parse_version(&fields)?,
35            body: parse_body(&s),
36            headers: parse_headers(&s)?,
37        })
38    }
39}
40
41fn parse_version(fields: &[&str]) -> Result<String, &'static str> {
42    fields
43        .get(2)
44        .map(|&s| String::from(s))
45        .ok_or("Could not parse HTTP version")
46}
47
48fn parse_path(fields: &[&str]) -> Result<String, &'static str> {
49    fields
50        .get(1)
51        .map(|&s| String::from(s))
52        .ok_or("Could not parse HTTP version")
53}
54
55fn parse_method(fields: &[&str]) -> Result<Method, &'static str> {
56    match fields.get(0).cloned() {
57        Some("GET") => Ok(Method::GET),
58        Some("POST") => Ok(Method::POST),
59        Some("PUT") => Ok(Method::PUT),
60        Some("PATCH") => Ok(Method::PATCH),
61        Some("DELETE") => Ok(Method::DELETE),
62        // FIXME: This will recognize things as HTTP methods that are not.
63        Some(method) => Ok(Method::UNKNOWN(method.to_string())),
64        None => Err("Could not parse HTTP method"),
65    }
66}
67
68/// Parses the body of a request
69fn parse_body(s: &str) -> Option<String> {
70    // RFC 7230 Section 3: Body begins after two CRLF (\r\n) sequences.
71    // See: https://tools.ietf.org/html/rfc7230#section-3
72    let text = s.split("\r\n\r\n").skip(1).collect::<String>();
73    if text.is_empty() {
74        None
75    } else {
76        Some(text)
77    }
78}
79
80fn parse_headers(s: &str) -> Result<HashMap<String, String>, &'static str> {
81    // RFC 7230 Section 3: Header section (start-line) ends, when two CRLF (\r\n) sequences are encountered.
82    // See: https://tools.ietf.org/html/rfc7230#section-3
83    let raw_header_section = s.split("\r\n\r\n").next().unwrap_or_default();
84
85    // RFC 7230 Section 3.2: Each header is separated by one CRLF.
86    // See: https://tools.ietf.org/html/rfc7230#section-3.2
87    let raw_headers = raw_header_section.split("\r\n").skip(1).collect::<Vec<_>>();
88    let mut map = HashMap::new();
89
90    for header in raw_headers {
91        let sections = header.split(':').collect::<Vec<_>>();
92        let field_name = sections.get(0);
93        let field_value = sections.get(1);
94
95        // RFC 7230 Section 3.2.4: Empty header names or fields render the request invalid.
96        // See: https://tools.ietf.org/html/rfc7230#section-3.2.4
97        field_name
98            .and(field_value)
99            .ok_or("Error while parsing request headers")?;
100
101        if let Some(field_name) = field_name {
102            // RFC 7230 Section 3.2.4: No whitespace is allowed between the header field-name and colon.
103            // See: https://tools.ietf.org/html/rfc7230#section-3.2.4
104            if field_name
105                .chars()
106                .last()
107                .filter(|c| c.is_whitespace())
108                .is_some()
109            {
110                return Err("No whitespace is allowed between the header field-name and colon");
111            }
112
113            if let Some(field_value) = field_value {
114                map.insert(
115                    field_name.split_whitespace().collect(),
116                    field_value.split_whitespace().collect(),
117                );
118            }
119        }
120    }
121
122    Ok(map)
123}
124
125/// Represents a HTTP response
126#[derive(Debug, PartialEq)]
127pub struct Response {
128    pub stream: String,
129    pub headers: Vec<String>,
130    pub(crate) status: u16,
131}
132
133impl Default for Response {
134    fn default() -> Self {
135        Response::new()
136    }
137}
138
139impl Response {
140    pub fn new() -> Self {
141        Self {
142            stream: String::new(),
143            headers: Vec::new(),
144            status: 200,
145        }
146    }
147
148    /// Writes plain text to the response buffer
149    pub fn send(&mut self, s: String) {
150        self.stream.push_str(&s);
151    }
152
153    /// Change the status code of a response
154    ///
155    /// # Examples
156    ///
157    /// ```
158    /// use express_rs::Express;
159    ///
160    /// let mut app = Express::new();
161    ///
162    /// app.get("/", |_, res| {
163    ///     res.status(301).send("This route has a custom status code".to_string())
164    /// });
165    /// ```
166    pub fn status(&mut self, status: u16) -> &mut Self {
167        self.status = status;
168        self
169    }
170}