1use std::collections::{HashMap};
2use std::net::TcpStream;
3use std::io::{Read, Write};
4use std::sync::{Arc, Mutex};
5use std::thread;
6use tera::{Context, Tera};
7use std::fs;
8use chrono::{Utc, Datelike};
9
10pub struct Server {
11 routes: HashMap<String, Arc<Mutex<dyn Fn(&str, HashMap<String, String>, &str, HashMap<String, String>) -> Result<String, String> + Send + Sync>>>,
12 static_routes: HashMap<String, String>,
13}
14
15impl Clone for Server {
16 fn clone(&self) -> Self {
17 Server {
18 routes: self.routes.clone(),
19 static_routes: self.static_routes.clone(),
20 }
21 }
22}
23
24impl Server {
25 pub fn new() -> Self {
26 Server {
27 routes: HashMap::new(),
28 static_routes: HashMap::new(),
29 }
30 }
31
32 pub fn route<F>(&mut self, path: &str, func: F)
33 where
34 F: Fn(&str, HashMap<String, String>, &str, HashMap<String, String>) -> Result<String, String> + 'static + Send + Sync,
35 {
36 self.routes.insert(path.to_string(), Arc::new(Mutex::new(func)));
37 }
38
39 pub fn static_route(&mut self, path: &str, content: &str) {
40 self.static_routes.insert(path.to_string(), content.to_string());
41 }
42
43 pub fn add_route_to_sitemap(&self, path: &str, lastmod: bool, changefreq: &str, priority: f32, base_url: &str) {
44 let current_date = Utc::now().format("%Y-%m-%d").to_string();
45 let full_url = format!("{}{}", base_url, path);
46
47 let mut sitemap_content = if let Ok(content) = fs::read_to_string("static/sitemap.xml") {
48 content
49 } else {
50 String::from("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n</urlset>")
51 };
52
53 let url_to_check = format!("<loc>{}</loc>", full_url);
54 if !sitemap_content.contains(&url_to_check) {
55 let mut route_entry = String::new();
56 route_entry.push_str(" <url>\n");
57 route_entry.push_str(&format!(" <loc>{}</loc>\n", full_url));
58 if lastmod {
59 route_entry.push_str(&format!(" <lastmod>{}</lastmod>\n", current_date));
60 }
61 route_entry.push_str(&format!(" <changefreq>{}</changefreq>\n", changefreq));
62 route_entry.push_str(&format!(" <priority>{}</priority>\n", priority));
63 route_entry.push_str(" </url>\n");
64
65 sitemap_content.insert_str(sitemap_content.rfind("</urlset>").unwrap(), &route_entry);
66 if let Err(err) = fs::write("static/sitemap.xml", sitemap_content) {
67 eprintln!("Failed to write sitemap: {}", err);
68 }
69 } else {
70 println!("The route {} already exists in the sitemap.", url_to_check);
71 }
72 }
73
74 pub fn generate_sitemap(&self, sitemap: bool, lastmod: bool, changefreq: &str, priority: f32, base_url: &str) {
75 if sitemap {
76 let mut sitemap_content = if let Ok(content) = fs::read_to_string("static/sitemap.xml") {
77 content
78 } else {
79 String::from("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n</urlset>")
80 };
81
82 let current_date = Utc::now().format("%Y-%m-%d").to_string();
83
84 for (path, _) in &self.static_routes {
85 let full_url = format!("{}{}", base_url, path);
86
87 let url_to_check = format!("<loc>{}</loc>", full_url);
88 if !sitemap_content.contains(&url_to_check) {
89 let mut route_entry = String::new();
90 route_entry.push_str(" <url>\n");
91 route_entry.push_str(&format!(" <loc>{}</loc>\n", full_url));
92 if lastmod {
93 route_entry.push_str(&format!(" <lastmod>{}</lastmod>\n", current_date));
94 }
95 route_entry.push_str(&format!(" <changefreq>{}</changefreq>\n", changefreq));
96 route_entry.push_str(&format!(" <priority>{}</priority>\n", priority));
97 route_entry.push_str(" </url>\n");
98
99 sitemap_content.insert_str(sitemap_content.rfind("</urlset>").unwrap(), &route_entry);
100 } else {
101 println!("The route {} already exists in the sitemap.", url_to_check);
102 }
103 }
104
105 if let Err(err) = fs::write("static/sitemap.xml", sitemap_content) {
106 eprintln!("Failed to write sitemap: {}", err);
107 }
108 }
109 }
110
111 pub fn load_static_files(&mut self, static_folder: &str) {
112 if let Ok(entries) = fs::read_dir(static_folder) {
113 for entry in entries {
114 if let Ok(entry) = entry {
115 let path = entry.path();
116 if path.is_file() {
117 if let Some(file_name) = path.file_name() {
118 if let Some(file_str) = file_name.to_str() {
119 let route = format!("/{}", file_str);
120 self.static_route(&route, &path.to_string_lossy());
121 }
122 }
123 }
124 }
125 }
126 }
127 }
128
129 fn serve_static(&self, path: &str) -> Option<String> {
130 if let Some(file_path) = self.static_routes.get(path) {
131 if let Ok(content) = fs::read_to_string(&file_path) {
132 let response = format!(
133 "HTTP/1.1 200 OK\r\nContent-Length: {}\r\n\r\n{}",
134 content.len(),
135 content
136 );
137 return Some(response);
138 }
139 }
140 None
141 }
142
143 fn handle_static(&self, path: &str) -> Option<String> {
144 if let Some(content) = self.static_routes.get(path) {
145 Some(content.clone())
146 } else {
147 None
148 }
149 }
150
151 fn path_matches_route(&self, path: &str, route: &str) -> Option<HashMap<String, String>> {
152 let path_parts: Vec<&str> = path.split('/').collect();
153 let route_parts: Vec<&str> = route.split('/').collect();
154
155 if path_parts.len() != route_parts.len() {
156 return None;
157 }
158
159 let mut params = HashMap::new();
160
161 for (path_part, route_part) in path_parts.iter().zip(route_parts.iter()) {
162 if route_part.starts_with(':') {
163 let slug_name = &route_part[1..];
164 params.insert(slug_name.to_string(), path_part.to_string());
165 } else if path_part != route_part {
166 return None;
167 }
168 }
169
170 Some(params)
171 }
172
173 fn handle_connection(&self, mut stream: TcpStream) {
174 let mut buffer = [0; 1024];
175 stream.read(&mut buffer).unwrap();
176
177 let request = String::from_utf8_lossy(&buffer[..]);
178 let request_parts: Vec<&str> = request.split("\r\n\r\n").collect();
179
180 let path_params: Vec<&str> = request_parts[0].split_whitespace().collect();
181 let path = path_params[1].split('?').next().unwrap_or("");
182
183 if let Some(content) = self.serve_static(path) {
184 stream.write_all(content.as_bytes()).unwrap();
185 stream.flush().unwrap();
186 return;
187 }
188
189 let method = path_params[0];
190 let mut params = HashMap::new();
191 let mut data = HashMap::new();
192
193 if let Some(query_params) = path_params[1].splitn(2, '?').nth(1) {
194 let payload_parts: Vec<&str> = query_params.split("&").collect();
195 for part in payload_parts {
196 let kv: Vec<&str> = part.split("=").collect();
197 if kv.len() == 2 {
198 params.insert(kv[0].to_string(), kv[1].to_string());
199 }
200 }
201 }
202
203 let body_params: Vec<&str> = request_parts[1].split('&').collect();
204 for part in body_params {
205 let kv: Vec<&str> = part.split("=").collect();
206 if kv.len() == 2 {
207 data.insert(kv[0].to_string(), kv[1].to_string());
208 }
209 }
210
211 let response = match self.static_routes.get(path) {
212 Some(content) => Ok(format!("{}", content)),
213 None => match self.routes.iter().find_map(|(route, handler)| {
214 if let Some(params) = self.path_matches_route(path, route) {
215 let handler = handler.lock().unwrap();
216 let cloned_params = params.clone();
217 let cloned_data = data.clone();
218 return Some(handler(path, cloned_params, method, cloned_data).map(|res| {
219 res
220 }));
221 }
222 None
223 }) {
224 Some(result) => result,
225 None => Err("HTTP/1.1 404 NOT FOUND\n\nPath Not Found".to_string()),
226 },
227 };
228
229 match response {
230 Ok(result) => {
231 stream.write_all(result.as_bytes()).unwrap();
232 stream.flush().unwrap();
233 }
234 Err(err) => {
235 stream.write_all(err.as_bytes()).unwrap();
236 stream.flush().unwrap();
237 }
238 }
239 }
240
241
242 pub fn run(mut self, ip: &str, port: &str) {
243 self.load_static_files("static");
244
245 let listener = std::net::TcpListener::bind(format!("{}:{}", ip, port)).unwrap();
246
247 for stream in listener.incoming() {
248 let stream = stream.unwrap();
249 let server = self.clone();
250
251 thread::spawn(move || {
252 server.handle_connection(stream);
253 });
254 }
255 }
256}
257
258pub struct TemplateEngine;
259
260impl TemplateEngine {
261 pub fn render(template: &str, context: &HashMap<&str, &str>) -> String {
262 let mut tera = Tera::default();
263 tera.add_raw_template("template", template).unwrap();
264
265 let mut ctx = Context::new();
266 for (key, val) in context {
267 ctx.insert(*key, val);
268 }
269
270 tera.render("template", &ctx).unwrap()
271 }
272
273 pub fn render_template(template_name: &str, context: &HashMap<&str, &str>) -> Result<String, String> {
274 let file_content = match std::fs::read_to_string(format!("templates/{}", template_name)) {
275 Ok(content) => content,
276 Err(_) => return Err("Template file not found".to_string()),
277 };
278
279 let mut tera = Tera::default();
280 tera.add_raw_template("template", &file_content).unwrap();
281
282 let mut ctx = Context::new();
283 for (key, val) in context {
284 ctx.insert(*key, val);
285 }
286
287 tera.render("template", &ctx).map_err(|e| e.to_string())
288 }
289}