rush_sync_server/server/handlers/web/
mod.rs

1// src/server/handlers/web/mod.rs
2pub mod api;
3pub mod assets;
4pub mod logs;
5pub mod server;
6pub mod templates;
7
8pub use api::*;
9pub use assets::*;
10pub use logs::*;
11pub use server::*;
12pub use templates::*;
13
14use crate::core::config::Config;
15use crate::server::logging::ServerLogger;
16use crate::server::middleware::LoggingMiddleware;
17use crate::server::tls::TlsManager;
18use crate::server::types::{ServerContext, ServerData, ServerInfo};
19use crate::server::watchdog::{get_watchdog_manager, ws_hot_reload};
20use actix_cors::Cors;
21use actix_web::{middleware, web, App, HttpServer};
22use std::path::PathBuf;
23use std::sync::Arc;
24use std::sync::OnceLock;
25use std::time::Duration;
26
27static GLOBAL_CONFIG: OnceLock<Config> = OnceLock::new();
28
29// Funktion um Config zu setzen
30pub fn set_global_config(config: Config) {
31    let _ = GLOBAL_CONFIG.set(config);
32}
33
34// KORRIGIERTE Port-Funktionen mit TOML-Config
35pub fn get_proxy_http_port() -> u16 {
36    // HTTP Proxy läuft auf dem konfigurierten Proxy-Port (3000)
37    GLOBAL_CONFIG.get().map(|c| c.proxy.port).unwrap_or(3000)
38}
39
40pub fn get_proxy_https_port() -> u16 {
41    // HTTPS Proxy läuft auf HTTP-Port + https_port_offset
42    GLOBAL_CONFIG
43        .get()
44        .map(|c| c.proxy.port + c.proxy.https_port_offset)
45        .unwrap_or(3443)
46}
47
48// Alternative: Falls Config erweitert wird
49pub fn get_proxy_https_port_from_config() -> u16 {
50    GLOBAL_CONFIG
51        .get()
52        .map(|c| {
53            // Wenn Config später https_port_offset hat:
54            // c.proxy.port + c.proxy.https_port_offset
55
56            // Aktuell: Standardoffset
57            c.proxy.port + 443
58        })
59        .unwrap_or(3443)
60}
61
62pub fn create_server_directory_and_files(
63    server_name: &str,
64    port: u16,
65) -> crate::core::error::Result<PathBuf> {
66    let exe_path = std::env::current_exe().map_err(crate::core::error::AppError::Io)?;
67    let base_dir = exe_path.parent().ok_or_else(|| {
68        crate::core::error::AppError::Validation(
69            "Cannot determine executable directory".to_string(),
70        )
71    })?;
72
73    let server_dir = base_dir
74        .join("www")
75        .join(format!("{}-[{}]", server_name, port));
76    std::fs::create_dir_all(&server_dir).map_err(crate::core::error::AppError::Io)?;
77
78    // Template-Pfade korrigiert für neue Struktur
79    let readme_template = include_str!("../templates/README.md");
80    let readme_content = readme_template
81        .replace("{{SERVER_NAME}}", server_name)
82        .replace("{{PORT}}", &port.to_string());
83    std::fs::write(server_dir.join("README.md"), readme_content)
84        .map_err(crate::core::error::AppError::Io)?;
85
86    let robots_template = include_str!("../templates/robots.txt");
87    let robots_content = robots_template.replace("{{PORT}}", &port.to_string());
88    std::fs::write(server_dir.join("robots.txt"), robots_content)
89        .map_err(crate::core::error::AppError::Io)?;
90
91    log::info!("Created development directory: {:?}", server_dir);
92    log::info!("Files created: README.md, robots.txt");
93    Ok(server_dir)
94}
95
96pub fn create_web_server(
97    ctx: &ServerContext,
98    server_info: ServerInfo,
99    config: &Config,
100) -> std::result::Result<actix_web::dev::ServerHandle, String> {
101    let server_id = server_info.id.clone();
102    let server_name = server_info.name.clone();
103    let server_port = server_info.port;
104    let servers_clone = Arc::clone(&ctx.servers);
105
106    let server_logger =
107        match ServerLogger::new_with_config(&server_name, server_info.port, &config.logging) {
108            Ok(logger) => Arc::new(logger),
109            Err(e) => return Err(format!("Logger creation failed: {}", e)),
110        };
111
112    if let Err(e) = crate::server::watchdog::start_server_watching(&server_name, server_port) {
113        log::warn!("Failed to start file watching for {}: {}", server_name, e);
114    } else {
115        log::info!(
116            "File watching started for server {} on port {}",
117            server_name,
118            server_port
119        );
120    }
121
122    let logger_for_start = server_logger.clone();
123    tokio::spawn(async move {
124        if let Err(e) = logger_for_start.log_server_start().await {
125            log::error!("Failed to log server start: {}", e);
126        }
127    });
128
129    // KORRIGIERTE ServerDataWithConfig mit richtigen Ports
130    let server_data = web::Data::new(ServerDataWithConfig {
131        server: ServerData {
132            id: server_id.clone(),
133            port: server_info.port,
134            name: server_name.clone(),
135        },
136        proxy_http_port: get_proxy_http_port(),
137        proxy_https_port: get_proxy_https_port(),
138    });
139
140    let server_logger_for_app = server_logger.clone();
141    let watchdog_manager = get_watchdog_manager().clone();
142
143    let tls_config = if config.server.enable_https && config.server.auto_cert {
144        match TlsManager::new(&config.server.cert_dir, config.server.cert_validity_days) {
145            Ok(tls_manager) => match tls_manager.get_rustls_config(&server_name, server_port) {
146                Ok(rustls_config) => {
147                    log::info!("TLS certificate loaded for {}:{}", server_name, server_port);
148                    Some(rustls_config)
149                }
150                Err(e) => {
151                    log::error!("TLS setup failed: {}", e);
152                    None
153                }
154            },
155            Err(e) => {
156                log::error!("TLS manager creation failed: {}", e);
157                None
158            }
159        }
160    } else {
161        None
162    };
163
164    let mut http_server = HttpServer::new(move || {
165        App::new()
166            .app_data(server_data.clone())
167            .app_data(web::Data::new(watchdog_manager.clone()))
168            .wrap(LoggingMiddleware::new(server_logger_for_app.clone()))
169            .wrap(middleware::Compress::default())
170            .wrap(LoggingMiddleware::new(server_logger_for_app.clone()))
171            .wrap(Cors::permissive())
172            // Assets
173            .route("/.rss/_reset.css", web::get().to(serve_global_reset_css))
174            .route("/.rss/style.css", web::get().to(serve_system_css))
175            .route("/.rss/favicon.svg", web::get().to(serve_system_favicon))
176            .route("/.rss/", web::get().to(serve_system_dashboard))
177            // Font Assets
178            .route("/.rss/fonts/{font}", web::get().to(serve_quicksand_font))
179            // JavaScript Assets
180            .route("/rss.js", web::get().to(serve_rss_js))
181            .route("/.rss/js/rush-app.js", web::get().to(serve_rush_app_js))
182            .route("/.rss/js/rush-api.js", web::get().to(serve_rush_api_js))
183            .route("/.rss/js/rush-ui.js", web::get().to(serve_rush_ui_js))
184            // API Routes - SPEZIFISCH VOR GENERISCH
185            .route("/api/status", web::get().to(status_handler))
186            .route("/api/health", web::get().to(health_handler))
187            .route("/api/info", web::get().to(info_handler))
188            .route("/api/metrics", web::get().to(metrics_handler))
189            .route("/api/stats", web::get().to(stats_handler))
190            .route("/api/ping", web::post().to(ping_handler))
191            .route("/api/message", web::post().to(message_handler))
192            .route("/api/messages", web::get().to(messages_handler))
193            .route("/api/close-browser", web::get().to(close_browser_handler))
194            .route("/api/logs", web::get().to(logs_handler))
195            .route("/api/logs/raw", web::get().to(logs_raw_handler))
196            // WebSocket Routes
197            .route("/ws/hot-reload", web::get().to(ws_hot_reload))
198            // ===== FALLBACK ZULETZT =====
199            .default_service(web::route().to(serve_fallback_or_inject))
200    })
201    .workers(config.server.workers)
202    .shutdown_timeout(config.server.shutdown_timeout)
203    .disable_signals();
204
205    http_server = http_server
206        .bind(("127.0.0.1", server_info.port))
207        .map_err(|e| format!("HTTP bind failed: {}", e))?;
208
209    if tls_config.is_some() {
210        let https_port = server_port + 443; // FIXME: config.server.https_port_offset existiert nicht
211        log::info!("TLS certificate ready for HTTPS on port {}", https_port);
212        log::info!(
213            "Certificate: .rss/certs/{}-{}.cert",
214            server_name,
215            server_port
216        );
217    }
218
219    let server_result = http_server.run();
220    let server_handle = server_result.handle();
221
222    let server_id_for_thread = server_id.clone();
223    let logger_for_cleanup = server_logger.clone();
224    let startup_delay = config.server.startup_delay_ms;
225    let server_name_for_cleanup = server_name.clone();
226    let server_port_for_cleanup = server_port;
227
228    if config.proxy.enabled {
229        let proxy_manager = crate::server::shared::get_proxy_manager();
230        let proxy_server_name = server_name.clone();
231        let proxy_server_id = server_id.clone();
232        let proxy_server_port = server_port;
233        let startup_delay_clone = startup_delay;
234
235        tokio::spawn(async move {
236            tokio::time::sleep(tokio::time::Duration::from_millis(
237                startup_delay_clone + 100,
238            ))
239            .await;
240
241            if let Err(e) = proxy_manager
242                .add_route(&proxy_server_name, &proxy_server_id, proxy_server_port)
243                .await
244            {
245                log::error!(
246                    "Failed to register server {} with proxy: {}",
247                    proxy_server_name,
248                    e
249                );
250            } else {
251                log::info!(
252                    "Server {} registered with proxy: {}.localhost -> 127.0.0.1:{}",
253                    proxy_server_name,
254                    proxy_server_name,
255                    proxy_server_port
256                );
257            }
258        });
259    }
260
261    std::thread::spawn(move || {
262        let rt = tokio::runtime::Runtime::new().unwrap();
263        rt.block_on(async move {
264            match server_result.await {
265                Ok(_) => log::info!("Server {} ended normally", server_id_for_thread),
266                Err(e) => {
267                    log::error!("Server {} error: {}", server_id_for_thread, e);
268                    if let Ok(mut servers) = servers_clone.write() {
269                        if let Some(server) = servers.get_mut(&server_id_for_thread) {
270                            server.status = crate::server::types::ServerStatus::Failed;
271                        }
272                    }
273                }
274            }
275
276            if let Err(e) = crate::server::watchdog::stop_server_watching(
277                &server_name_for_cleanup,
278                server_port_for_cleanup,
279            ) {
280                log::warn!("Failed to stop file watching: {}", e);
281            } else {
282                log::info!(
283                    "File watching stopped for server {}",
284                    server_name_for_cleanup
285                );
286            }
287
288            if let Err(e) = logger_for_cleanup.log_server_stop().await {
289                log::error!("Failed to log server stop: {}", e);
290            }
291
292            if let Ok(mut servers) = servers_clone.write() {
293                if let Some(server) = servers.get_mut(&server_id_for_thread) {
294                    server.status = crate::server::types::ServerStatus::Stopped;
295                }
296            }
297        });
298    });
299
300    std::thread::sleep(Duration::from_millis(startup_delay));
301    Ok(server_handle)
302}
303
304#[derive(Debug, Clone)]
305pub struct ServerDataWithConfig {
306    pub server: ServerData,
307    pub proxy_http_port: u16,
308    pub proxy_https_port: u16,
309}