Delete src/server/http.rs

This commit is contained in:
Григорий Сафронов 2025-12-11 22:21:57 +00:00
parent 54d7313fe8
commit 54b95f0ee6

View File

@ -1,790 +0,0 @@
// src/server/http.rs
//! Lock-free HTTP/HTTPS сервер для Futriix Database
//!
//! Этот модуль реализует веб-интерфейс для базы данных с поддержкой:
//! - HTTP/1.1 и HTTP/2 протоколов
//! - TLS/HTTPS для безопасного соединения
//! - Обслуживание статических файлов (HTML, CSS, JS)
//! - REST API для доступа к данным
//! - Access Control Lists (ACL) для управления доступом
//! - Статистику запросов без блокировок
//!
//! Архитектурные особенности:
//! - Атомарные счетчики для статистики (без блокировок)
//! - Изолированная обработка каждого запроса
//! - Поддержка консистентного хэширования для распределенных систем
//! - Автоматическое определение Content-Type файлов
#![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),
}
}
/// Записывает начало обработки запроса
/// Увеличивает счетчики total_requests и active_requests
fn record_request_start(&self) {
self.total_requests.fetch_add(1, Ordering::SeqCst);
self.active_requests.fetch_add(1, Ordering::SeqCst);
}
/// Записывает успешное завершение запроса
/// Увеличивает счетчик successful_requests и уменьшает active_requests
fn record_request_success(&self) {
self.successful_requests.fetch_add(1, Ordering::SeqCst);
self.active_requests.fetch_sub(1, Ordering::SeqCst);
}
/// Записывает неудачное завершение запроса
/// Увеличивает счетчик failed_requests и уменьшает active_requests
fn record_request_failure(&self) {
self.failed_requests.fetch_add(1, Ordering::SeqCst);
self.active_requests.fetch_sub(1, Ordering::SeqCst);
}
/// Возвращает текущие значения статистики
/// Возвращает кортеж: (total, success, failed, active)
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 для 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<String>, // Разрешенные IP-адреса
pub denied_ips: Vec<String>, // Запрещенные 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<Body>,
db: Arc<Database>,
static_config: StaticFilesConfig,
acl_config: AclConfig,
request_stats: Arc<RequestStats>,
) -> Result<Response<Body>> {
// Начинаем отслеживание запроса
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::<std::net::SocketAddr>() {
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();
// Логируем запрос без блокировок
// ИЗМЕНЕНИЕ: Отключаем непрерывный вывод логов в CLI
// 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 запросов
/// Обрабатывает REST API endpoints для доступа к данным
async fn handle_api_request(
req: Request<Body>,
db: Arc<Database>,
) -> Result<Response<Body>> {
// Разбираем путь 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 обслуживание статических файлов
/// Читает файлы из директории и отдает их с правильным Content-Type
async fn handle_static_file(
path: &str,
config: StaticFilesConfig,
) -> Result<Response<Body>> {
// Убираем начальный слеш из пути
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#"
<!DOCTYPE html>
<html>
<head>
<title>Futriix Database Server</title>
<meta charset="utf-8">
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
margin: 0;
padding: 40px;
background: linear-gradient(135deg, #0f2027, #203a43, #2c5364);
color: #ffffff;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
}
.container {
max-width: 800px;
padding: 40px;
background: rgba(255, 255, 255, 0.1);
border-radius: 20px;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
}
h1 {
font-size: 3rem;
margin-bottom: 20px;
background: linear-gradient(45deg, #00bfff, #0080ff);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.subtitle {
font-size: 1.5rem;
color: #a0d2ff;
margin-bottom: 30px;
}
.logo {
font-size: 4rem;
margin-bottom: 30px;
color: #00bfff;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.1); }
}
.status {
padding: 20px;
background: rgba(0, 191, 255, 0.1);
border-radius: 10px;
margin: 30px 0;
border: 1px solid rgba(0, 191, 255, 0.3);
}
.status h2 {
color: #00bfff;
margin-top: 0;
}
.info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin: 30px 0;
}
.info-card {
background: rgba(255, 255, 255, 0.08);
padding: 20px;
border-radius: 10px;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.info-card h3 {
color: #a0d2ff;
margin-top: 0;
}
.btn {
display: inline-block;
padding: 15px 30px;
background: linear-gradient(45deg, #00bfff, #0080ff);
color: white;
text-decoration: none;
border-radius: 8px;
font-weight: bold;
margin: 10px;
transition: all 0.3s ease;
border: none;
cursor: pointer;
}
.btn:hover {
background: linear-gradient(45deg, #0080ff, #00bfff);
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(0, 191, 255, 0.3);
}
.btn-secondary {
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.btn-secondary:hover {
background: rgba(255, 255, 255, 0.2);
}
.features {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 15px;
margin: 30px 0;
}
.feature-tag {
background: rgba(0, 191, 255, 0.15);
padding: 8px 15px;
border-radius: 20px;
font-size: 0.9rem;
color: #a0d2ff;
border: 1px solid rgba(0, 191, 255, 0.3);
}
.time-display {
font-family: 'Courier New', monospace;
font-size: 1.2rem;
color: #00ffaa;
background: rgba(0, 0, 0, 0.3);
padding: 10px 20px;
border-radius: 5px;
margin: 20px 0;
display: inline-block;
}
footer {
margin-top: 40px;
padding-top: 20px;
border-top: 1px solid rgba(255, 255, 255, 0.1);
color: #88aacc;
font-size: 0.9rem;
}
.server-info {
font-family: 'Courier New', monospace;
background: rgba(0, 0, 0, 0.3);
padding: 15px;
border-radius: 5px;
margin: 20px 0;
text-align: left;
overflow-x: auto;
}
.path-info {
color: #ffa500;
font-weight: bold;
}
@media (max-width: 768px) {
body {
padding: 20px;
}
.container {
padding: 20px;
}
h1 {
font-size: 2rem;
}
.subtitle {
font-size: 1.2rem;
}
}
</style>
</head>
<body>
<div class="container">
<div class="logo"></div>
<h1>Futriix Database Server</h1>
<div class="subtitle">Lock-Free Document Database with Lua Scripting</div>
<div class="status">
<h2>Server Status: <span style="color: #33d17a;">Online </span></h2>
<p>This is a fallback page. The enhanced default.html was not found or could not be loaded.</p>
<p>Requested path: <span class="path-info">PLACEHOLDER_PATH</span></p>
</div>
<div class="info-grid">
<div class="info-card">
<h3>Server Information</h3>
<p><strong>Version:</strong> Futriix 1.0.0</p>
<p><strong>Architecture:</strong> Wait-Free / Lock-Free</p>
<p><strong>Protocol:</strong> HTTP/1.1, HTTP/2, HTTPS</p>
</div>
<div class="info-card">
<h3>Current Status</h3>
<p><strong>Static Files:</strong> Fallback Mode</p>
<p><strong>Database:</strong> Ready</p>
<p><strong>Lua Engine:</strong> Active</p>
</div>
</div>
<div class="features">
<span class="feature-tag">Atomic Transactions</span>
<span class="feature-tag">Real-time Analytics</span>
<span class="feature-tag">Lua Scripting</span>
<span class="feature-tag">Auto-Sharding</span>
<span class="feature-tag">High Performance</span>
<span class="feature-tag">CSV Import/Export</span>
</div>
<div class="server-time">
<div>Server Time:</div>
<div class="time-display" id="current-time">PLACEHOLDER_TIME</div>
</div>
<div>
<a href="/api/status" class="btn" target="_blank">API Status</a>
<a href="/api/collections" class="btn btn-secondary" target="_blank">Collections</a>
<a href="https://github.com/futriix" class="btn btn-secondary" target="_blank">GitHub</a>
</div>
<div class="server-info">
<strong>Server Log:</strong><br>
- HTTP Server: Running<br>
- Database Engine: Initialized<br>
- Static Files: Fallback Mode Active<br>
- Current Time: PLACEHOLDER_TIME<br>
- Request Path: PLACEHOLDER_PATH
</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.querySelectorAll('#current-time, .time-display').forEach(el => {
el.textContent = timeString;
});
// Обновляем серверную информацию
document.querySelectorAll('.server-info').forEach(el => {
el.innerHTML = el.innerHTML.replace(/PLACEHOLDER_TIME/g, timeString);
});
}
// Инициализация
document.addEventListener('DOMContentLoaded', () => {
// Обновляем информацию о пути
const currentPath = window.location.pathname;
document.querySelectorAll('.path-info, .server-info').forEach(el => {
el.innerHTML = el.innerHTML.replace(/PLACEHOLDER_PATH/g, currentPath);
});
// Начальное обновление времени
updateTime();
// Обновляем время каждую секунду
setInterval(updateTime, 1000);
// Отображаем информацию о браузере в консоли
console.log('Futriix Database Server - Fallback Page Loaded');
console.log('Server is running with lock-free architecture');
console.log('Static files directory: static/');
console.log('Requested path: ' + currentPath);
console.log('Enhanced default.html not found, showing fallback page');
});
</script>
</body>
</html>
"#.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 по расширению файла
/// Сопоставляет расширения файлов с соответствующими 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<Database>,
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());
// ИЗМЕНЕНИЕ: Отключаем периодический вывод статистики в CLI
// Периодический вывод статистики был отключен, так как он мешал работе
// интерактивной оболочки и встроенной субд
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 архитектурой
/// Настраивает TLS и запускает защищенный сервер
pub async fn start_https_server(
addr: &str,
db: Arc<Database>,
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(())
}