Delete src/server/http.rs
This commit is contained in:
parent
54d7313fe8
commit
54b95f0ee6
@ -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(())
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user