Skip to main content

rustbasic_core/
server.rs

1use axum::{Router, response::IntoResponse, ServiceExt, handler::HandlerWithoutStateExt};
2use tower_http::services::ServeDir;
3use tower_http::normalize_path::NormalizePathLayer;
4use tower::Layer;
5use tower_governor::{governor::GovernorConfigBuilder, GovernorLayer, key_extractor::SmartIpKeyExtractor};
6use axum_session::{SessionLayer, SessionStore};
7use crate::app::Config;
8use crate::session_manager::RustBasicSessionStore;
9use crate::errors::ErrorController;
10use tower_governor::GovernorError;
11use std::net::SocketAddr;
12use sea_orm::DatabaseConnection;
13use std::sync::Arc;
14use std::process::Command;
15use std::time::Duration;
16use tower_livereload::LiveReloadLayer;
17
18#[derive(Clone)]
19#[allow(dead_code)]
20pub struct AppState {
21    pub db: DatabaseConnection,
22    pub config: Arc<Config>,
23}
24
25pub async fn start_server(
26    cfg: Config, 
27    session_store: SessionStore<RustBasicSessionStore>,
28    static_files: ServeDir,
29    db: DatabaseConnection,
30    app_router: Router<AppState>
31) {
32    // 0. Kill port jika sedang digunakan (Force Restart)
33    kill_port_if_in_use(cfg.app_port);
34
35    // 0.5 Set Timezone Global
36    unsafe {
37        std::env::set_var("TZ", &cfg.app_timezone);
38    }
39
40    // 1. Inisialisasi State
41    let state = AppState {
42        db,
43        config: Arc::new(cfg.clone()),
44    };
45
46    // 1.5 Konfigurasi Rate Limiting
47    let governor_conf = Arc::new(
48        GovernorConfigBuilder::default()
49            .key_extractor(SmartIpKeyExtractor)
50            .period(Duration::from_millis(1000 / cfg.app_limit_request))
51            .burst_size(cfg.app_limit_request as u32)
52            .finish()
53            .unwrap(),
54    );
55
56    // 2. Bangun Router
57    let app = Router::new()
58        .merge(app_router)
59        .fallback_service(static_files.not_found_service(ErrorController::not_found.into_service()))
60        .layer(axum::middleware::from_fn(crate::middleware::security_headers::security_headers_middleware))
61        .layer(axum::middleware::from_fn(crate::middleware::logging::logging_middleware))
62        .layer(GovernorLayer::new(governor_conf))
63        .layer(SessionLayer::new(session_store))
64        .with_state(state);
65
66    // 2.5 Live Reload (Hanya aktif jika APP_DEBUG=true)
67    let app = if cfg.app_debug {
68        tracing::info!("🔄 Fitur Live Reload (Auto-refresh) diaktifkan.");
69        app.layer(LiveReloadLayer::new())
70    } else {
71        app
72    };
73    
74    // 2.6 Normalisasi Path (Menangani trailing slash /home/ -> /home)
75    let app = NormalizePathLayer::trim_trailing_slash().layer(app);
76
77    // 3. Tentukan Alamat
78    let addr_str = format!("{}:{}", cfg.app_host, cfg.app_port);
79    let addr: SocketAddr = addr_str.parse().expect("Alamat server tidak valid");
80    
81    tracing::info!("{} berjalan di: http://{}", cfg.app_name, addr);
82    
83    // 4. Jalankan Server dengan ConnectInfo agar IP bisa dideteksi
84    let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
85    axum::serve(listener, ServiceExt::<axum::extract::Request>::into_make_service_with_connect_info::<SocketAddr>(app)).await.unwrap();
86}
87
88/// Membunuh proses yang menggunakan port tertentu agar tidak terjadi error "Address already in use"
89fn kill_port_if_in_use(port: u16) {
90    #[cfg(target_os = "macos")]
91    {
92        // Mencari PID yang menggunakan port tersebut
93        let output = Command::new("lsof")
94            .arg("-t")
95            .arg(format!("-i:{}", port))
96            .output();
97
98        if let Ok(out) = output {
99            let pid_str = String::from_utf8_lossy(&out.stdout).trim().to_string();
100            if !pid_str.is_empty() {
101                tracing::warn!("Port {} sedang digunakan oleh PID {}. Membunuh proses...", port, pid_str);
102                
103                // Membunuh proses tersebut
104                for pid in pid_str.split('\n') {
105                    if !pid.is_empty() {
106                        let _ = Command::new("kill")
107                            .arg("-9")
108                            .arg(pid)
109                            .output();
110                    }
111                }
112
113                // Beri waktu sejenak agar OS melepas port (Penting agar tidak panic AddrInUse)
114                std::thread::sleep(std::time::Duration::from_millis(500));
115            }
116        }
117    }
118
119    #[cfg(target_os = "linux")]
120    {
121        let _ = Command::new("fuser")
122            .arg("-k")
123            .arg(format!("{}/tcp", port))
124            .output();
125    }
126
127    #[cfg(target_os = "windows")]
128    {
129        let output = Command::new("cmd")
130            .args(&["/C", &format!("netstat -ano | findstr :{}", port)])
131            .output();
132
133        if let Ok(out) = output {
134            let stdout = String::from_utf8_lossy(&out.stdout);
135            let mut found = false;
136            for line in stdout.lines() {
137                let parts: Vec<&str> = line.split_whitespace().collect();
138                if let Some(pid) = parts.last() {
139                    if pid.parse::<u32>().is_ok() {
140                        tracing::warn!("Port {} sedang digunakan oleh PID {}. Membunuh proses...", port, pid);
141                        let _ = Command::new("taskkill")
142                            .args(&["/F", "/PID", pid])
143                            .output();
144                        found = true;
145                    }
146                }
147            }
148            if found {
149                // Beri waktu sejenak agar OS melepas port
150                std::thread::sleep(std::time::Duration::from_millis(500));
151            }
152        }
153    }
154}
155
156/// Menangani error dari Rate Limiter (Governor) dengan tampilan HTML Premium
157#[allow(dead_code)]
158fn handle_governor_error(err: GovernorError) -> axum::response::Response {
159    match err {
160        GovernorError::TooManyRequests { wait_time, .. } => {
161            ErrorController::show(
162                429, 
163                &format!("Terlalu banyak permintaan. Silakan tunggu {} detik lagi.", wait_time)
164            ).into_response()
165        },
166        _ => ErrorController::show(500, "Terjadi kesalahan pada sistem pembatas request.").into_response(),
167    }
168}