use crate::http::kernel::Route;
use crate::http::shutdown::{ShutdownConfig, ShutdownRegistry};
use crate::http::Method;
use crate::FoxtiveNtexState;
use foxtive::prelude::AppResult;
use foxtive::setup::trace::Tracing;
use foxtive::setup::FoxtiveSetup;
use ntex::time::Seconds;
use std::any::Any;
use std::collections::HashMap;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
pub type ShutdownSignalHandler = Pin<Box<dyn Future<Output = ()> + Send + 'static>>;
type CustomStateBuilderFn = Box<dyn FnOnce() -> HashMap<String, Box<dyn Any + Send + Sync>> + Send>;
#[cfg(feature = "static")]
pub struct StaticFileConfig {
pub path: String,
pub dir: String,
}
#[derive(Clone, Debug)]
pub struct BodyConfig {
pub(crate) json_limit: usize,
pub(crate) string_limit: usize,
pub(crate) byte_limit: usize,
}
impl BodyConfig {
pub fn json_limit(mut self, limit: usize) -> Self {
self.json_limit = limit;
self
}
pub fn string_limit(mut self, limit: usize) -> Self {
self.string_limit = limit;
self
}
pub fn byte_limit(mut self, limit: usize) -> Self {
self.byte_limit = limit;
self
}
}
impl Default for BodyConfig {
fn default() -> Self {
Self {
json_limit: 51_000,
string_limit: 51_000,
byte_limit: 51_000,
}
}
}
#[deprecated(since = "0.31.0", note = "Use BodyConfig instead")]
pub type JsonConfig = BodyConfig;
pub struct ServerBuilder {
pub(crate) host: String,
pub(crate) port: u16,
pub(crate) workers: usize,
pub(crate) max_connections: usize,
pub(crate) max_connections_rate: usize,
pub(crate) client_timeout: Seconds,
pub(crate) client_disconnect: Seconds,
pub(crate) keep_alive: Seconds,
pub(crate) backlog: i32,
pub(crate) body_config: Option<BodyConfig>,
pub(crate) app: String,
pub(crate) foxtive_setup: FoxtiveSetup,
pub(crate) tracing: Option<Tracing>,
#[cfg(feature = "static")]
pub(crate) static_config: StaticFileConfig,
pub(crate) has_started_bootstrap: bool,
pub(crate) allowed_origins: Vec<String>,
pub(crate) allowed_methods: Vec<Method>,
pub(crate) route_factory: Arc<dyn Fn() -> Vec<Route> + Send + Sync>,
pub(crate) on_shutdown: Option<ShutdownSignalHandler>,
pub(crate) shutdown_signal: Option<Pin<Box<dyn Future<Output = ()> + Send>>>,
pub(crate) shutdown_config: Option<ShutdownConfig>,
pub(crate) shutdown_registry: ShutdownRegistry,
pub(crate) custom_state_builder: Option<CustomStateBuilderFn>,
}
impl ServerBuilder {
pub fn create(host: &str, port: u16, setup: FoxtiveSetup) -> ServerBuilder {
ServerBuilder {
host: host.to_string(),
port,
workers: 2,
max_connections: 25_000,
max_connections_rate: 256,
client_timeout: Seconds(3),
client_disconnect: Seconds(5),
keep_alive: Seconds(5),
backlog: 2048,
app: "foxtive".to_string(),
foxtive_setup: setup,
#[cfg(feature = "static")]
static_config: StaticFileConfig::default(),
has_started_bootstrap: false,
allowed_origins: vec![],
allowed_methods: vec![],
route_factory: Arc::new(Vec::new),
tracing: None,
body_config: None,
on_shutdown: None,
shutdown_signal: None,
shutdown_config: None,
shutdown_registry: ShutdownRegistry::new(),
custom_state_builder: None,
}
}
#[cfg(feature = "static")]
pub fn create_with_static(
host: &str,
port: u16,
setup: FoxtiveSetup,
config: StaticFileConfig,
) -> ServerBuilder {
Self::create(host, port, setup).static_config(config)
}
pub fn app(mut self, app: &str) -> Self {
self.app = app.to_string();
self
}
pub fn tracing(mut self, config: Tracing) -> Self {
self.tracing = Some(config);
self
}
pub fn workers(mut self, workers: usize) -> Self {
self.workers = workers;
self
}
pub fn backlog(mut self, backlog: i32) -> Self {
self.backlog = backlog;
self
}
pub fn keep_alive(mut self, keep_alive: Seconds) -> Self {
self.keep_alive = keep_alive;
self
}
pub fn client_timeout(mut self, timeout: u16) -> Self {
self.client_timeout = Seconds(timeout);
self
}
pub fn client_disconnect(mut self, timeout: u16) -> Self {
self.client_disconnect = Seconds(timeout);
self
}
pub fn max_conn(mut self, max: usize) -> Self {
self.max_connections = max;
self
}
pub fn max_conn_rate(mut self, max: usize) -> Self {
self.max_connections_rate = max;
self
}
pub fn allowed_origins(mut self, allowed_origins: Vec<String>) -> Self {
self.allowed_origins = allowed_origins;
self
}
pub fn allowed_methods(mut self, allowed_methods: Vec<Method>) -> Self {
self.allowed_methods = allowed_methods;
self
}
#[cfg(feature = "static")]
pub fn static_config(mut self, static_config: StaticFileConfig) -> Self {
self.static_config = static_config;
self
}
pub fn route_factory<F: Fn() -> Vec<Route> + Send + Sync + 'static>(mut self, factory: F) -> Self {
self.route_factory = Arc::new(factory);
self
}
pub fn route_factory_arc(mut self, factory: Arc<dyn Fn() -> Vec<Route> + Send + Sync>) -> Self {
self.route_factory = factory;
self
}
#[deprecated(since = "0.32.0", note = "Use route_factory instead")]
pub fn boot_thread<F: Fn() -> Vec<Route> + Send + Sync + 'static>(self, factory: F) -> Self {
self.route_factory(factory)
}
pub fn has_started_bootstrap(mut self, has_started_bootstrap: bool) -> Self {
self.has_started_bootstrap = has_started_bootstrap;
self
}
pub fn body_config(mut self, body_config: BodyConfig) -> Self {
self.body_config = Some(body_config);
self
}
#[deprecated(since = "0.31.0", note = "Use body_config instead")]
pub fn json_config(self, config: BodyConfig) -> Self {
self.body_config(config)
}
pub fn custom_state_builder(
mut self,
builder: CustomStateBuilderFn,
) -> Self {
self.custom_state_builder = Some(builder);
self
}
pub fn on_shutdown<F>(mut self, func: F) -> Self
where
F: Future<Output = ()> + Send + 'static,
{
self.on_shutdown = Some(Box::pin(func));
self
}
pub fn shutdown_signal<F>(mut self, func: F) -> Self
where
F: Future<Output = ()> + Send + 'static,
{
self.shutdown_signal = Some(Box::pin(func));
self
}
pub fn validate(&self) -> foxtive::results::AppResult<()> {
use foxtive::internal_server_error;
if self.port == 0 {
return Err(internal_server_error!("Port cannot be 0"));
}
if self.workers == 0 {
return Err(internal_server_error!("Workers must be at least 1"));
}
if self.backlog < 0 {
return Err(internal_server_error!("Backlog cannot be negative"));
}
if self.max_connections == 0 {
return Err(internal_server_error!("Max connections must be at least 1"));
}
if self.max_connections_rate == 0 {
return Err(internal_server_error!(
"Max connection rate must be at least 1"
));
}
if self.client_timeout.0 > 300 {
tracing::warn!(
"Client timeout is very high: {} seconds. Consider reducing for better resource management.",
self.client_timeout.0
);
}
if self.keep_alive.0 > 300 {
tracing::warn!(
"Keep-alive timeout is very high: {} seconds. This may cause resource exhaustion under load.",
self.keep_alive.0
);
}
if self.workers > num_cpus::get() * 2 {
tracing::warn!(
"Worker count ({}) is more than 2x the available CPU cores ({}). This may degrade performance.",
self.workers,
num_cpus::get()
);
}
if self.backlog > 10000 {
tracing::warn!(
"Backlog ({}) is very high. This may cause memory issues under extreme load.",
self.backlog
);
}
Ok(())
}
pub fn dev_mode(host: &str, port: u16, setup: FoxtiveSetup) -> Self {
Self::create(host, port, setup)
.workers(1)
.client_timeout(60)
.keep_alive(Seconds(60))
.max_conn(1000)
.backlog(256)
}
pub fn production_mode(host: &str, port: u16, setup: FoxtiveSetup) -> Self {
let workers = num_cpus::get();
Self::create(host, port, setup)
.workers(workers)
.client_timeout(15)
.keep_alive(ntex::time::Seconds(30))
.max_conn(25_000)
.backlog(2048)
}
pub fn high_performance_mode(host: &str, port: u16, setup: FoxtiveSetup) -> Self {
let workers = num_cpus::get() * 2;
Self::create(host, port, setup)
.workers(workers)
.client_timeout(5)
.keep_alive(Seconds(10))
.max_conn(50_000)
.max_conn_rate(512)
.backlog(4096)
}
pub fn shutdown_config(mut self, config: ShutdownConfig) -> Self {
self.shutdown_config = Some(config);
self
}
pub fn register_shutdown_service<F, Fut>(mut self, name: &str, priority: u8, cleanup: F) -> Self
where
F: FnOnce() -> Fut + Send + 'static,
Fut: Future<Output = ()> + Send + 'static,
{
self.shutdown_registry.register(name, priority, cleanup);
self
}
pub async fn start<Callback, Fut>(self, callback: Callback) -> AppResult<()>
where
Callback: FnOnce(FoxtiveNtexState) -> Fut + Copy + Send + 'static,
Fut: Future<Output = AppResult<()>> + Send + 'static,
{
super::start_ntex_server(self, callback).await
}
pub async fn run(self) -> AppResult<()> {
self.start(|_state| async { Ok(()) }).await
}
}
#[cfg(feature = "static")]
impl Default for StaticFileConfig {
fn default() -> Self {
Self {
path: "static".to_string(),
dir: "./static".to_string(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_body_config_limits() {
let config = BodyConfig::default()
.json_limit(1024 * 1024)
.string_limit(512 * 1024)
.byte_limit(2 * 1024 * 1024);
assert_eq!(config.json_limit, 1_048_576);
assert_eq!(config.string_limit, 524_288);
assert_eq!(config.byte_limit, 2_097_152);
}
}