tinyhttp_internal/
http.rs

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