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

375 lines
13 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/http.rs
//! HTTP/HTTPS сервер с wait-free обработкой запросов
#![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 crate::common::Result;
use crate::server::database::Database;
/// Конфигурация статических файлов
#[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<String>,
pub denied_ips: Vec<String>,
}
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![],
}
}
}
/// Wait-free обработчик HTTP запросов с поддержкой ACL
async fn handle_request(
req: Request<Body>,
db: Arc<Database>,
static_config: StaticFilesConfig,
acl_config: AclConfig,
) -> Result<Response<Body>> {
// Проверка ACL, если включена
if acl_config.enabled {
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) {
return Ok(Response::builder()
.status(StatusCode::FORBIDDEN)
.body(Body::from("Access denied"))
.unwrap());
}
// Проверка разрешенных IP (если список не пустой)
if !acl_config.allowed_ips.is_empty() && !acl_config.allowed_ips.contains(&ip) {
return Ok(Response::builder()
.status(StatusCode::FORBIDDEN)
.body(Body::from("Access denied"))
.unwrap());
}
}
}
let path = req.uri().path();
println!("HTTP Request: {} {}", req.method(), path);
// Обработка API запросов
if path.starts_with("/api/") {
handle_api_request(req, db).await
}
// Обслуживание статических файлов
else if static_config.enabled {
handle_static_file(path, static_config).await
}
// Корневой путь
else if path == "/" {
// ВОЗВРАЩАЕМ ПРОСТОЙ HTML ДЛЯ КОРНЕВОГО ПУТИ
Ok(Response::builder()
.header("Content-Type", "text/html; charset=utf-8")
.body(Body::from(r#"
<!DOCTYPE html>
<html>
<head>
<title>Futriix Database Server</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
h1 { color: #00bfff; }
</style>
</head>
<body>
<h1>Futriix Database Server</h1>
<p>Server is running successfully!</p>
<p>Try accessing <a href="/index.html">/index.html</a> for the main interface.</p>
</body>
</html>
"#))
.unwrap())
}
// 404 для остальных запросов
else {
Ok(Response::builder()
.status(StatusCode::NOT_FOUND)
.body(Body::from("Not Found"))
.unwrap())
}
}
/// Wait-free обработка API запросов
async fn handle_api_request(
_req: Request<Body>,
_db: Arc<Database>,
) -> Result<Response<Body>> {
// TODO: Реализовать wait-free обработку CRUD операций через HTTP
Ok(Response::builder()
.header("Content-Type", "application/json")
.body(Body::from(r#"{"status": "ok", "message": "Futriix Server is running"}"#))
.unwrap())
}
/// Wait-free обслуживание статических файлов
async fn handle_static_file(
path: &str,
config: StaticFilesConfig,
) -> Result<Response<Body>> {
// Убираем начальный слеш из пути
let clean_path = path.trim_start_matches('/');
// Если путь пустой или корневой, используем index.html
let file_path = if clean_path.is_empty() || clean_path == "/" {
format!("{}/index.html", config.directory)
} else {
format!("{}/{}", config.directory, clean_path)
};
// ДОБАВЛЯЕМ ДЕБАГ-ЛОГИРОВАНИЕ
println!("Trying to serve static file: {}", file_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);
println!("Successfully served static file: {} ({} bytes)", file_path, contents.len());
Ok(Response::builder()
.header("Content-Type", content_type)
.body(Body::from(contents))
.unwrap())
}
Err(e) => {
eprintln!("File not found: {} (error: {})", file_path, e);
// ДОБАВЛЯЕМ ПРОСТОЙ HTML ДЛЯ ТЕСТИРОВАНИЯ, ЕСЛИ ФАЙЛ НЕ НАЙДЕН
if clean_path == "index.html" {
let fallback_html = r#"
<!DOCTYPE html>
<html>
<head>
<title>Futriix Database Server</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
h1 { color: #00bfff; }
</style>
</head>
<body>
<h1>Futriix Database Server</h1>
<p>Welcome to Futriix Database Server!</p>
<p>Static file serving is working correctly.</p>
<p>Current time: PLACEHOLDER_TIME</p>
<p>Requested path: PLACEHOLDER_PATH</p>
</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 по расширению файла
fn get_content_type(file_path: &str) -> &'static str {
if file_path.ends_with(".html") {
"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 {
"text/plain; charset=utf-8"
}
}
/// Запуск HTTP сервера с wait-free архитектурой
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 db_clone = db.clone();
let static_clone = static_config.clone();
let acl_clone = acl_config.clone();
// Создание wait-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();
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();
async move {
handle_request(req, db, static_config, acl_config).await
}
}))
}
});
println!("HTTP server starting on {}...", addr);
// ЗАПУСКАЕМ СЕРВЕР И БЛОКИРУЕМСЯ НА ЕГО ВЫПОЛНЕНИИ
// Это гарантирует, что сервер продолжит работать
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 сервера с wait-free архитектурой
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(());
}
// ПРОСТОЙ ВАРИАНТ БЕЗ TLS ДЛЯ ТЕСТИРОВАНИЯ
// В реальном коде здесь должна быть TLS конфигурация
println!("HTTPS server would start on {} (TLS configuration needed)", addr);
// ЗАПУСКАЕМ ОБЫЧНЫЙ HTTP СЕРВЕР НА HTTPS ПОРТУ ДЛЯ ТЕСТИРОВАНИЯ
// Но используем тот же порт, чтобы не путать
let http_config = HttpConfig {
enabled: true,
port: 8443, // Используем HTTPS порт для тестирования
http2_enabled: false,
};
// ИСПРАВЛЕНИЕ ОШИБКИ: создаем owned копии всех данных для использования в async move
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();
// Запускаем обычный HTTP сервер на HTTPS порту для тестирования
// Это временное решение до настройки TLS
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(())
}
// Вспомогательная функция для логирования
fn log_to_file(message: &str) {
use std::fs::OpenOptions;
use std::io::Write;
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());
}
}