Files
futriix-old/src/server/mod.rs

329 lines
14 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// src/server/mod.rs
//! Сервер Futriix - документо-ориентированная БД с wait-free архитектурой
//!
//! Основной модуль сервера, реализующий wait-free доступ к данным,
//! синхронную master-master репликацию и поддержку HTTP/HTTPS.
#![allow(dead_code)]
use std::sync::Arc;
use std::fs::OpenOptions;
use std::io::Write;
use crate::common::Result;
use crate::common::config::Config;
use crate::lua_shell::LuaShell;
// Импортируем подмодули
pub mod database;
pub mod lua_engine;
pub mod http;
pub mod sharding; // Объединенный модуль шардинга и репликации
pub mod csv_import_export; // Модуль для CSV импорта/экспорта
/// Функция для логирования в файл
fn log_to_file(message: &str) {
if let Ok(mut file) = OpenOptions::new()
.create(true)
.append(true)
.open("futriix.log")
{
let timestamp = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f").to_string();
let log_message = format!("[{}] {}\n", timestamp, message);
let _ = file.write_all(log_message.as_bytes());
}
}
/// Функция для вывода текста с ANSI цветом
#[allow(dead_code)]
fn print_colored(text: &str, ansi_color: &str) {
println!("{}{}\x1b[0m", ansi_color, text);
}
/// Конвертация HEX цвета в ANSI escape code
fn hex_to_ansi(hex_color: &str) -> String {
let hex = hex_color.trim_start_matches('#');
if hex.len() == 6 {
if let (Ok(r), Ok(g), Ok(b)) = (
u8::from_str_radix(&hex[0..2], 16),
u8::from_str_radix(&hex[2..4], 16),
u8::from_str_radix(&hex[4..6], 16),
) {
return format!("\x1b[38;2;{};{};{}m", r, g, b);
}
}
"\x1b[38;2;255;255;255m".to_string()
}
/// Основный сервер Futriix с wait-free архитектураой
pub struct FutriixServer {
config: Config,
database: Arc<database::Database>,
lua_engine: lua_engine::LuaEngine,
sharding_manager: Arc<sharding::ShardingManager>, // Объединенный менеджер
http_enabled: bool,
csv_manager: Arc<csv_import_export::CsvManager>,
}
impl FutriixServer {
/// Создание нового сервера с wait-free архитектурой
pub async fn new(config_path: &str) -> Result<Self> {
// Загрузка конфигурации
let config = Config::load(config_path)?;
// Инициализация компонентов с wait-free подходами
let database = Arc::new(database::Database::new());
let lua_engine = lua_engine::LuaEngine::new()?;
// Инициализация объединенного менеджера шардинга и репликации
let sharding_manager = Arc::new(sharding::ShardingManager::new(
160, // virtual_nodes_per_node
config.replication.enabled,
));
// Инициализация менеджера CSV
let csv_manager = Arc::new(csv_import_export::CsvManager::new(
database.clone(),
config.csv.clone(),
));
// Регистрация функций БД в Lua
lua_engine.register_db_functions(database.clone(), sharding_manager.clone())?;
// Инициализация базы данных
FutriixServer::initialize_database(database.clone())?;
// Проверяем, включен ли HTTP режим (теперь учитываем новые директивы)
let http_enabled = (config.server.http_port.is_some() && config.server.http) ||
(config.server.https_port.is_some() && config.server.https);
Ok(Self {
config,
database,
lua_engine,
sharding_manager,
http_enabled,
csv_manager,
})
}
/// Инициализация базы данных с wait-free структурами
fn initialize_database(db: Arc<database::Database>) -> Result<()> {
// Создаем системные коллекции с wait-free доступом
let _system_collection = db.get_collection("_system");
let _users_collection = db.get_collection("_users");
let _logs_collection = db.get_collection("_logs");
let _procedures_collection = db.get_collection("_procedures");
let _triggers_collection = db.get_collection("_triggers");
let _csv_imports_collection = db.get_collection("_csv_imports");
// Создаем директорию для бэкапов
let backup_dir = "/futriix/backups";
if let Err(e) = std::fs::create_dir_all(backup_dir) {
// Используем текущую директорию как запасной вариант
let current_backup_dir = "./futriix_backups";
if let Err(e2) = std::fs::create_dir_all(current_backup_dir) {
eprintln!("Warning: Failed to create backup directory '{}': {}", backup_dir, e);
eprintln!("Warning: Also failed to create fallback directory '{}': {}", current_backup_dir, e2);
} else {
println!("Backup directory created at: {}", current_backup_dir);
}
} else {
println!("Backup directory created at: {}", backup_dir);
}
// Создаем директорию для CSV файлов
let csv_dir = "/futriix/csv";
if let Err(e) = std::fs::create_dir_all(csv_dir) {
// Используем текущую директорию как запасной вариант
let current_csv_dir = "./futriix_csv";
if let Err(e2) = std::fs::create_dir_all(current_csv_dir) {
eprintln!("Warning: Failed to create CSV directory '{}': {}", csv_dir, e);
eprintln!("Warning: Also failed to create fallback directory '{}': {}", current_csv_dir, e2);
} else {
println!("CSV directory created at: {}", current_csv_dir);
}
} else {
println!("CSV directory created at: {}", csv_dir);
}
// Создаем директорию для статических файлов
let static_dir = "static";
if let Err(e) = std::fs::create_dir_all(static_dir) {
eprintln!("Warning: Failed to create static files directory '{}': {}", static_dir, e);
} else {
println!("Static files directory created at: {}", static_dir);
}
// СОЗДАЕМ ПРОСТОЙ INDEX.HTML ДЛЯ ТЕСТИРОВАНИЯ
let index_html_content = r#"<!DOCTYPE html>
<html>
<head>
<title>Futriix Database Server</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
h1 { color: #00bfff; }
.status { padding: 10px; background: #f0f0f0; border-radius: 5px; }
</style>
</head>
<body>
<h1>Futriix Database Server</h1>
<div class="status">
<p>Server is running successfully!</p>
<p>This is a test page to verify HTTP server functionality.</p>
<p>Current time: <span id="time"></span></p>
</div>
<script>
document.getElementById('time').textContent = new Date().toLocaleString();
</script>
</body>
</html>"#;
if let Err(e) = std::fs::write("static/index.html", index_html_content) {
eprintln!("Warning: Failed to create index.html: {}", e);
} else {
println!("Created test index.html in static directory");
}
let message = "Database initialized with system collections";
println!("{}", message);
log_to_file(message);
Ok(())
}
/// Запуск сервера с wait-free архитектурой
pub async fn run(&self) -> Result<()> {
// Определяем режим работы и имя кластера
let cluster_name = &self.config.cluster.name;
println!("Mode: cluster (cluster: '{}')", cluster_name);
log_to_file("Futriix Database Server started");
log_to_file(&format!("Mode: cluster (cluster: '{}')", cluster_name));
// Запуск HTTP/HTTPS серверов в отдельных задачах, если настроены
if self.http_enabled {
// ЗАПУСКАЕМ СЕРВЕРЫ В ФОНОВЫХ ЗАДАЧАХ, НЕ БЛОКИРУЯ ОСНОВНОЙ ПОТОК
self.start_http_servers_in_background().await?;
} else {
println!("HTTP/HTTPS servers disabled in configuration");
}
// Добавляем пустую строку после информации о серверах
println!();
let mut lua_shell = LuaShell::new(
self.lua_engine.clone(),
self.database.clone(),
self.sharding_manager.clone(),
self.csv_manager.clone(),
);
// Запуск интерактивной оболочки - ЭТО ОСНОВНОЙ ПОТОК ВЫПОЛНЕНИЯ
lua_shell.run().await?;
Ok(())
}
/// Запуск HTTP/HTTPS серверов в фоновых задачах (не блокирующий)
async fn start_http_servers_in_background(&self) -> Result<()> {
let static_config = self::http::StaticFilesConfig::default();
let acl_config = self::http::AclConfig {
enabled: self.config.acl.enabled,
allowed_ips: self.config.acl.allowed_ips.clone(),
denied_ips: self.config.acl.denied_ips.clone(),
};
// Запуск HTTP сервера, если настроен и включен
if let Some(http_port) = self.config.server.http_port {
if self.config.server.http {
let http_addr = format!("{}:{}", self.config.server.host, http_port);
let http_config = self::http::HttpConfig {
enabled: true,
port: http_port,
http2_enabled: self.config.server.http2_enabled.unwrap_or(false),
};
let db_clone = self.database.clone();
let static_config_clone = static_config.clone();
let acl_config_clone = acl_config.clone();
// ЗАПУСКАЕМ В ФОНОВОЙ ЗАДАЧЕ БЕЗ ОЖИДАНИЯ
tokio::spawn(async move {
println!("Starting HTTP server on {}...", http_addr);
match self::http::start_http_server(&http_addr, db_clone, static_config_clone, http_config, acl_config_clone).await {
Ok(_) => {
let message = format!("HTTP server started on {}", http_addr);
println!("{}", message);
log_to_file(&message);
}
Err(e) => {
let message = format!("Failed to start HTTP server: {}", e);
eprintln!("{}", message);
log_to_file(&message);
}
}
});
} else {
println!("HTTP server disabled in configuration");
}
}
// Запуск HTTPS сервера, если настроен и включен
if let Some(https_port) = self.config.server.https_port {
if self.config.server.https && self.config.tls.enabled {
let https_addr = format!("{}:{}", self.config.server.host, https_port);
let tls_config = self::http::TlsConfig {
enabled: self.config.tls.enabled,
cert_path: self.config.tls.cert_path.clone(),
key_path: self.config.tls.key_path.clone(),
};
let db_clone = self.database.clone();
let static_config_clone = static_config.clone();
let acl_config_clone = acl_config.clone();
// ЗАПУСКАЕМ В ФОНОВОЙ ЗАДАЧЕ БЕЗ ОЖИДАНИЯ
tokio::spawn(async move {
println!("Starting HTTPS server on {}...", https_addr);
match self::http::start_https_server(&https_addr, db_clone, static_config_clone, tls_config, acl_config_clone).await {
Ok(_) => {
let message = format!("HTTPS server started on {}", https_addr);
println!("{}", message);
log_to_file(&message);
}
Err(e) => {
let message = format!("Failed to start HTTPS server: {}", e);
eprintln!("{}", message);
log_to_file(&message);
}
}
});
} else {
if !self.config.tls.enabled {
println!("HTTPS disabled: TLS not enabled in configuration");
} else {
println!("HTTPS server disabled in configuration");
}
}
}
Ok(())
}
/// Получение менеджера шардинга (для тестов и расширений)
#[allow(dead_code)]
pub fn get_sharding_manager(&self) -> Arc<sharding::ShardingManager> {
self.sharding_manager.clone()
}
/// Получение менеджера CSV (для тестов и расширений)
#[allow(dead_code)]
pub fn get_csv_manager(&self) -> Arc<csv_import_export::CsvManager> {
self.csv_manager.clone()
}
}