1use crate::app::Config;
2use crate::tracing;
3use crate::session_manager::RustBasicSessionStore;
4use crate::router::{Router, Response};
5use crate::requests::Request;
6use std::net::SocketAddr;
7use crate::sql::AnyPool;
8use std::sync::Arc;
9use std::process::Command;
10use std::convert::Infallible;
11use tokio::net::TcpListener;
12use hyper::service::service_fn;
13use hyper_util::rt::TokioIo;
14use hyper::server::conn::http1;
15use crate::rand::distr::SampleString;
16
17#[derive(Clone)]
18pub struct AppState {
19 pub db: AnyPool,
20 pub config: Arc<Config>,
21}
22
23static EMBEDDED_PUBLIC_GET: std::sync::OnceLock<fn(&str) -> Option<crate::rust_embed::EmbeddedFile>> = std::sync::OnceLock::new();
24
25pub fn set_embedded_public(f: fn(&str) -> Option<crate::rust_embed::EmbeddedFile>) {
26 EMBEDDED_PUBLIC_GET.set(f).ok();
27}
28
29fn guess_mime(path: &str) -> &'static str {
30 if path.ends_with(".js") {
31 "application/javascript"
32 } else if path.ends_with(".css") {
33 "text/css"
34 } else if path.ends_with(".html") {
35 "text/html"
36 } else if path.ends_with(".png") {
37 "image/png"
38 } else if path.ends_with(".jpg") || path.ends_with(".jpeg") {
39 "image/jpeg"
40 } else if path.ends_with(".svg") {
41 "image/svg+xml"
42 } else if path.ends_with(".ico") {
43 "image/x-icon"
44 } else if path.ends_with(".json") {
45 "application/json"
46 } else if path.ends_with(".woff") {
47 "font/woff"
48 } else if path.ends_with(".woff2") {
49 "font/woff2"
50 } else {
51 "application/octet-stream"
52 }
53}
54
55pub async fn start_server(
56 cfg: Config,
57 session_store: RustBasicSessionStore,
58 db: AnyPool,
59 app_router: Router<AppState>,
60) {
61 let mut routes_map = std::collections::HashMap::new();
63 for r in &app_router.routes {
64 if let Some(ref name) = r.name {
65 routes_map.insert(name.clone(), r.path.clone());
66 }
67 }
68 let _ = crate::router::NAMED_ROUTES.set(routes_map);
69
70 kill_port_if_in_use(cfg.app_port);
72
73 unsafe {
75 std::env::set_var("TZ", &cfg.app_timezone);
76 }
77
78 let state = AppState {
80 db,
81 config: Arc::new(cfg.clone()),
82 };
83
84 let addr_str = format!("{}:{}", cfg.app_host, cfg.app_port);
86 let addr: SocketAddr = addr_str.parse().expect("Alamat server tidak valid");
87
88 tracing::info!("{} berjalan di: http://{}", cfg.app_name, addr);
89
90 let listener = TcpListener::bind(addr).await.unwrap();
92
93 loop {
94 let (stream, peer_addr) = match listener.accept().await {
95 Ok(ok) => ok,
96 Err(_) => continue,
97 };
98
99 let _ = stream.set_nodelay(true);
101
102 let io = TokioIo::new(stream);
103 let state = state.clone();
104 let router = app_router.clone();
105 let peer_ip = peer_addr.ip().to_string();
106 let session_store = session_store.clone();
107
108 tokio::task::spawn(async move {
109 let service = service_fn(move |req: hyper::Request<hyper::body::Incoming>| {
110 let state = state.clone();
111 let router = router.clone();
112 let peer_ip = peer_ip.clone();
113 let session_store = session_store.clone();
114 async move {
115 let res = handle_http_request(req, peer_ip, state, router, session_store).await;
116 Ok::<_, Infallible>(res)
117 }
118 });
119
120 if let Err(err) = http1::Builder::new()
121 .serve_connection(io, service)
122 .await
123 {
124 tracing::debug!("Error serving connection: {:?}", err);
125 }
126 });
127 }
128}
129
130pub(crate) fn match_path(route_path: &str, req_path: &str) -> bool {
131 let r_parts: Vec<&str> = route_path.split('/').filter(|s| !s.is_empty()).collect();
132 let q_parts: Vec<&str> = req_path.split('/').filter(|s| !s.is_empty()).collect();
133
134 if r_parts.len() != q_parts.len() {
135 return false;
136 }
137
138 for (r, q) in r_parts.iter().zip(q_parts.iter()) {
139 if r.starts_with(':') || (r.starts_with('{') && r.ends_with('}')) {
140 continue;
141 }
142 if r != q {
143 return false;
144 }
145 }
146 true
147}
148
149pub(crate) fn extract_params(route_path: &str, req_path: &str) -> std::collections::HashMap<String, String> {
152 let mut params = std::collections::HashMap::new();
153 let r_parts: Vec<&str> = route_path.split('/').filter(|s| !s.is_empty()).collect();
154 let q_parts: Vec<&str> = req_path.split('/').filter(|s| !s.is_empty()).collect();
155
156 for (r, q) in r_parts.iter().zip(q_parts.iter()) {
157 if r.starts_with('{') && r.ends_with('}') {
158 let key = &r[1..r.len() - 1];
160 params.insert(key.to_string(), q.to_string());
161 } else if r.starts_with(':') {
162 let key = &r[1..];
164 params.insert(key.to_string(), q.to_string());
165 }
166 }
167 params
168}
169
170async fn serve_static_or_404(path: &str, state: &AppState) -> Response {
171 let clean_path = path.trim_start_matches('/');
172 let file_path = if clean_path.is_empty() { "index.html" } else { clean_path };
173
174 if state.config.app_debug {
175 let disk_path = std::path::Path::new("public").join(file_path);
176 if disk_path.exists() && disk_path.is_file() {
177 if let Ok(content) = std::fs::read(&disk_path) {
178 let mime = guess_mime(file_path);
179 return http::Response::builder()
180 .header(http::header::CONTENT_TYPE, mime)
181 .body(content)
182 .unwrap();
183 }
184 }
185 } else {
186 if let Some(file) = EMBEDDED_PUBLIC_GET.get().and_then(|f| f(file_path)) {
187 let mime = guess_mime(file_path);
188 return http::Response::builder()
189 .header(http::header::CONTENT_TYPE, mime)
190 .body(file.data.to_vec())
191 .unwrap();
192 }
193 }
194
195 crate::errors::ErrorController::not_found().await
196}
197
198async fn handle_http_request(
199 hyper_req: hyper::Request<hyper::body::Incoming>,
200 peer_ip: String,
201 state: AppState,
202 router: Router<AppState>,
203 session_store: RustBasicSessionStore,
204) -> hyper::Response<http_body_util::Full<hyper::body::Bytes>> {
205 use http_body_util::BodyExt;
206
207 let (parts, body) = hyper_req.into_parts();
208 let method = parts.method.clone();
209 let uri = parts.uri.clone();
210 let path = uri.path().to_string();
211
212 let mut headers = std::collections::HashMap::new();
213 for (name, val) in parts.headers.iter() {
214 if let Ok(val_str) = val.to_str() {
215 headers.insert(name.as_str().to_lowercase(), val_str.to_string());
216 }
217 }
218
219 let mut inputs = serde_json::json!({});
220 if let Some(query) = uri.query() {
221 if let Ok(params) = crate::serde_urlencoded::from_str::<std::collections::HashMap<String, String>>(query) {
222 for (k, v) in params {
223 inputs[k] = serde_json::json!(v);
224 }
225 }
226 }
227
228 let body_bytes = body.collect().await.map(|c| c.to_bytes()).unwrap_or_default();
229 let content_type = headers.get("content-type").map(|s| s.as_str()).unwrap_or("");
230 if content_type.starts_with("application/json") {
231 if let Ok(json_val) = serde_json::from_slice::<serde_json::Value>(&body_bytes) {
232 if let serde_json::Value::Object(obj) = json_val {
233 for (k, v) in obj {
234 inputs[k] = v;
235 }
236 }
237 }
238 } else if content_type.starts_with("application/x-www-form-urlencoded") {
239 if let Ok(params) = crate::serde_urlencoded::from_bytes::<std::collections::HashMap<String, String>>(&body_bytes) {
240 for (k, v) in params {
241 inputs[k] = serde_json::json!(v);
242 }
243 }
244 }
245
246 let mut session_id = None;
247 if let Some(cookie_header) = headers.get("cookie") {
248 for cookie in cookie_header.split(';') {
249 let parts: Vec<&str> = cookie.split('=').map(|s| s.trim()).collect();
250 if parts.len() == 2 && parts[0] == "rustbasic_session" {
251 session_id = Some(parts[1].to_string());
252 break;
253 }
254 }
255 }
256
257 let id = session_id.unwrap_or_else(|| {
258 crate::rand::distr::Alphanumeric.sample_string(&mut crate::rand::rng(), 40)
259 });
260
261 let session_data = if let Some(payload_str) = session_store.load(&id).await {
262 serde_json::from_str::<serde_json::Map<String, serde_json::Value>>(&payload_str).unwrap_or_default()
263 } else {
264 serde_json::Map::new()
265 };
266
267 let session = crate::session::Session::new(id.clone());
268 *session.data.lock().unwrap() = session_data;
269
270 if session.get::<String>("_token").is_none() {
271 let new_token = crate::rand::distr::Alphanumeric.sample_string(&mut crate::rand::rng(), 40);
272 session.set("_token", new_token);
273 }
274
275 let req = Request {
276 inputs,
277 method: method.clone(),
278 path: path.clone(),
279 headers,
280 session: session.clone(),
281 state: state.clone(),
282 ip_address: peer_ip,
283 params: std::collections::HashMap::new(), };
285
286 struct RouteDispatcher {
287 router: Router<AppState>,
288 state: AppState,
289 }
290
291 #[crate::async_trait]
292 impl crate::router::ErasedHandler for RouteDispatcher {
293 async fn call(&self, req: Request) -> Response {
294 let method = req.method.clone();
295 let path = req.path.clone();
296
297 let mut matched_handler = None;
298 let mut matched_params = std::collections::HashMap::new();
299 for route in &self.router.routes {
300 if match_path(&route.path, &path) {
301 for (m, h) in &route.handlers {
302 if m == &method {
303 matched_handler = Some(h.clone());
304 matched_params = extract_params(&route.path, &path);
305 break;
306 }
307 }
308 }
309 if matched_handler.is_some() {
310 break;
311 }
312 }
313
314 if let Some(handler) = matched_handler {
315 let mut req = req;
317 req.params = matched_params;
318 let mut chain = std::sync::Arc::new(crate::middleware::MiddlewareChain::End(handler));
319 for mw in self.router.middlewares.iter().rev() {
320 chain = std::sync::Arc::new(crate::middleware::MiddlewareChain::Next(mw.clone(), chain));
321 }
322 chain.next(req).await
323 } else {
324 serve_static_or_404(&path, &self.state).await
325 }
326 }
327 }
328
329 let dispatcher = std::sync::Arc::new(RouteDispatcher {
330 router,
331 state: state.clone(),
332 });
333
334 let mut chain = std::sync::Arc::new(crate::middleware::MiddlewareChain::End(dispatcher));
335 chain = std::sync::Arc::new(crate::middleware::MiddlewareChain::Next(
336 crate::middleware::from_fn(crate::middleware::security_headers::security_headers_middleware),
337 chain,
338 ));
339 chain = std::sync::Arc::new(crate::middleware::MiddlewareChain::Next(
340 crate::middleware::from_fn(crate::middleware::logging::logging_middleware),
341 chain,
342 ));
343
344 let ip = req.ip_address.clone();
345 let res = chain.next(req).await;
346
347 let final_session_data = session.data.lock().unwrap().clone();
348 if let Ok(session_json) = serde_json::to_string(&final_session_data) {
349 session_store.store(&id, &session_json, &ip).await;
350 }
351
352 let (mut res_parts, res_body) = res.into_parts();
353 let cookie_val = format!("rustbasic_session={}; Path=/; HttpOnly; SameSite=Lax", id);
354 res_parts.headers.insert(
355 http::header::SET_COOKIE,
356 http::HeaderValue::from_str(&cookie_val).unwrap(),
357 );
358
359 hyper::Response::from_parts(res_parts, http_body_util::Full::new(hyper::body::Bytes::from(res_body)))
360}
361
362fn kill_port_if_in_use(port: u16) {
363 #[cfg(target_os = "macos")]
364 {
365 let output = Command::new("lsof")
366 .arg("-t")
367 .arg(format!("-i:{}", port))
368 .output();
369
370 if let Ok(out) = output {
371 let pid_str = String::from_utf8_lossy(&out.stdout).trim().to_string();
372 if !pid_str.is_empty() {
373 tracing::warn!("Port {} sedang digunakan oleh PID {}. Membunuh proses...", port, pid_str);
374
375 for pid in pid_str.split('\n') {
376 if !pid.is_empty() {
377 let _ = Command::new("kill")
378 .arg("-9")
379 .arg(pid)
380 .output();
381 }
382 }
383
384 std::thread::sleep(std::time::Duration::from_millis(500));
385 }
386 }
387 }
388
389 #[cfg(target_os = "linux")]
390 {
391 let _ = Command::new("fuser")
392 .arg("-k")
393 .arg(format!("{}/tcp", port))
394 .output();
395 }
396
397 #[cfg(target_os = "windows")]
398 {
399 let output = Command::new("cmd")
400 .args(&["/C", &format!("netstat -ano | findstr :{}", port)])
401 .output();
402
403 if let Ok(out) = output {
404 let stdout = String::from_utf8_lossy(&out.stdout);
405 let mut found = false;
406 for line in stdout.lines() {
407 let parts: Vec<&str> = line.split_whitespace().collect();
408 if let Some(pid) = parts.last() {
409 if pid.parse::<u32>().is_ok() {
410 tracing::warn!("Port {} sedang digunakan oleh PID {}. Membunuh proses...", port, pid);
411 let _ = Command::new("taskkill")
412 .args(&["/F", "/PID", pid])
413 .output();
414 found = true;
415 }
416 }
417 }
418 if found {
419 std::thread::sleep(std::time::Duration::from_millis(500));
420 }
421 }
422 }
423}