1use std::{
2 io::{BufRead, BufReader, Write},
3 net::{TcpListener, TcpStream},
4 thread,
5};
6
7use config::Config;
8mod config;
9
10use response::Response;
11mod response;
12
13mod page;
14
15use cache::Cache;
16mod cache;
17
18mod content;
19
20pub struct Server {
21 listener: TcpListener,
22 config: Config,
23 cache: Option<Cache>,
24 requests_handled: u128,
25}
26
27impl Server {
28 pub fn new() -> Self {
29 let config: Config = confy::load("salient", None).unwrap();
30 let listener = TcpListener::bind(&config.address).unwrap();
31
32 let cache: Option<Cache> = if config.caching {
33 Some(Cache::new())
34 } else {
35 None
36 };
37
38 let requests_handled = 0u128;
39
40 Server {
41 listener,
42 config,
43 cache,
44 requests_handled,
45 }
46 }
47
48 pub fn run(&'static mut self) {
49 let mut threads: Vec<thread::JoinHandle<_>> = Vec::new();
50 let output_delay = self.config.statistics_output_delay as u128;
51 for stream in self.listener.incoming() {
52 let stream = match stream {
53 Ok(stream) => stream,
54 Err(_) => continue,
55 };
56
57 if self.config.statistics {
58 self.requests_handled += 1;
59 if self.requests_handled % output_delay == 0 {
60 println!("Requests handled: {}", self.requests_handled);
61 }
62 }
63
64 loop {
65 if threads.len() < self.config.thread_limit {
66 break;
67 } else {
68 let _ = threads.remove(0).join();
69 }
70 }
71
72 threads.push(thread::spawn(|| {
73 Self::handle_connection(stream, &self.config.double_dot_defence, &self.cache);
74 }));
75 }
76 }
77
78 fn handle_connection(mut stream: TcpStream, double_dot_defence: &bool, cache: &Option<Cache>) {
79 let buf_reader = BufReader::new(&mut stream);
80 let http_request: Vec<_> = buf_reader
81 .lines()
82 .map(|result| result.unwrap())
83 .take_while(|line| !line.is_empty())
84 .collect();
85
86 let request_type = http_request[0].split(' ').nth(0).unwrap_or("GET");
87 let path = http_request[0].split(' ').nth(1).unwrap_or("/");
88
89 if request_type == "GET" {
90 let path = content::format_path(path, &double_dot_defence);
91 let response: Response = match cache {
92 None => content::page_from_file(&path),
93 Some(cache) => content::page_from_cache(&cache, &path),
94 };
95 let _ = stream.write_all(response.bytes());
96 } else if request_type == "POST" {
97 match path {
98 _ => {}
99 }
100 }
101 }
102}