futriix/src/server/http.rs

361 lines
12 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::error::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();
// Обработка 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 == "/" {
Ok(Response::new(Body::from("Falcot Database Server - HTTP API")))
}
// 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::new(Body::from(r#"{"status": "ok", "message": "Falcot Server is running"}"#)))
}
/// Wait-free обслуживание статических файлов
async fn handle_static_file(
path: &str,
config: StaticFilesConfig,
) -> Result<Response<Body>> {
let file_path = if path == "/" {
format!("{}/index.html", config.directory)
} else {
format!("{}{}", config.directory, path)
};
match File::open(&file_path).await {
Ok(mut file) => {
let mut contents = Vec::new();
file.read_to_end(&mut contents).await
.map_err(|e: std::io::Error| crate::common::error::FalcotError::HttpError(e.to_string()))?;
let content_type = get_content_type(&file_path);
Ok(Response::builder()
.header("Content-Type", content_type)
.body(Body::from(contents))
.unwrap())
}
Err(_) => {
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"
} else if file_path.ends_with(".css") {
"text/css"
} else if file_path.ends_with(".js") {
"application/javascript"
} 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"
} else if file_path.ends_with(".mp3") {
"audio/mpeg"
} else if file_path.ends_with(".mp4") {
"video/mp4"
} else if file_path.ends_with(".pdf") {
"application/pdf"
} else if file_path.ends_with(".md") {
"text/markdown"
} else {
"text/plain"
}
}
/// Запуск 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::error::FalcotError::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| {
handle_request(req, db.clone(), static_config.clone(), acl_config.clone())
}))
}
});
// Запуск сервера
let builder = Server::bind(&addr_parsed);
let server = if http_config.http2_enabled {
// Включение HTTP/2 для HTTP сервера
builder.http2_only(true).serve(make_svc)
} else {
// Использование HTTP/1.1
builder.serve(make_svc)
};
// Запускаем сервер синхронно
server.await
.map_err(|e: hyper::Error| crate::common::error::FalcotError::HttpError(e.to_string()))?;
Ok(())
}
/// Запуск HTTPS сервера с wait-free архитектурой
// Убираем лишний параметр http_config из сигнатуры функции
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;
use tokio_rustls::TlsAcceptor;
use rustls::{Certificate, PrivateKey, ServerConfig};
use rustls_pemfile::{certs, pkcs8_private_keys};
use std::fs::File;
use std::io::BufReader;
if !tls_config.enabled {
println!("HTTPS disabled in configuration");
return Ok(());
}
// Загрузка TLS сертификата и ключа
let cert_file = File::open(&tls_config.cert_path)
.map_err(|e| crate::common::error::FalcotError::HttpError(format!("Failed to open certificate: {}", e)))?;
let key_file = File::open(&tls_config.key_path)
.map_err(|e| crate::common::error::FalcotError::HttpError(format!("Failed to open key: {}", e)))?;
// Загрузка сертификатов
let cert_chain: Vec<Certificate> = certs(&mut BufReader::new(cert_file))
.map_err(|e| crate::common::error::FalcotError::HttpError(format!("Failed to parse certificate: {}", e)))?
.into_iter()
.map(Certificate)
.collect();
// Загрузка приватного ключа
let mut keys: Vec<PrivateKey> = pkcs8_private_keys(&mut BufReader::new(key_file))
.map_err(|e| crate::common::error::FalcotError::HttpError(format!("Failed to parse key: {}", e)))?
.into_iter()
.map(PrivateKey)
.collect();
if keys.is_empty() {
return Err(crate::common::error::FalcotError::HttpError("No private keys found".to_string()));
}
// Создание конфигурации сервера TLS
let server_config = ServerConfig::builder()
.with_safe_defaults()
.with_no_client_auth()
.with_single_cert(cert_chain, keys.remove(0))
.map_err(|e| crate::common::error::FalcotError::HttpError(format!("Failed to create server config: {}", e)))?;
let addr_parsed: std::net::SocketAddr = addr.parse()
.map_err(|e: std::net::AddrParseError| crate::common::error::FalcotError::HttpError(e.to_string()))?;
let db_clone = db.clone();
let static_clone = static_config.clone();
let acl_clone = acl_config.clone();
// Создание TLS акцептора
let tls_acceptor = TlsAcceptor::from(Arc::new(server_config));
// Создание TCP listener
let tcp_listener = TcpListener::bind(&addr_parsed).await
.map_err(|e| crate::common::error::FalcotError::HttpError(format!("Failed to bind to {}: {}", addr, e)))?;
println!("HTTPS server starting on {}...", addr);
// Принимаем и обрабатываем соединения
while let Ok((tcp_stream, _)) = tcp_listener.accept().await {
let tls_acceptor = tls_acceptor.clone();
let db = db_clone.clone();
let static_config = static_clone.clone();
let acl_config = acl_clone.clone();
tokio::spawn(async move {
// Принимаем TLS соединение
let tls_stream = match tls_acceptor.accept(tcp_stream).await {
Ok(stream) => stream,
Err(e) => {
eprintln!("TLS handshake failed: {}", e);
return;
}
};
// Создаем сервис для этого соединения
let service = service_fn(move |req| {
handle_request(req, db.clone(), static_config.clone(), acl_config.clone())
});
// Обрабатываем HTTP запросы поверх TLS
if let Err(e) = hyper::server::conn::Http::new()
.serve_connection(tls_stream, service)
.await
{
eprintln!("HTTP 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("falcot.log")
{
let timestamp = chrono::Local::now().format("%Y-%m-%d %H:%M:%S");
let log_message = format!("[{}] {}\n", timestamp, message);
let _ = file.write_all(log_message.as_bytes());
}
}