lazyhttp/
lib.rs

1use std::collections::HashMap;
2use std::error::Error;
3use std::io::{BufRead, BufReader, Read};
4
5pub fn handle_stream<T>(stream: &T) -> Result<Request, Box<dyn Error + Send + Sync + 'static>>
6where
7    for<'a> &'a T: std::io::Read,
8{
9    let mut buf_reader = BufReader::new(stream);
10
11    let mut line_buf = String::new();
12
13    if let Err(_) = buf_reader.read_line(&mut line_buf) {
14        panic!("Bad Request");
15    }
16
17    let mut request_parts: Vec<&str> = line_buf.split_whitespace().collect();
18
19    // We only want POST requests being made
20
21    let method = match request_parts.get(0) {
22        Some(method) => method.to_string(),
23        None => panic!("No request method"),
24    };
25
26    let mut headers = HashMap::new();
27
28    loop {
29        let mut line_buf = String::new();
30
31        if let Err(_) = buf_reader.read_line(&mut line_buf) {
32            panic!("Bad Request");
33        }
34
35        if line_buf.is_empty() || line_buf == "\n" || line_buf == "\r\n" {
36            break;
37        }
38
39        let mut comps = line_buf.split(":");
40        let key = comps.next().unwrap_or("None");
41        let value = comps.next().unwrap_or("None").trim();
42
43        headers.insert(key.to_string(), value.to_string());
44    }
45
46    let body;
47
48    if let Some(length) = headers.get("Content-Length") {
49        let mut bytes = vec![0_u8; length.parse().expect("Bad Content Length Header")];
50
51        buf_reader
52            .read_exact(&mut bytes)
53            .expect("Failed to read content!");
54
55        body = Some(String::from_utf8(bytes).expect("Invalid String!"));
56    } else {
57        body = None;
58    }
59
60    Ok(Request {
61        method: method,
62        route: request_parts.swap_remove(1).to_string(),
63        version: request_parts.pop().unwrap().to_string(),
64        headers: headers,
65        body: body,
66    })
67}
68
69pub struct Request {
70    pub method: String,
71    pub route: String,
72    pub version: String,
73    pub headers: HashMap<String, String>,
74    pub body: Option<String>,
75}
76
77impl Request {
78    /// Outputs a new owned string of the full request.
79    pub fn as_text(&self) -> String{
80        match &self.body {
81            Some(body) => {
82                let mut r = String::new();
83                r.push_str(&format!("{} {} {}\r\n", self.method, self.route, self.version));
84                for (k , v) in &self.headers {
85                    r.push_str(&format!("{}: {}\r\n", k, v));
86                }
87
88                r.push_str("\r\n");
89                r.push_str(&body);
90
91                r
92            }
93            None => {
94                let mut r = String::new();
95                r.push_str(&format!("{} {} {}\r\n", self.method, self.route, self.version));
96                for (k , v) in &self.headers {
97                    r.push_str(&format!("{}: {}\r\n", k, v));
98                }
99
100                r.push_str("\r\n");
101
102                r
103            }
104        }
105    }
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111
112    #[test]
113    fn test_as_text_body() {
114        let mut headers = HashMap::new();
115        headers.insert("Test".into(), "Value".into());
116
117        let req = Request {
118            method: "POST".to_string(),
119            route: "/".to_string(),
120            version: "HTTP/1".to_string(),
121            headers: headers,
122            body: Some("My body".into()),
123        };
124
125        assert_eq!(req.as_text(), "POST / HTTP/1\r\nTest: Value\r\n\r\nMy body");
126    }
127
128    #[test]
129    fn test_as_text() {
130        let mut headers = HashMap::new();
131        headers.insert("Test".into(), "Value".into());
132
133        let req = Request {
134            method: "POST".to_string(),
135            route: "/".to_string(),
136            version: "HTTP/1".to_string(),
137            headers: headers,
138            body: None,
139        };
140
141        assert_eq!(req.as_text(), "POST / HTTP/1\r\nTest: Value\r\n\r\n");
142
143    }
144}