futriix/src/server/mod.rs

820 lines
32 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 - полностью lock-free документо-ориентированная БД
//!
//! Основной модуль сервера, который инициализирует все компоненты системы:
//! - Базу данных с lock-free архитектурой
//! - Lua движок для выполнения скриптов
//! - Менеджер шардинга и репликации
//! - HTTP/HTTPS серверы
//! - CSV менеджер для импорта/экспорта данных
//!
//! Архитектурные особенности:
//! - Все операции выполняются без блокировок (lock-free)
//! - Используются атомарные структуры данных
//! - Поддержка транзакций через Software Transactional Memory
//! - Интеграция с Lua для кастомной логики
#![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;
/// Функция для логирования в файл
/// Используется для отладки и аудита работы сервера
/// Все сообщения записываются в файл futriix.log с временными метками
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 цветом
/// Использует escape-последовательности для цветного форматирования вывода
#[allow(dead_code)]
fn print_colored(text: &str, ansi_color: &str) {
println!("{}{}\x1b[0m", ansi_color, text);
}
/// Конвертация HEX цвета в ANSI escape code
/// Поддерживает формат #RRGGBB для установки произвольных цветов
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 с полностью lock-free архитектурой
/// Координирует работу всех компонентов системы и предоставляет
/// единую точку входа для управления базой данных
pub struct FutriixServer {
config: Config, // Конфигурация сервера
database: Arc<database::Database>, // Lock-free база данных
lua_engine: lua_engine::LuaEngine, // Встроенный Lua интерпретатор
sharding_manager: Arc<sharding::ShardingManager>, // Менеджер шардинга и репликации
http_enabled: bool, // Флаг включения HTTP сервера
csv_manager: Arc<csv_import_export::CsvManager>, // Менеджер CSV операций
}
impl FutriixServer {
/// Создание нового сервера с lock-free архитектурой
/// Инициализирует все компоненты системы на основе конфигурации
pub async fn new(config_path: &str) -> Result<Self> {
// Загрузка конфигурации из файла TOML
let config = Config::load(config_path)?;
// Инициализация компонентов с lock-free подходами
let database = Arc::new(database::Database::new());
let lua_engine = lua_engine::LuaEngine::new()?;
// Генерируем уникальный ID для текущего узла
let node_id = format!("node_{}", uuid::Uuid::new_v4().to_string()[..8].to_string());
// Инициализация менеджера шардинга и репликации
let sharding_manager = Arc::new(sharding::ShardingManager::new(
config.sharding.virtual_nodes_per_node,
config.replication.enabled,
config.sharding.min_nodes_for_cluster,
node_id,
));
// Инициализация менеджера CSV
let csv_manager = Arc::new(csv_import_export::CsvManager::new(
database.clone(),
config.csv.clone(),
));
// Регистрация функций БД в Lua движке
// Это позволяет выполнять 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,
})
}
/// Инициализация базы данных с lock-free структурами
/// Создает системные коллекции, директории для бэкапов и статических файлов
fn initialize_database(db: Arc<database::Database>) -> Result<()> {
// Создаем системные коллекции с lock-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) {
log_to_file(&format!("Warning: Failed to create backup directory '{}': {}", backup_dir, e));
} else {
log_to_file(&format!("Backup directory created at: {}", backup_dir));
}
// Создаем директорию для CSV файлов
let csv_dir = "./futriix_csv";
if let Err(e) = std::fs::create_dir_all(csv_dir) {
log_to_file(&format!("Warning: Failed to create CSV directory '{}': {}", csv_dir, e));
} else {
log_to_file(&format!("CSV directory created at: {}", csv_dir));
}
// Создаем поддиректории для CSV импорта и экспорта
let import_dir = "./futriix_csv/import";
let export_dir = "./futriix_csv/export";
let _ = std::fs::create_dir_all(import_dir);
let _ = std::fs::create_dir_all(export_dir);
// Создаем директорию для статических файлов веб-интерфейса
let static_dir = "static";
if let Err(e) = std::fs::create_dir_all(static_dir) {
log_to_file(&format!("Warning: Failed to create static files directory '{}': {}", static_dir, e));
} else {
log_to_file(&format!("Static files directory created at: {}", static_dir));
}
// ИЗМЕНЕНИЕ: Создаем простой default.html для тестирования вместо index.html
// Используем default.html как точку входа для веб-интерфейса Futriix
let default_html_content = r#"<!DOCTYPE html>
<html>
<head>
<title>Futriix Database Server</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #0f2027, #203a43, #2c5364);
color: #ffffff;
min-height: 100vh;
line-height: 1.6;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
}
header {
text-align: center;
margin-bottom: 3rem;
padding: 2rem;
background: rgba(255, 255, 255, 0.1);
border-radius: 15px;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.logo {
display: flex;
align-items: center;
justify-content: center;
gap: 1rem;
margin-bottom: 1.5rem;
}
.logo-icon {
font-size: 3rem;
color: #00bfff;
}
h1 {
font-size: 3rem;
background: linear-gradient(45deg, #00bfff, #0080ff);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
margin-bottom: 0.5rem;
}
.tagline {
font-size: 1.2rem;
color: #a0d2ff;
margin-bottom: 1rem;
}
.version {
display: inline-block;
background: rgba(0, 191, 255, 0.2);
padding: 0.5rem 1rem;
border-radius: 20px;
font-size: 0.9rem;
color: #00bfff;
}
.status-panel {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1.5rem;
margin-bottom: 3rem;
}
.status-card {
background: rgba(255, 255, 255, 0.08);
padding: 1.5rem;
border-radius: 12px;
border: 1px solid rgba(255, 255, 255, 0.1);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.status-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 30px rgba(0, 191, 255, 0.2);
border-color: rgba(0, 191, 255, 0.3);
}
.status-icon {
font-size: 2rem;
margin-bottom: 1rem;
color: #00bfff;
}
.status-title {
font-size: 1.2rem;
margin-bottom: 0.5rem;
color: #a0d2ff;
}
.status-value {
font-size: 2rem;
font-weight: bold;
color: #ffffff;
}
.status-subtext {
font-size: 0.9rem;
color: #88aacc;
margin-top: 0.5rem;
}
.info-panel {
background: rgba(255, 255, 255, 0.05);
padding: 2rem;
border-radius: 12px;
margin-bottom: 2rem;
}
.info-title {
font-size: 1.5rem;
margin-bottom: 1rem;
color: #00bfff;
}
.info-content {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1.5rem;
}
.info-item {
margin-bottom: 1rem;
}
.info-label {
font-weight: bold;
color: #a0d2ff;
margin-bottom: 0.3rem;
}
.info-value {
color: #ffffff;
}
.features {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin-bottom: 2rem;
}
.feature {
background: rgba(0, 191, 255, 0.1);
padding: 1rem;
border-radius: 8px;
text-align: center;
border: 1px solid rgba(0, 191, 255, 0.2);
}
.feature-icon {
font-size: 1.5rem;
margin-bottom: 0.5rem;
color: #00bfff;
}
.action-buttons {
display: flex;
gap: 1rem;
flex-wrap: wrap;
justify-content: center;
margin-top: 2rem;
}
.btn {
padding: 1rem 2rem;
border: none;
border-radius: 8px;
font-size: 1rem;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
text-decoration: none;
display: inline-block;
}
.btn-primary {
background: linear-gradient(45deg, #00bfff, #0080ff);
color: white;
}
.btn-primary:hover {
background: linear-gradient(45deg, #0080ff, #00bfff);
transform: scale(1.05);
box-shadow: 0 5px 15px rgba(0, 191, 255, 0.4);
}
.btn-secondary {
background: rgba(255, 255, 255, 0.1);
color: white;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.btn-secondary:hover {
background: rgba(255, 255, 255, 0.2);
transform: scale(1.05);
}
.server-time {
text-align: center;
margin-top: 2rem;
padding-top: 2rem;
border-top: 1px solid rgba(255, 255, 255, 0.1);
color: #88aacc;
}
.time-display {
font-size: 1.2rem;
color: #00bfff;
font-weight: bold;
margin-top: 0.5rem;
}
footer {
text-align: center;
margin-top: 3rem;
padding-top: 2rem;
border-top: 1px solid rgba(255, 255, 255, 0.1);
color: #88aacc;
font-size: 0.9rem;
}
@media (max-width: 768px) {
.container {
padding: 1rem;
}
h1 {
font-size: 2rem;
}
.status-panel {
grid-template-columns: 1fr;
}
.action-buttons {
flex-direction: column;
}
.btn {
width: 100%;
text-align: center;
}
}
.status-online {
color: #33d17a;
}
.status-offline {
color: #ff6b6b;
}
.status-loading {
color: #ffa500;
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<div class="logo">
<div class="logo-icon">⚡</div>
<div>
<h1>Futriix Database Server</h1>
<div class="tagline">Lock-Free Document Database with Lua Scripting</div>
</div>
</div>
<div class="version">v1.0.0 • futriix 3i²</div>
</header>
<div class="status-panel">
<div class="status-card">
<div class="status-icon">🚀</div>
<div class="status-title">Server Status</div>
<div class="status-value status-online" id="server-status">Online</div>
<div class="status-subtext" id="connection-info">Ready to accept connections</div>
</div>
<div class="status-card">
<div class="status-icon">🗄️</div>
<div class="status-title">Database</div>
<div class="status-value" id="db-status">Active</div>
<div class="status-subtext">Lock-free architecture</div>
</div>
<div class="status-card">
<div class="status-icon">⚙️</div>
<div class="status-title">Lua Engine</div>
<div class="status-value status-online" id="lua-status">Running</div>
<div class="status-subtext">Script execution enabled</div>
</div>
<!-- ИЗМЕНЕНИЕ: Удалена карточка "Cluster Mode" с надписью "Standalone mode" -->
<!-- Вместо нее оставляем только три карточки статуса для более чистого интерфейса -->
<div class="status-card">
<div class="status-icon">💾</div>
<div class="status-title">Storage</div>
<div class="status-value status-online" id="storage-status">Ready</div>
<div class="status-subtext">CSV import/export available</div>
</div>
</div>
<div class="info-panel">
<div class="info-title">System Information</div>
<div class="info-content">
<div class="info-item">
<div class="info-label">Server Version</div>
<div class="info-value">Futriix 1.0.0</div>
</div>
<div class="info-item">
<div class="info-label">Architecture</div>
<div class="info-value">Wait-Free / Lock-Free</div>
</div>
<div class="info-item">
<div class="info-label">Protocol Support</div>
<div class="info-value">HTTP/1.1, HTTP/2, HTTPS</div>
</div>
<div class="info-item">
<div class="info-label">Default Ports</div>
<div class="info-value">HTTP: 9090, HTTPS: 8443</div>
</div>
</div>
<div class="features">
<div class="feature">
<div class="feature-icon">📊</div>
<div>Real-time Analytics</div>
</div>
<div class="feature">
<div class="feature-icon">🔒</div>
<div>Atomic Transactions</div>
</div>
<div class="feature">
<div class="feature-icon">🚀</div>
<div>High Performance</div>
</div>
<div class="feature">
<div class="feature-icon">📝</div>
<div>Lua Scripting</div>
</div>
<div class="feature">
<div class="feature-icon">🔄</div>
<div>Auto-Sharding</div>
</div>
<div class="feature">
<div class="feature-icon">💾</div>
<div>CSV Import/Export</div>
</div>
</div>
</div>
<div class="action-buttons">
<a href="/api/status" class="btn btn-primary" target="_blank">API Status</a>
<a href="/api/collections" class="btn btn-secondary" target="_blank">Collections</a>
<a href="/docs" class="btn btn-secondary" target="_blank">Documentation</a>
<a href="https://github.com/futriix" class="btn btn-secondary" target="_blank">GitHub</a>
</div>
<div class="server-time">
<div>Server Time</div>
<div class="time-display" id="current-time">Loading...</div>
</div>
<footer>
<p>© 2025 Futriix Database Server. All rights reserved.</p>
<p>Built with Rust • Lock-Free Architecture • Enterprise Ready</p>
</footer>
</div>
<script>
// Обновляем текущее время
function updateTime() {
const now = new Date();
const timeString = now.toLocaleString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
timeZoneName: 'short'
});
document.getElementById('current-time').textContent = timeString;
}
// Проверяем статус кластера
function checkClusterStatus() {
fetch('/api/status')
.then(response => {
if response.ok) {
return response.json();
}
throw new Error('Network response was not ok');
})
.then(data => {
// ИЗМЕНЕНИЕ: Убрана обработка статуса кластера, так как раздел удален
// Вместо этого можно обновить статус хранилища
const storageStatus = document.getElementById('storage-status');
storageStatus.textContent = 'Ready';
storageStatus.className = 'status-value status-online';
})
.catch(error => {
console.log('Status check failed:', error);
const storageStatus = document.getElementById('storage-status');
storageStatus.textContent = 'Ready';
storageStatus.className = 'status-value status-online';
});
}
// Инициализация
document.addEventListener('DOMContentLoaded', () => {
// Начальное обновление времени
updateTime();
// Обновляем время каждую секунду
setInterval(updateTime, 1000);
// Проверяем статус системы
setTimeout(checkClusterStatus, 2000);
// Отслеживание онлайн-статуса
window.addEventListener('online', () => {
document.getElementById('server-status').textContent = 'Online';
document.getElementById('server-status').className = 'status-value status-online';
document.getElementById('connection-info').textContent = 'Ready to accept connections';
});
window.addEventListener('offline', () => {
document.getElementById('server-status').textContent = 'Offline';
document.getElementById('server-status').className = 'status-value status-offline';
document.getElementById('connection-info').textContent = 'No network connection';
});
// Отображаем информацию о браузере в консоли
console.log('Futriix Database Server default.html loaded successfully');
console.log('Server is running with lock-free architecture');
console.log('Available at: ' + window.location.origin);
});
</script>
</body>
</html>"#;
// ИЗМЕНЕНИЕ: Сохраняем как default.html вместо index.html
// Это обеспечивает лучшую совместимость и избегает конфликтов с другими системами
if let Err(e) = std::fs::write("static/default.html", default_html_content) {
log_to_file(&format!("Warning: Failed to create default.html: {}", e));
} else {
log_to_file("Created enhanced default.html in static directory");
log_to_file("Web interface will be available at: http://localhost:9090/default.html");
}
let message = "Database initialized with system collections";
log_to_file(message);
Ok(())
}
/// Запуск сервера с lock-free архитектурой
/// Координирует запуск HTTP серверов и интерактивной оболочки
pub async fn run(&self) -> Result<()> {
// Определяем режим работы и имя кластера
let cluster_name = &self.config.cluster.name;
log_to_file(&format!("Run: cluster (cluster: '{}')", cluster_name));
log_to_file("Futriix Database Server started");
log_to_file(&format!("Run: cluster (cluster: '{}')", cluster_name));
// Сначала запускаем HTTP серверы и ждем их запуска
if self.http_enabled {
self.start_http_servers().await?;
} else {
log_to_file("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(&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(),
};
let mut http_server_handles = Vec::new();
// Запуск 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();
log_to_file(&format!("Starting HTTP server on {}", http_addr));
// Запускаем в фоновой задаче
let handle = tokio::spawn(async move {
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);
log_to_file(&message);
}
Err(e) => {
let message = format!("Failed to start HTTP server: {}", e);
log_to_file(&message);
}
}
});
http_server_handles.push(handle);
} else {
log_to_file("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();
log_to_file(&format!("Starting HTTPS server on {}", https_addr));
let handle = tokio::spawn(async move {
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);
log_to_file(&message);
}
Err(e) => {
let message = format!("Failed to start HTTPS server: {}", e);
log_to_file(&message);
}
}
});
http_server_handles.push(handle);
} else {
if !self.config.tls.enabled {
log_to_file("HTTPS disabled: TLS not enabled in configuration");
} else {
log_to_file("HTTPS server disabled in configuration");
}
}
}
// Ждем небольшое время, чтобы серверы успели стартовать
if !http_server_handles.is_empty() {
log_to_file("Waiting for HTTP servers to start...");
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
}
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()
}
/// Получение базы данных для внешнего использования
#[allow(dead_code)]
pub fn get_database(&self) -> Arc<database::Database> {
self.database.clone()
}
/// Получение Lua движка для внешнего использования
#[allow(dead_code)]
pub fn get_lua_engine(&self) -> lua_engine::LuaEngine {
self.lua_engine.clone()
}
}