1pub mod prelude;
2pub mod rust_to_html;
3use owo_colors::OwoColorize;
4use std::{
5 fs::{self, File},
6 io::{Read, Write},
7 net::{TcpListener, TcpStream},
8 path::Path,
9 thread,
10};
11
12pub trait Debug {
13 fn debug(&self);
14}
15
16pub struct Server {
17 host: String,
18 port: u32,
19}
20
21pub struct ServerResponse {
22 content: String,
23}
24pub struct StyleResponse {
25 content: String,
26}
27pub struct Custom404 {
28 content: String,
29}
30#[derive(Clone)]
31pub struct CustomRoutes {
32 pub path: String,
33 pub content_type: String,
34 pub response: String,
35 pub styles: Option<String>,
36}
37impl Custom404 {
38 pub fn new(content: impl Into<String>) -> Self {
39 Self {
40 content: content.into(),
41 }
42 }
43 pub fn default() -> Self {
44 Self {
45 content: "404 page not found!".to_string(),
46 }
47 }
48}
49
50impl CustomRoutes {
51 pub fn new(
52 path: impl Into<String>,
53 content_type: impl Into<String>,
54 response: impl Into<String>,
55 css_file: Option<impl Into<String>>,
56 ) -> Self {
57 Self {
58 path: path.into(),
59 content_type: content_type.into(),
60 response: response.into(),
61 styles: css_file.map(Into::into),
62 }
63 }
64
65 pub fn include_css(&self) -> Option<String> {
66 if let Some(styles) = &self.styles {
67 match fs::read_to_string(styles) {
68 Ok(css) => Some(css),
69 Err(e) => {
70 eprintln!("Error reading CSS file: {}", e);
71 None
72 }
73 }
74 } else {
75 None
76 }
77 }
78}
79impl Server {
80 pub fn new(host: impl Into<String>, port: u32) -> Self {
81 Self {
82 host: host.into(),
83 port,
84 }
85 }
86
87 pub fn start(
88 &self,
89 content: ServerResponse,
90 style: StyleResponse,
91 custom_routes: Vec<CustomRoutes>,
92 custom_404: Custom404,
93 ) {
94 let custom404 = custom_404.content.clone();
95 let routes = custom_routes.clone();
96 fn handle_client(
97 mut stream: TcpStream,
98 content: String,
99 style: String,
100 custom_routes: Vec<CustomRoutes>,
101 custom_404: String,
102 ) {
103 let mut buffer = [0; 1024];
104 let css_content = include_css(&style);
105 match stream.read(&mut buffer) {
106 Ok(_) => {
107 let request = String::from_utf8_lossy(&buffer);
108
109 let response = if request.starts_with("GET / HTTP/1.1") {
110 format!(
112 "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n{}",
113 content
114 )
115 } else if request.starts_with("GET /style.css HTTP/1.1") {
116 format!(
117 "HTTP/1.1 200 OK\r\nContent-Type: text/css\r\n\r\n{}",
118 css_content
119 )
120 } else {
121 for route in custom_routes.iter() {
123 if request.starts_with(&format!("GET {} HTTP/1.1", route.path)) {
124 let response = match &route.content_type[..] {
125 "text/plain" => format!(
126 "HTTP/1.1 200 OK\r\nContent-Type: {}\r\n\r\n{}",
127 route.content_type, route.response
128 ),
129 "application/json" => format!(
130 "HTTP/1.1 200 OK\r\nContent-Type: {}\r\n\r\n{}",
131 route.content_type, route.response
132 ),
133 _ => format!(
134 "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n{}",
135 route.response
136 ),
137 };
138 if let Some(css_content) = &route.styles {
140 let styled_response = format!(
142 "{}\r\n\r\n<style type=\"text/css\">\r\n{}\r\n</style>",
143 response, css_content
144 );
145 return stream.write_all(styled_response.as_bytes()).unwrap();
146 } else {
147 return stream.write_all(response.as_bytes()).unwrap();
148 }
149 }
150 }
151
152 format!("HTTP/1.1 404 NOT FOUND\r\n\r\n{}", custom_404)
154 };
155 stream.write_all(response.as_bytes()).unwrap();
156 }
157 Err(e) => eprintln!("Error reading from connection: {}", e),
158 }
159 }
160
161 let listener = TcpListener::bind(format!("{}:{}", self.host, self.port))
162 .expect("Failed to bind adress!");
163 println!("Server listening on http://{}:{}", self.host, self.port);
164
165 thread::spawn({
166 move || {
167 for stream in listener.incoming() {
168 match stream {
169 Ok(stream) => {
170 let content = content.content.clone();
171 let css_content = style.content.clone();
172 let custom_routes = custom_routes.clone();
173 let custom404 = custom404.clone();
174 thread::spawn(move || {
175 handle_client(
176 stream,
177 content,
178 css_content,
179 custom_routes,
180 custom404,
181 );
182 });
183 }
184 Err(e) => {
185 println!("Error accepting connection: {}", e);
186 }
187 }
188 }
189 }
190 });
191 loop {
192 let mut prompt = String::new();
193 std::io::stdin().read_line(&mut prompt).unwrap_or_default();
194
195 match prompt.trim() {
196 ".help" => {
197 println!("{}", "Commands:".green().bold());
198 println!("{}", ".stop - Stop the server".yellow());
199 println!("{}", ".info - Get server info".yellow());
200 }
201 ".stop" => {
202 println!("Shutting down");
203 std::process::exit(0);
204 }
205 ".routes" => {
206 for route in routes.iter() {
207 println!(
208 "{} - {}",
209 route.path.bold().green(),
210 route.content_type.bold().yellow()
211 );
212 }
213 }
214 ".info" => {
215 println!("Server listening on http://{}:{}", self.host, self.port);
216 }
217 _ => println!(
218 "{}{}",
219 "Unknown Command: ".red().bold(),
220 prompt.trim().red()
221 ),
222 }
223 }
224 }
225}
226fn include_css(style_path: &str) -> String {
227 match fs::read_to_string(style_path) {
228 Ok(css) => css,
229 Err(e) => {
230 eprintln!("Error reading CSS file: {}", e);
231 "Error reading CSS file".to_string()
232 }
233 }
234}
235
236impl Debug for Server {
237 fn debug(&self) {
238 println!("Server {{ host: {}, port: {} }}", self.host, self.port)
239 }
240}
241impl ServerResponse {
242 pub fn new(content: impl Into<String>) -> Self {
243 Self {
244 content: content.into(),
245 }
246 }
247}
248impl StyleResponse {
249 pub fn new(content: impl Into<String>) -> Self {
250 Self {
251 content: content.into(),
252 }
253 }
254}
255pub fn include_html(path: impl Into<String> + std::convert::AsRef<std::path::Path>) -> String {
256 let binding = path.into();
257 let pth = Path::new(&binding);
258 let cat = File::open(pth);
259 match cat {
260 Ok(mut file) => {
261 let mut file_contents = String::new();
262 if let Err(error) = file.read_to_string(&mut file_contents) {
263 eprintln!("Error reading file: {}", error);
264 }
265
266 return file_contents;
267 }
268 Err(e) => {
269 eprintln!("Failed to read file {:?}", e);
270 return format!(
271 "
272 <div style='text-align: center; font-family: sans-serif;'>
273 <h1 style='margin: auto; background: red; border-radius: 5px; padding: 5px; color: white;'>Error reading your file!</h1>
274 <h2>Cannot find {}<br></h2>
275 <p>{}</p>
276 </div>",
277 &binding, e,
278 );
279 }
280 }
281}