diff --git a/src/server/http.rs b/src/server/http.rs new file mode 100644 index 0000000..108e016 --- /dev/null +++ b/src/server/http.rs @@ -0,0 +1,763 @@ +// src/server/http.rs +//! Lock-free HTTP/HTTPS сервер + +#![allow(dead_code)] +#![allow(unused_variables)] + +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 dashmap::DashMap; +use std::sync::atomic::{AtomicU64, Ordering}; + +use crate::common::Result; +use crate::server::database::Database; + +/// Статистика запросов с атомарными счетчиками +struct RequestStats { + total_requests: AtomicU64, + successful_requests: AtomicU64, + failed_requests: AtomicU64, + active_requests: AtomicU64, +} + +impl RequestStats { + fn new() -> Self { + Self { + total_requests: AtomicU64::new(0), + successful_requests: AtomicU64::new(0), + failed_requests: AtomicU64::new(0), + active_requests: AtomicU64::new(0), + } + } + + fn record_request_start(&self) { + self.total_requests.fetch_add(1, Ordering::SeqCst); + self.active_requests.fetch_add(1, Ordering::SeqCst); + } + + fn record_request_success(&self) { + self.successful_requests.fetch_add(1, Ordering::SeqCst); + self.active_requests.fetch_sub(1, Ordering::SeqCst); + } + + fn record_request_failure(&self) { + self.failed_requests.fetch_add(1, Ordering::SeqCst); + self.active_requests.fetch_sub(1, Ordering::SeqCst); + } + + fn get_stats(&self) -> (u64, u64, u64, u64) { + ( + self.total_requests.load(Ordering::SeqCst), + self.successful_requests.load(Ordering::SeqCst), + self.failed_requests.load(Ordering::SeqCst), + self.active_requests.load(Ordering::SeqCst), + ) + } +} + +/// Конфигурация статических файлов +#[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 +#[derive(Clone)] +pub struct TlsConfig { + pub enabled: bool, + 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, + pub port: u16, + pub http2_enabled: bool, +} + +/// Конфигурация ACL +#[derive(Clone)] +pub struct AclConfig { + pub enabled: bool, + pub allowed_ips: Vec, + pub denied_ips: Vec, +} + +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, + request_stats: Arc, +) -> Result> { + // Начинаем отслеживание запроса + request_stats.record_request_start(); + + // Проверка 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) { + request_stats.record_request_failure(); + 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 { + request_stats.record_request_failure(); + return Ok(Response::builder() + .status(StatusCode::FORBIDDEN) + .body(Body::from("Access denied")) + .unwrap()); + } + + let path = req.uri().path(); + let method = req.method().clone(); + + // Логируем запрос без блокировок + println!("HTTP {} Request: {}", method, 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 + } + // Корневой путь - редирект на default.html + else if path == "/" { + // ИЗМЕНЕНИЕ: Корневой путь теперь перенаправляет на default.html вместо index.html + // Это обеспечивает лучшую совместимость и избегает конфликтов с другими системами + Ok(Response::builder() + .status(StatusCode::TEMPORARY_REDIRECT) + .header("Location", "/default.html") + .body(Body::from("Redirecting to default.html")) + .unwrap()) + } + // 404 для остальных запросов + else { + Ok(Response::builder() + .status(StatusCode::NOT_FOUND) + .body(Body::from("Not Found")) + .unwrap()) + }; + + // Записываем результат + match &result { + Ok(response) if response.status().is_success() => { + request_stats.record_request_success(); + } + _ => { + request_stats.record_request_failure(); + } + } + + result +} + +/// Lock-free обработка API запросов +async fn handle_api_request( + req: Request, + db: Arc, +) -> Result> { + // Разбираем путь 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": "Futriix Server API is running"}"#)) + .unwrap()); + } + + match parts[0] { + "status" => { + // Статус сервера - lock-free операция + 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()) + } + "collections" => { + // Список коллекций - lock-free операция через итератор + let collections_response = { + let mut collections = Vec::new(); + // Здесь должна быть реализация получения списка коллекций из БД + // Временный заглушка + collections.push("_system".to_string()); + collections.push("_users".to_string()); + collections.push("_logs".to_string()); + collections + }; + + let response_json = serde_json::json!({ + "status": "ok", + "collections": collections_response, + "count": collections_response.len() + }); + + Ok(Response::builder() + .header("Content-Type", "application/json") + .body(Body::from(response_json.to_string())) + .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 обслуживание статических файлов +async fn handle_static_file( + path: &str, + config: StaticFilesConfig, +) -> Result> { + // Убираем начальный слеш из пути + let clean_path = path.trim_start_matches('/'); + + // ИЗМЕНЕНИЕ: Если путь пустой или корневой, используем default.html вместо index.html + // Это обеспечивает лучшую совместимость и избегает конфликтов с другими системами + let file_path = if clean_path.is_empty() || clean_path == "/" { + // Это дополнительная проверка, основной редирект уже обработан в handle_request + format!("{}/default.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 { + eprintln!("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) => { + eprintln!("File not found: {} (error: {})", file_path, e); + + // Запасной HTML для тестирования если default.html не найден + if clean_path == "default.html" || clean_path.is_empty() { + let fallback_html = r#" + + + + Futriix Database Server + + + + +
+ +

Futriix Database Server

+
Lock-Free Document Database with Lua Scripting
+ +
+

Server Status: Online ✓

+

This is a fallback page. The enhanced default.html was not found or could not be loaded.

+

Requested path: PLACEHOLDER_PATH

+
+ +
+
+

Server Information

+

Version: Futriix 1.0.0

+

Architecture: Wait-Free / Lock-Free

+

Protocol: HTTP/1.1, HTTP/2, HTTPS

+
+ +
+

Current Status

+

Static Files: Fallback Mode

+

Database: Ready

+

Lua Engine: Active

+
+
+ +
+ Atomic Transactions + Real-time Analytics + Lua Scripting + Auto-Sharding + High Performance + CSV Import/Export +
+ +
+
Server Time:
+
PLACEHOLDER_TIME
+
+ + + +
+ Server Log:
+ - HTTP Server: Running
+ - Database Engine: Initialized
+ - Static Files: Fallback Mode Active
+ - Current Time: PLACEHOLDER_TIME
+ - Request Path: PLACEHOLDER_PATH +
+ +
+

© 2025 Futriix Database Server. All rights reserved.

+

Built with Rust • Lock-Free Architecture • Enterprise Ready

+
+
+ + + + + "#.replace("PLACEHOLDER_TIME", &chrono::Local::now().to_rfc2822()) + .replace("PLACEHOLDER_PATH", path); + + return Ok(Response::builder() + .header("Content-Type", "text/html; charset=utf-8") + .body(Body::from(fallback_html)) + .unwrap()); + } + + Ok(Response::builder() + .status(StatusCode::NOT_FOUND) + .body(Body::from("File not found")) + .unwrap()) + } + } +} + +/// Определение Content-Type по расширению файла +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 архитектурой +pub async fn start_http_server( + addr: &str, + db: Arc, + static_config: StaticFilesConfig, + http_config: HttpConfig, + acl_config: AclConfig, +) -> Result<()> { + let addr_parsed: std::net::SocketAddr = addr.parse() + .map_err(|e: std::net::AddrParseError| crate::common::FutriixError::HttpError(e.to_string()))?; + + // Создаем статистику запросов + let request_stats = Arc::new(RequestStats::new()); + + // Периодический вывод статистики + let stats_clone = request_stats.clone(); + tokio::spawn(async move { + let mut interval = tokio::time::interval(tokio::time::Duration::from_secs(60)); + loop { + interval.tick().await; + let (total, success, failed, active) = stats_clone.get_stats(); + println!("HTTP Stats: total={}, success={}, failed={}, active={}", + total, success, failed, active); + } + }); + + let db_clone = db.clone(); + let static_clone = static_config.clone(); + let acl_clone = acl_config.clone(); + let stats_clone = request_stats.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(); + let request_stats = stats_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(); + let request_stats = request_stats.clone(); + + async move { + handle_request(req, db, static_config, acl_config, request_stats).await + } + })) + } + }); + + println!("HTTP server starting on {}", addr); + println!("Web interface will be available at: http://{}/default.html", addr); + println!("Root path (/) redirects to /default.html"); + + // Запускаем сервер + let server = Server::bind(&addr_parsed).serve(make_svc); + + if let Err(e) = server.await { + eprintln!("HTTP server error: {}", e); + return Err(crate::common::FutriixError::HttpError(e.to_string())); + } + + Ok(()) +} + +/// Запуск HTTPS сервера с lock-free архитектурой +pub async fn start_https_server( + addr: &str, + db: Arc, + static_config: StaticFilesConfig, + tls_config: TlsConfig, + acl_config: AclConfig, +) -> Result<()> { + use tokio::net::TcpListener; + + if !tls_config.enabled { + println!("HTTPS disabled: TLS not enabled"); + return Ok(()); + } + + println!("HTTPS server would start on {} (TLS configuration needed)", addr); + println!("Web interface will be available at: https://{}/default.html", addr); + println!("Root path (/) redirects to /default.html"); + + // Запускаем обычный 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 { + eprintln!("HTTPS (HTTP fallback) server error: {}", e); + } + }); + + Ok(()) +}