358 lines
12 KiB
Rust
358 lines
12 KiB
Rust
|
|
// 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 {
|
|||
|
|
"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());
|
|||
|
|
}
|
|||
|
|
}
|