// src/server/http.rs //! Lock-free HTTP/HTTPS сервер для Futriix Database //! //! Этот модуль реализует веб-интерфейс для базы данных с поддержкой: //! - HTTP/1.1 и HTTP/2 протоколов //! - TLS/HTTPS для безопасного соединения //! - Обслуживание статических файлов (HTML, CSS, JS) //! - REST API для доступа к данным //! - Статистику запросов без блокировок use std::sync::Arc; use hyper::{Body, Request, Response, Server, StatusCode}; use hyper::service::{make_service_fn, service_fn}; use tokio::fs::File; use tokio::io::AsyncReadExt; use crate::server::database::Database; /// Конфигурация статических файлов /// Определяет параметры обслуживания статических ресурсов #[derive(Clone)] pub struct StaticFilesConfig { pub enabled: bool, // Включено ли обслуживание статических файлов pub directory: String, // Директория со статическими файлами } impl Default for StaticFilesConfig { fn default() -> Self { Self { enabled: true, directory: "static".to_string(), } } } /// Конфигурация TLS для HTTPS сервера /// Содержит пути к сертификатам и ключам #[derive(Clone)] pub struct TlsConfig { pub enabled: bool, // Включен ли TLS pub cert_path: String, // Путь к файлу сертификата pub key_path: String, // Путь к файлу приватного ключа } impl Default for TlsConfig { fn default() -> Self { Self { enabled: true, cert_path: "certs/cert.pem".to_string(), key_path: "certs/key.pem".to_string(), } } } /// Конфигурация HTTP сервера /// Определяет параметры протокола и порты #[derive(Clone)] pub struct HttpConfig { pub enabled: bool, // Включен ли HTTP сервер pub port: u16, // Порт для привязки pub http2_enabled: bool, // Включена ли поддержка HTTP/2 } /// Конфигурация Access Control List (ACL) /// Позволяет управлять доступом по IP-адресам #[derive(Clone)] pub struct AclConfig { pub enabled: bool, // Включена ли проверка ACL pub allowed_ips: Vec, // Разрешенные IP-адреса pub denied_ips: Vec, // Запрещенные IP-адреса } impl Default for AclConfig { fn default() -> Self { Self { enabled: false, allowed_ips: vec!["127.0.0.1".to_string(), "::1".to_string()], denied_ips: vec![], } } } /// Lock-free обработчик HTTP запросов /// Диспетчеризирует запросы по типам и выполняет соответствующие операции async fn handle_request( req: Request, db: Arc, static_config: StaticFilesConfig, acl_config: AclConfig, ) -> Result, crate::common::FutriixError> { // Проверка ACL с lock-free доступом let acl_check = if acl_config.enabled { let mut allowed = false; if let Some(remote_addr) = req.extensions().get::() { let ip = remote_addr.ip().to_string(); // Проверка запрещенных IP if acl_config.denied_ips.contains(&ip) { return Ok(Response::builder() .status(StatusCode::FORBIDDEN) .body(Body::from("Access denied")) .unwrap()); } // Проверка разрешенных IP (если список не пустой) if !acl_config.allowed_ips.is_empty() { allowed = acl_config.allowed_ips.contains(&ip); } else { allowed = true; } } allowed } else { true }; if !acl_check { return Ok(Response::builder() .status(StatusCode::FORBIDDEN) .body(Body::from("Access denied")) .unwrap()); } let path = req.uri().path(); // Логируем запрос без блокировок в файл crate::server::log_to_file(&format!("HTTP Request: {}", path)); // Обработка API запросов let result = if path.starts_with("/api/") { handle_api_request(req, db).await } // Обслуживание статических файлов else if static_config.enabled { handle_static_file(path, static_config).await } // Корневой путь else if path == "/" { // Корневой путь перенаправляет на главную страницу Ok(Response::builder() .status(StatusCode::TEMPORARY_REDIRECT) .header("Location", "/index.html") .body(Body::from("Redirecting to main page")) .unwrap()) } // 404 для остальных запросов else { Ok(Response::builder() .status(StatusCode::NOT_FOUND) .body(Body::from("Not Found")) .unwrap()) }; result } /// Lock-free обработка API запросов /// Обрабатывает REST API endpoints для доступа к данным async fn handle_api_request( req: Request, db: Arc, ) -> Result, crate::common::FutriixError> { // Разбираем путь API let path = req.uri().path(); let parts: Vec<&str> = path.trim_start_matches("/api/").split('/').collect(); if parts.is_empty() { return Ok(Response::builder() .header("Content-Type", "application/json") .body(Body::from(r#"{"status": "ok", "message": "Server API is running"}"#)) .unwrap()); } match parts[0] { "status" => { // Статус сервера Ok(Response::builder() .header("Content-Type", "application/json") .body(Body::from(r#"{"status": "running", "timestamp": ""#.to_owned() + &chrono::Utc::now().to_rfc3339() + r#"", "version": "1.0.0"}"#)) .unwrap()) } _ => { Ok(Response::builder() .status(StatusCode::NOT_FOUND) .header("Content-Type", "application/json") .body(Body::from(r#"{"status": "error", "message": "API endpoint not found"}"#)) .unwrap()) } } } /// Lock-free обслуживание статических файлов /// Читает файлы из директории и отдает их с правильным Content-Type async fn handle_static_file( path: &str, config: StaticFilesConfig, ) -> Result, crate::common::FutriixError> { // Убираем начальный слеш из пути let clean_path = path.trim_start_matches('/'); // Определяем путь к файлу let file_path = if clean_path.is_empty() { format!("{}/index.html", config.directory) } else { format!("{}/{}", config.directory, clean_path) }; match File::open(&file_path).await { Ok(mut file) => { let mut contents = Vec::new(); if let Err(e) = file.read_to_end(&mut contents).await { crate::server::log_to_file(&format!("Failed to read file {}: {}", file_path, e)); return Ok(Response::builder() .status(StatusCode::INTERNAL_SERVER_ERROR) .body(Body::from("Internal server error")) .unwrap()); } let content_type = get_content_type(&file_path); Ok(Response::builder() .header("Content-Type", content_type) .body(Body::from(contents)) .unwrap()) } Err(e) => { crate::server::log_to_file(&format!("File not found: {} (error: {})", file_path, e)); Ok(Response::builder() .status(StatusCode::NOT_FOUND) .body(Body::from("File not found")) .unwrap()) } } } /// Определение Content-Type по расширению файла /// Сопоставляет расширения файлов с соответствующими MIME-типами fn get_content_type(file_path: &str) -> &'static str { if file_path.ends_with(".html") || file_path.ends_with(".htm") { "text/html; charset=utf-8" } else if file_path.ends_with(".css") { "text/css; charset=utf-8" } else if file_path.ends_with(".js") { "application/javascript; charset=utf-8" } else if file_path.ends_with(".png") { "image/png" } else if file_path.ends_with(".jpg") || file_path.ends_with(".jpeg") { "image/jpeg" } else if file_path.ends_with(".json") { "application/json; charset=utf-8" } else if file_path.ends_with(".ico") { "image/x-icon" } else if file_path.ends_with(".svg") { "image/svg+xml" } else if file_path.ends_with(".txt") { "text/plain; charset=utf-8" } else { "application/octet-stream" } } /// Запуск HTTP сервера с lock-free архитектурой /// Создает сервер Hyper, настраивает обработчики и запускает его pub async fn start_http_server( addr: &str, db: Arc, static_config: StaticFilesConfig, http_config: HttpConfig, acl_config: AclConfig, ) -> Result<(), crate::common::FutriixError> { let addr_parsed: std::net::SocketAddr = addr.parse() .map_err(|e: std::net::AddrParseError| crate::common::FutriixError::HttpError(e.to_string()))?; let db_clone = db.clone(); let static_clone = static_config.clone(); let acl_clone = acl_config.clone(); // Создание lock-free сервиса let make_svc = make_service_fn(move |_conn| { let db = db_clone.clone(); let static_config = static_clone.clone(); let acl_config = acl_clone.clone(); async move { Ok::<_, hyper::Error>(service_fn(move |req| { let db = db.clone(); let static_config = static_config.clone(); let acl_config = acl_config.clone(); async move { handle_request(req, db, static_config, acl_config).await } })) } }); crate::server::log_to_file(&format!("HTTP server starting on {}", addr)); // Запускаем сервер let server = Server::bind(&addr_parsed).serve(make_svc); if let Err(e) = server.await { crate::server::log_to_file(&format!("HTTP server error: {}", e)); return Err(crate::common::FutriixError::HttpError(e.to_string())); } Ok(()) } /// Запуск HTTPS сервера с lock-free архитектурой /// Настраивает TLS и запускает защищенный сервер pub async fn start_https_server( addr: &str, db: Arc, static_config: StaticFilesConfig, tls_config: TlsConfig, acl_config: AclConfig, ) -> Result<(), crate::common::FutriixError> { use tokio::net::TcpListener; if !tls_config.enabled { crate::server::log_to_file("HTTPS disabled: TLS not enabled"); return Ok(()); } crate::server::log_to_file(&format!("HTTPS server would start on {} (TLS configuration needed)", addr)); // Запускаем обычный HTTP сервер на HTTPS порту для тестирования let http_config = HttpConfig { enabled: true, port: 8443, http2_enabled: false, }; let owned_addr = addr.to_string(); let owned_db = db.clone(); let owned_static_config = static_config.clone(); let owned_acl_config = acl_config.clone(); let server_future = async move { start_http_server(&owned_addr, owned_db, owned_static_config, http_config, owned_acl_config).await }; tokio::spawn(async move { if let Err(e) = server_future.await { crate::server::log_to_file(&format!("HTTPS (HTTP fallback) server error: {}", e)); } }); Ok(()) }