tinyhttp_internal/
http.rs

1use std::{
2    io::{self, BufRead, BufReader},
3    net::TcpStream,
4    path::Path,
5    sync::Arc,
6};
7
8use std::{fs::File, io::Read};
9
10use crate::{
11    config::{Config, HttpListener},
12    headers::HeaderMap,
13    request::{Request, RequestError},
14    response::Response,
15};
16
17#[cfg(feature = "sys")]
18use flate2::{write::GzEncoder, Compression};
19use unicase::Ascii;
20
21pub fn start_http(http: HttpListener, config: Config) {
22    #[cfg(feature = "log")]
23    log::info!(
24        "Listening on port {}",
25        http.socket.local_addr().unwrap().port()
26    );
27
28    let arc_config = Arc::new(config);
29    for stream in http.get_stream() {
30        let mut conn = stream.unwrap();
31
32        let config = arc_config.clone();
33        if http.use_pool {
34            http.pool.execute(move || {
35                #[cfg(feature = "log")]
36                log::trace!("parse_request() called");
37
38                parse_request(&mut conn, config);
39            });
40        } else {
41            #[cfg(feature = "log")]
42            log::trace!("parse_request() called");
43
44            parse_request(&mut conn, config);
45        }
46    }
47}
48
49fn build_and_parse_req<P: Read>(conn: &mut P) -> Result<Request, RequestError> {
50    let mut buf_reader = BufReader::new(conn);
51    let mut status_line_str = String::new();
52
53    buf_reader.read_line(&mut status_line_str).unwrap();
54    status_line_str.drain(status_line_str.len() - 2..status_line_str.len());
55
56    #[cfg(feature = "log")]
57    log::trace!("STATUS LINE: {:#?}", status_line_str);
58
59    let iter = buf_reader.fill_buf().unwrap();
60    let header_end_idx = iter
61        .windows(4)
62        .position(|w| matches!(w, b"\r\n\r\n"))
63        .unwrap();
64
65    #[cfg(feature = "log")]
66    log::trace!("Body starts at {}", header_end_idx);
67    let headers_buf = iter[..header_end_idx + 2].to_vec();
68
69    buf_reader.consume(header_end_idx + 4); // Add 4 bytes since header_end_idx does not count
70                                            // \r\n\r\n
71
72    let mut headers = HeaderMap::new();
73    let mut headers_index = 0;
74
75    let mut headers_buf_iter = headers_buf.windows(2).enumerate();
76
77    //Sort through all request headers
78    while let Some(header_index) = headers_buf_iter
79        .find(|(_, w)| matches!(*w, b"\r\n"))
80        .map(|(i, _)| i)
81    {
82        #[cfg(feature = "log")]
83        log::trace!("header index: {}", header_index);
84
85        let header = std::str::from_utf8(&headers_buf[headers_index..header_index]).unwrap();
86
87        if header.is_empty() {
88            break;
89        }
90        #[cfg(feature = "log")]
91        log::trace!("HEADER: {:?}", header);
92
93        headers_index = header_index + 2;
94
95        let mut colon_split = header.splitn(2, ':');
96        headers.set(
97            colon_split.next().unwrap(),
98            colon_split.next().unwrap().trim(),
99        );
100    }
101
102    let body_len = headers
103        .get(Ascii::new("Content-Length".to_string()))
104        .map(|str| str.parse::<usize>().unwrap())
105        .unwrap_or(0usize);
106
107    let mut raw_body = vec![0; body_len];
108    buf_reader.read_exact(&mut raw_body).unwrap();
109
110    Ok(Request::new(
111        raw_body,
112        headers,
113        status_line_str
114            .split_whitespace()
115            .map(|s| s.to_string())
116            .collect(),
117        None,
118    ))
119}
120
121fn build_res(mut req: Request, config: &Config, sock: &mut TcpStream) -> Response {
122    let status_line = req.get_status_line();
123    #[cfg(feature = "log")]
124    log::trace!("build_res -> req_path: {}", status_line[1]);
125
126    match status_line[0].as_str() {
127        "GET" => match config.get_routes(&status_line[1]) {
128            Some(route) => {
129                #[cfg(feature = "log")]
130                log::trace!("Found path in routes!");
131
132                if route.wildcard().is_some() {
133                    let stat_line = &status_line[1];
134                    let split = stat_line
135                        .split(&(route.get_path().to_string() + "/"))
136                        .last()
137                        .unwrap();
138
139                    req.set_wildcard(Some(split.into()));
140                };
141
142                route.to_res(req, sock)
143            }
144
145            None => match config.get_mount() {
146                Some(old_path) => {
147                    let path = old_path.to_owned() + &status_line[1];
148                    if Path::new(&path).extension().is_none() && config.get_spa() {
149                        let body = read_to_vec(old_path.to_owned() + "/index.html").unwrap();
150                        let line = "HTTP/1.1 200 OK\r\n";
151
152                        Response::new()
153                            .status_line(line)
154                            .body(body)
155                            .mime("text/html")
156                    } else if Path::new(&path).is_file() {
157                        let body = read_to_vec(&path).unwrap();
158                        let line = "HTTP/1.1 200 OK\r\n";
159                        let mime = mime_guess::from_path(&path)
160                            .first_raw()
161                            .unwrap_or("text/plain");
162                        Response::new().status_line(line).body(body).mime(mime)
163                    } else if Path::new(&path).is_dir() {
164                        if Path::new(&(path.to_owned() + "/index.html")).is_file() {
165                            let body = read_to_vec(path + "/index.html").unwrap();
166
167                            let line = "HTTP/1.1 200 OK\r\n";
168                            Response::new()
169                                .status_line(line)
170                                .body(body)
171                                .mime("text/html")
172                        } else {
173                            Response::new()
174                                .status_line("HTTP/1.1 200 OK\r\n")
175                                .body(b"<h1>404 Not Found</h1>".to_vec())
176                                .mime("text/html")
177                        }
178                    } else if Path::new(&(path.to_owned() + ".html")).is_file() {
179                        let body = read_to_vec(path + ".html").unwrap();
180                        let line = "HTTP/1.1 200 OK\r\n";
181                        Response::new()
182                            .status_line(line)
183                            .body(body)
184                            .mime("text/html")
185                    } else {
186                        Response::new()
187                            .status_line("HTTP/1.1 404 NOT FOUND\r\n")
188                            .body(b"<h1>404 Not Found</h1>".to_vec())
189                            .mime("text/html")
190                    }
191                }
192
193                None => Response::new()
194                    .status_line("HTTP/1.1 404 NOT FOUND\r\n")
195                    .body(b"<h1>404 Not Found</h1>".to_vec())
196                    .mime("text/html"),
197            },
198        },
199        "POST" => match config.post_routes(&status_line[1]) {
200            Some(route) => {
201                #[cfg(feature = "log")]
202                log::debug!("POST");
203
204                if route.wildcard().is_some() {
205                    let stat_line = &status_line[1];
206
207                    let split = stat_line
208                        .split(&(route.get_path().to_string() + "/"))
209                        .last()
210                        .unwrap();
211
212                    req.set_wildcard(Some(split.into()));
213                };
214
215                route.to_res(req, sock)
216            }
217
218            None => Response::new()
219                .status_line("HTTP/1.1 404 NOT FOUND\r\n")
220                .body(b"<h1>404 Not Found</h1>".to_vec())
221                .mime("text/html"),
222        },
223
224        _ => Response::new()
225            .status_line("HTTP/1.1 404 NOT FOUND\r\n")
226            .body(b"<h1>Unkown Error Occurred</h1>".to_vec())
227            .mime("text/html"),
228    }
229}
230
231pub fn parse_request(conn: &mut TcpStream, config: Arc<Config>) {
232    let request = build_and_parse_req(conn);
233
234    let request = match request {
235        Ok(request) => request,
236        Err(e) => {
237            let specific_err = match e {
238                RequestError::StatusLineErr => b"failed to parse status line".to_vec(),
239                RequestError::HeadersErr => b"failed to parse headers".to_vec(),
240            };
241            Response::new()
242                .mime("text/plain")
243                .body(specific_err)
244                .send(conn);
245
246            return;
247        }
248    };
249
250    /*#[cfg(feature = "middleware")]
251    if let Some(req_middleware) = config.get_request_middleware() {
252        req_middleware.lock().unwrap()(&mut request);
253    };*/
254
255    let req_headers = request.get_headers();
256    let _comp = if config.get_gzip() {
257        if req_headers.contains("Accept-Encoding") {
258            let tmp_str = req_headers.get("Accept-Encoding").unwrap();
259            let res: Vec<&str> = tmp_str.split(',').map(|s| s.trim()).collect();
260
261            #[cfg(feature = "log")]
262            log::trace!("{:#?}", &res);
263
264            res.contains(&"gzip")
265        } else {
266            false
267        }
268    } else {
269        false
270    };
271
272    let mut response = build_res(request, &config, conn);
273    if response.manual_override {
274        conn.shutdown(std::net::Shutdown::Both).unwrap();
275        return;
276    }
277
278    match response.mime {
279        Some(ref t) => {
280            response
281                .headers
282                .insert("Content-Type".to_string(), t.to_owned());
283        }
284        None => {
285            if let Some(body) = &response.body {
286                let mime = infer::get(body)
287                    .map(|mime| mime.mime_type())
288                    .unwrap_or("text/plain");
289
290                response
291                    .headers
292                    .insert("Content-Type".to_string(), mime.to_string());
293            }
294        }
295    }
296
297    if let Some(config_headers) = config.get_headers() {
298        response.headers.extend(
299            config_headers
300                .iter()
301                .map(|(i, j)| (i.to_owned(), j.to_owned())),
302        );
303    }
304
305    response.headers.extend([(
306        "tinyhttp".to_string(),
307        env!("CARGO_PKG_VERSION").to_string(),
308    )]);
309
310    // Only check for 'accept-encoding' header
311    // when compression is enabled
312
313    #[cfg(feature = "sys")]
314    {
315        if _comp {
316            use std::io::Write;
317            let mut writer = GzEncoder::new(Vec::new(), Compression::default());
318            writer.write_all(response.body.as_ref().unwrap()).unwrap();
319            response.body = Some(writer.finish().unwrap());
320            response
321                .headers
322                .insert("Content-Encoding".to_string(), "gzip".to_string());
323        }
324    }
325
326    #[cfg(feature = "log")]
327    {
328        log::trace!(
329            "RESPONSE BODY: {:#?},\n RESPONSE HEADERS: {:#?}\n",
330            response.body.as_ref().unwrap(),
331            response.headers,
332        );
333    }
334
335    /*#[cfg(feature = "middleware")]
336    if let Some(middleware) = config.get_response_middleware() {
337        middleware.lock().unwrap()(res_brw.deref_mut());
338    }*/
339
340    response.send(conn);
341}
342
343fn read_to_vec<P: AsRef<Path>>(path: P) -> io::Result<Vec<u8>> {
344    fn inner(path: &Path) -> io::Result<Vec<u8>> {
345        let file = File::open(path).unwrap();
346        let mut content: Vec<u8> = Vec::new();
347        let mut reader = BufReader::new(file);
348        reader.read_to_end(&mut content).unwrap();
349        Ok(content)
350    }
351    inner(path.as_ref())
352}