tinyhttp_internal/
http.rs1use 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); 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 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 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 #[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 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}