Skip to main content

rustbasic_core/
server.rs

1use axum::{Router, response::IntoResponse};
2use tower_http::services::ServeDir;
3use tower_governor::{governor::GovernorConfigBuilder, GovernorLayer, key_extractor::SmartIpKeyExtractor};
4use axum_session::{SessionLayer, SessionStore};
5use crate::app::Config;
6use crate::session_manager::RustBasicSessionStore;
7use crate::errors::ErrorController;
8use tower_governor::GovernorError;
9use std::net::SocketAddr;
10use sea_orm::DatabaseConnection;
11use std::sync::Arc;
12use std::process::Command;
13use std::time::Duration;
14use tower_livereload::LiveReloadLayer;
15
16#[derive(Clone)]
17#[allow(dead_code)]
18pub struct AppState {
19    pub db: DatabaseConnection,
20    pub config: Arc<Config>,
21}
22
23pub async fn start_server(
24    cfg: Config, 
25    session_store: SessionStore<RustBasicSessionStore>,
26    static_files: ServeDir,
27    db: DatabaseConnection,
28    app_router: Router<AppState>
29) {
30    // 0. Kill port jika sedang digunakan (Force Restart)
31    kill_port_if_in_use(cfg.app_port);
32
33    // 0.5 Set Timezone Global
34    unsafe {
35        std::env::set_var("TZ", &cfg.app_timezone);
36    }
37
38    // 1. Inisialisasi State
39    let state = AppState {
40        db,
41        config: Arc::new(cfg.clone()),
42    };
43
44    // 1.5 Konfigurasi Rate Limiting
45    let governor_conf = Arc::new(
46        GovernorConfigBuilder::default()
47            .key_extractor(SmartIpKeyExtractor)
48            .period(Duration::from_millis(1000 / cfg.app_limit_request))
49            .burst_size(cfg.app_limit_request as u32)
50            .finish()
51            .unwrap(),
52    );
53
54    // 2. Bangun Router
55    let app = Router::new()
56        .merge(app_router)
57        .nest_service("/public", static_files)
58        .layer(GovernorLayer::new(governor_conf))
59        .layer(SessionLayer::new(session_store))
60        .fallback(ErrorController::not_found)
61        .with_state(state);
62
63    // 2.5 Live Reload (Hanya aktif jika APP_DEBUG=true)
64    let app = if cfg.app_debug {
65        tracing::info!("🔄 Fitur Live Reload (Auto-refresh) diaktifkan.");
66        app.layer(LiveReloadLayer::new())
67    } else {
68        app
69    };
70
71    // 3. Tentukan Alamat
72    let addr_str = format!("{}:{}", cfg.app_host, cfg.app_port);
73    let addr: SocketAddr = addr_str.parse().expect("Alamat server tidak valid");
74    
75    tracing::info!("{} berjalan di: http://{}", cfg.app_name, addr);
76    
77    // 4. Jalankan Server dengan ConnectInfo agar IP bisa dideteksi
78    let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
79    axum::serve(listener, app.into_make_service_with_connect_info::<SocketAddr>()).await.unwrap();
80}
81
82/// Membunuh proses yang menggunakan port tertentu agar tidak terjadi error "Address already in use"
83fn kill_port_if_in_use(port: u16) {
84    #[cfg(target_os = "macos")]
85    {
86        // Mencari PID yang menggunakan port tersebut
87        let output = Command::new("lsof")
88            .arg("-t")
89            .arg(format!("-i:{}", port))
90            .output();
91
92        if let Ok(out) = output {
93            let pid_str = String::from_utf8_lossy(&out.stdout).trim().to_string();
94            if !pid_str.is_empty() {
95                tracing::warn!("Port {} sedang digunakan oleh PID {}. Membunuh proses...", port, pid_str);
96                
97                // Membunuh proses tersebut
98                for pid in pid_str.split('\n') {
99                    let _ = Command::new("kill")
100                        .arg("-9")
101                        .arg(pid)
102                        .output();
103                }
104            }
105        }
106    }
107
108    #[cfg(target_os = "linux")]
109    {
110        let _ = Command::new("fuser")
111            .arg("-k")
112            .arg(format!("{}/tcp", port))
113            .output();
114    }
115}
116
117/// Menangani error dari Rate Limiter (Governor) dengan tampilan HTML Premium
118#[allow(dead_code)]
119fn handle_governor_error(err: GovernorError) -> axum::response::Response {
120    match err {
121        GovernorError::TooManyRequests { wait_time, .. } => {
122            ErrorController::show(
123                429, 
124                &format!("Terlalu banyak permintaan. Silakan tunggu {} detik lagi.", wait_time)
125            ).into_response()
126        },
127        _ => ErrorController::show(500, "Terjadi kesalahan pada sistem pembatas request.").into_response(),
128    }
129}