Skip to main content

foxtive_ntex/http/server/
config.rs

1use crate::http::Method;
2use crate::http::kernel::Route;
3use foxtive::setup::FoxtiveSetup;
4use foxtive::setup::trace::Tracing;
5use ntex::time::Seconds;
6use std::pin::Pin;
7use std::sync::Arc;
8
9pub type ShutdownSignalHandler = Pin<Box<dyn Future<Output = ()> + Send + 'static>>;
10
11/// Configuration for serving static files.
12///
13/// This struct defines the mapping between URL paths and filesystem directories
14/// for static file serving. Only available when the `static` feature is enabled.
15///
16/// # Example
17/// ```rust
18/// use foxtive_ntex::http::server::StaticFileConfig;
19///
20/// let config = StaticFileConfig {
21///     path: "/assets".to_string(),
22///     dir: "./public".to_string(),
23/// };
24///
25/// // This would serve files from "./public" directory at "/assets/*" URL path
26/// // e.g., "./public/style.css" would be accessible at "/assets/style.css"
27/// ```
28///
29/// # Security Notes
30/// - The `dir` path should be carefully validated to prevent directory traversal attacks
31/// - Consider using absolute paths or canonicalized paths for the `dir` field
32/// - Ensure proper file permissions are set on the served directory
33#[cfg(feature = "static")]
34pub struct StaticFileConfig {
35    /// The URL path prefix where static files will be served.
36    ///
37    /// This defines the base route under which static files are accessible.
38    /// Should start with "/" (e.g., "/static", "/assets", "/public").
39    pub path: String,
40
41    /// The filesystem directory path containing the static files to serve.
42    ///
43    /// This can be either a relative path (relative to the application's working directory)
44    /// or an absolute path. All files within this directory and its subdirectories
45    /// will be served under the configured URL path.
46    pub dir: String,
47}
48
49/// Configuration for JSON request body parsing.
50///
51/// This struct controls how JSON payloads are processed, including size limits
52/// and content-type validation.
53///
54/// # Default Settings
55/// - Maximum body size: 512,000 bytes (500 KB)
56/// - Content-type validation: None (accepts any content-type)
57///
58/// # Example
59/// ```rust
60/// use foxtive_ntex::http::server::JsonConfig;
61///
62/// // Use default configuration (50 KB limit)
63/// let config = JsonConfig::default();
64///
65/// // Custom size limit
66/// let config = JsonConfig::default().limit(1024 * 1024); // 1 MB
67/// ```
68#[derive(Clone)]
69pub struct JsonConfig {
70    /// Maximum allowed size for JSON request bodies in bytes.
71    ///
72    /// Requests exceeding this limit will be rejected with a payload too large error.
73    /// Default: 51,200 bytes (50 KB)
74    pub(crate) limit: usize,
75}
76
77pub struct ServerConfig {
78    pub(crate) host: String,
79    pub(crate) port: u16,
80    pub(crate) workers: usize,
81
82    pub(crate) max_connections: usize,
83
84    pub(crate) max_connections_rate: usize,
85
86    pub(crate) client_timeout: Seconds,
87
88    pub(crate) client_disconnect: Seconds,
89
90    pub(crate) keep_alive: Seconds,
91
92    pub(crate) backlog: i32,
93
94    pub(crate) json_config: Option<JsonConfig>,
95
96    pub(crate) app: String,
97    pub(crate) foxtive_setup: FoxtiveSetup,
98
99    pub(crate) tracing: Option<Tracing>,
100
101    #[cfg(feature = "static")]
102    pub(crate) static_config: StaticFileConfig,
103
104    /// whether the app bootstrap has started
105    pub(crate) has_started_bootstrap: bool,
106
107    /// list of allowed CORS origins
108    pub(crate) allowed_origins: Vec<String>,
109
110    /// list of allowed CORS origins
111    pub(crate) allowed_methods: Vec<Method>,
112
113    pub(crate) boot_thread: Arc<dyn Fn() -> Vec<Route> + Send + Sync>,
114
115    pub(crate) on_shutdown: Option<ShutdownSignalHandler>,
116
117    pub(crate) shutdown_signal: Option<Pin<Box<dyn Future<Output = ()> + Send>>>,
118}
119
120impl ServerConfig {
121    pub fn create(host: &str, port: u16, setup: FoxtiveSetup) -> ServerConfig {
122        ServerConfig {
123            host: host.to_string(),
124            port,
125            workers: 2,
126            max_connections: 25_000,
127            max_connections_rate: 256,
128            client_timeout: Seconds(3),
129            client_disconnect: Seconds(5),
130            keep_alive: Seconds(5),
131            backlog: 2048,
132            app: "foxtive".to_string(),
133            foxtive_setup: setup,
134            #[cfg(feature = "static")]
135            static_config: StaticFileConfig::default(),
136            has_started_bootstrap: false,
137            allowed_origins: vec![],
138            allowed_methods: vec![],
139            boot_thread: Arc::new(Vec::new),
140            tracing: None,
141            json_config: None,
142            on_shutdown: None,
143            shutdown_signal: None,
144        }
145    }
146
147    #[cfg(feature = "static")]
148    pub fn create_with_static(
149        host: &str,
150        port: u16,
151        setup: FoxtiveSetup,
152        config: StaticFileConfig,
153    ) -> ServerConfig {
154        Self::create(host, port, setup).static_config(config)
155    }
156
157    pub fn app(mut self, app: &str) -> Self {
158        self.app = app.to_string();
159        self
160    }
161
162    pub fn tracing(mut self, config: Tracing) -> Self {
163        self.tracing = Some(config);
164        self
165    }
166
167    /// Set number of workers to start.
168    ///
169    /// By default http server uses 2
170    pub fn workers(mut self, workers: usize) -> Self {
171        self.workers = workers;
172        self
173    }
174
175    /// Set the maximum number of pending connections.
176    ///
177    /// This refers to the number of clients that can be waiting to be served.
178    /// Exceeding this number results in the client getting an error when
179    /// attempting to connect. It should only affect servers under significant
180    /// load.
181    ///
182    /// Generally set in the 64-2048 range. Default value is 2048.
183    ///
184    /// This method should be called before `bind()` method call.
185    pub fn backlog(mut self, backlog: i32) -> Self {
186        self.backlog = backlog;
187        self
188    }
189
190    /// Set server keep-alive setting.
191    ///
192    /// By default keep alive is set to a 5 seconds.
193    pub fn keep_alive(mut self, keep_alive: Seconds) -> Self {
194        self.keep_alive = keep_alive;
195        self
196    }
197
198    /// Set request read timeout in seconds.
199    ///
200    /// Defines a timeout for reading client request headers. If a client does not transmit
201    /// the entire set headers within this time, the request is terminated with
202    /// the 408 (Request Time-out) error.
203    ///
204    /// To disable timeout set value to 0.
205    ///
206    /// By default client timeout is set to 3 seconds.
207    pub fn client_timeout(mut self, timeout: u16) -> Self {
208        self.client_timeout = Seconds(timeout);
209        self
210    }
211
212    /// Set server connection disconnect timeout in seconds.
213    ///
214    /// Defines a timeout for shutdown connection. If a shutdown procedure does not complete
215    /// within this time, the request is dropped.
216    ///
217    /// To disable timeout set value to 0.
218    ///
219    /// By default client timeout is set to 5 seconds.
220    pub fn client_disconnect(mut self, timeout: u16) -> Self {
221        self.client_disconnect = Seconds(timeout);
222        self
223    }
224
225    /// Sets the maximum per-worker number of concurrent connections.
226    ///
227    /// All socket listeners will stop accepting connections when this limit is reached
228    /// for each worker.
229    ///
230    /// By default max connections is set to a 25k.
231    pub fn max_conn(mut self, max: usize) -> Self {
232        self.max_connections = max;
233        self
234    }
235
236    /// Sets the maximum per-worker concurrent connection establish process.
237    ///
238    /// All listeners will stop accepting connections when this limit is reached. It
239    /// can be used to limit the global SSL CPU usage.
240    ///
241    /// By default max connections is set to a 256.
242    pub fn max_conn_rate(mut self, max: usize) -> Self {
243        self.max_connections_rate = max;
244        self
245    }
246
247    pub fn allowed_origins(mut self, allowed_origins: Vec<String>) -> Self {
248        self.allowed_origins = allowed_origins;
249        self
250    }
251
252    pub fn allowed_methods(mut self, allowed_methods: Vec<Method>) -> Self {
253        self.allowed_methods = allowed_methods;
254        self
255    }
256
257    #[cfg(feature = "static")]
258    pub fn static_config(mut self, static_config: StaticFileConfig) -> Self {
259        self.static_config = static_config;
260        self
261    }
262
263    pub fn boot_thread(mut self, boot_thread: Arc<dyn Fn() -> Vec<Route> + Send + Sync>) -> Self {
264        self.boot_thread = boot_thread;
265        self
266    }
267
268    pub fn has_started_bootstrap(mut self, has_started_bootstrap: bool) -> Self {
269        self.has_started_bootstrap = has_started_bootstrap;
270        self
271    }
272
273    pub fn json_config(mut self, json_config: JsonConfig) -> Self {
274        self.json_config = Some(json_config);
275        self
276    }
277
278    /// Sets a custom shutdown handler to be called when the application is shutting down.
279    ///
280    /// This method allows you to provide a future that will be awaited during shutdown.
281    /// It is typically used to perform cleanup tasks like closing database connections,
282    /// flushing logs, or other async teardown operations.
283    ///
284    /// **Note:** If a custom `shutdown_signal` is also provided using [`shutdown_signal`],
285    /// that will take precedence over this handler, and this `on_shutdown` handler will
286    /// **not** be executed.
287    ///
288    pub fn on_shutdown<F>(mut self, func: F) -> Self
289    where
290        F: Future<Output = ()> + Send + 'static,
291    {
292        self.on_shutdown = Some(Box::pin(func));
293        self
294    }
295
296    /// Sets a custom shutdown signal handler that determines when the application should begin shutting down.
297    ///
298    /// This method allows you to provide a future that, when resolved, triggers the application shutdown.
299    /// It is typically used to listen for signals like `Ctrl+C` or system termination requests (`SIGTERM`).
300    ///
301    /// If this shutdown signal is provided, it will override any handler set using [`on_shutdown`].
302    pub fn shutdown_signal<F>(mut self, func: F) -> Self
303    where
304        F: Future<Output = ()> + Send + 'static,
305    {
306        self.shutdown_signal = Some(Box::pin(func));
307        self
308    }
309}
310
311impl JsonConfig {
312    /// Change max size of payload. By default max size is 50Kb
313    pub fn limit(mut self, limit: usize) -> Self {
314        self.limit = limit;
315        self
316    }
317}
318
319#[cfg(feature = "static")]
320impl Default for StaticFileConfig {
321    fn default() -> Self {
322        Self {
323            path: "static".to_string(),
324            dir: "./static".to_string(),
325        }
326    }
327}
328
329impl Default for JsonConfig {
330    fn default() -> Self {
331        JsonConfig {
332            limit: 51_000, // 50 KB
333        }
334    }
335}