tinyhttp_internal/
http.rs1use 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 }
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); 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 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 let request = unsafe { request.unwrap_unchecked() };
256 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 }
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 #[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}