use std::net::{IpAddr, SocketAddr};
use crate::{
ExtractionConfig, Result, core::ServerConfig, extractors, plugins::startup_validation::validate_plugins_at_startup,
};
use super::{config::load_server_config, router::create_router_with_limits_and_server_config, types::ApiSizeLimits};
async fn shutdown_signal() {
#[cfg(unix)]
{
use tokio::signal::unix::{SignalKind, signal};
let mut sigterm = match signal(SignalKind::terminate()) {
Ok(s) => s,
Err(e) => {
tracing::warn!("Failed to install SIGTERM handler: {}", e);
tokio::signal::ctrl_c()
.await
.unwrap_or_else(|e| tracing::warn!("Failed to listen for Ctrl-C: {}", e));
tracing::info!("Shutting down gracefully on signal...");
return;
}
};
tokio::select! {
_ = sigterm.recv() => {
tracing::info!("Shutting down gracefully on signal...");
}
result = tokio::signal::ctrl_c() => {
if let Err(e) = result {
tracing::warn!("Failed to listen for Ctrl-C: {}", e);
}
tracing::info!("Shutting down gracefully on signal...");
}
}
}
#[cfg(not(unix))]
{
tokio::signal::ctrl_c()
.await
.unwrap_or_else(|e| tracing::warn!("Failed to listen for Ctrl-C: {}", e));
tracing::info!("Shutting down gracefully on signal...");
}
}
#[cfg_attr(alef, alef(skip))]
pub async fn serve(host: impl AsRef<str>, port: u16) -> Result<()> {
let extraction_config = match ExtractionConfig::discover()? {
Some(config) => {
tracing::info!("Loaded extraction config from discovered file");
config
}
None => {
tracing::info!("No config file found, using default configuration");
ExtractionConfig::default()
}
};
let server_config = load_server_config(None)?;
let limits = ApiSizeLimits::new(
server_config.max_request_body_bytes,
server_config.max_multipart_field_bytes,
);
extractors::ensure_initialized()?;
validate_plugins_at_startup()?;
serve_with_config_and_limits(host, port, extraction_config, limits).await
}
#[cfg_attr(alef, alef(skip))]
pub async fn serve_with_config(host: impl AsRef<str>, port: u16, config: ExtractionConfig) -> Result<()> {
let limits = ApiSizeLimits::default();
tracing::info!(
"Upload size limit: 100 MB (default, {} bytes)",
limits.max_request_body_bytes
);
extractors::ensure_initialized()?;
validate_plugins_at_startup()?;
serve_with_config_and_limits(host, port, config, limits).await
}
#[cfg_attr(alef, alef(skip))]
pub async fn serve_with_config_and_limits(
host: impl AsRef<str>,
port: u16,
config: ExtractionConfig,
limits: ApiSizeLimits,
) -> Result<()> {
let ip: IpAddr = host
.as_ref()
.parse()
.map_err(|e| crate::error::XbergError::validation(format!("Invalid host address: {}", e)))?;
let server_config = ServerConfig {
host: host.as_ref().to_string(),
port,
max_request_body_bytes: limits.max_request_body_bytes,
max_multipart_field_bytes: limits.max_multipart_field_bytes,
..Default::default()
};
let addr = SocketAddr::new(ip, port);
let app = create_router_with_limits_and_server_config(config, limits, server_config);
extractors::ensure_initialized()?;
validate_plugins_at_startup()?;
tracing::info!("Starting Xberg API server on http://{}:{}", ip, port);
let listener = tokio::net::TcpListener::bind(addr)
.await
.map_err(crate::XbergError::from)?;
axum::serve(listener, app)
.with_graceful_shutdown(shutdown_signal())
.await
.map_err(|e| crate::error::XbergError::Other(e.to_string()))?;
Ok(())
}
#[cfg_attr(alef, alef(skip))]
pub async fn serve_with_server_config(extraction_config: ExtractionConfig, server_config: ServerConfig) -> Result<()> {
let ip: IpAddr = server_config
.host
.parse()
.map_err(|e| crate::error::XbergError::validation(format!("Invalid host address: {}", e)))?;
let limits = ApiSizeLimits::new(
server_config.max_request_body_bytes,
server_config.max_multipart_field_bytes,
);
let addr = SocketAddr::new(ip, server_config.port);
let app = create_router_with_limits_and_server_config(extraction_config, limits, server_config.clone());
extractors::ensure_initialized()?;
validate_plugins_at_startup()?;
tracing::info!(
"Starting Xberg API server on http://{}:{} (request_body_limit={} MB, multipart_field_limit={} MB)",
ip,
server_config.port,
server_config.max_request_body_mb(),
server_config.max_multipart_field_mb()
);
let listener = tokio::net::TcpListener::bind(addr)
.await
.map_err(crate::XbergError::from)?;
axum::serve(listener, app)
.with_graceful_shutdown(shutdown_signal())
.await
.map_err(|e| crate::error::XbergError::Other(e.to_string()))?;
Ok(())
}
#[cfg_attr(alef, alef(skip))]
pub async fn serve_default() -> Result<()> {
serve("127.0.0.1", 8000).await
}