Skip to main content

ntex_server/net/
builder.rs

1use std::{fmt, io, net, sync::Arc};
2
3use ntex_io::Io;
4use ntex_rt::System;
5use ntex_service::{IntoService, Service, cfg::SharedCfg};
6use ntex_util::time::Millis;
7use socket2::{Domain, SockAddr, Socket, Type};
8
9use crate::{NoConfig, Server, ServerAppConfig, WorkerPool};
10
11use super::accept::AcceptLoop;
12use super::config::ServiceConfig;
13use super::factory::{self, FactoryServiceType};
14use super::{Connection, ServerStatus, StreamServer, Token, socket::Listener};
15
16/// Builder for a network server.
17///
18/// Register listeners and their service factories, configure the worker pool,
19/// and call [`run`](Self::run) to start the server.
20pub struct ServerBuilder<Cfg = NoConfig> {
21    name: String,
22    token: Token,
23    backlog: i32,
24    state: Arc<Cfg>,
25    services: Vec<FactoryServiceType<Cfg>>,
26    sockets: Vec<(Token, String, Listener)>,
27    accept: AcceptLoop,
28    pool: WorkerPool,
29}
30
31impl Default for ServerBuilder {
32    fn default() -> Self {
33        Self::new(NoConfig)
34    }
35}
36
37impl<Cfg> ServerBuilder<Cfg>
38where
39    Cfg: ServerAppConfig,
40{
41    #[must_use]
42    /// Creates a server builder with the specified application configuration.
43    pub fn new(cfg: Cfg) -> ServerBuilder<Cfg> {
44        let sys = System::current();
45        let mut accept = AcceptLoop::default();
46        accept.name(sys.name());
47        if sys.testing() {
48            accept.testing();
49        }
50
51        ServerBuilder {
52            accept,
53            name: sys.name().to_string(),
54            token: Token(0),
55            state: Arc::new(cfg),
56            services: Vec::new(),
57            sockets: Vec::new(),
58            backlog: 2048,
59            pool: WorkerPool::default().name(sys.name()),
60        }
61    }
62
63    #[must_use]
64    /// Sets the server name.
65    ///
66    /// The name is also used for worker threads.
67    pub fn name<T: AsRef<str>>(mut self, name: T) -> Self {
68        self.name = name.as_ref().to_string();
69        self.accept.name(self.name.as_str());
70        self.pool = self.pool.name(self.name.as_str());
71        self
72    }
73
74    #[must_use]
75    /// Sets the number of worker threads to start.
76    ///
77    /// By default, the server uses the number of available logical CPUs.
78    pub fn workers(mut self, num: usize) -> Self {
79        self.pool = self.pool.workers(num);
80        self
81    }
82
83    #[must_use]
84    /// Set the maximum number of pending connections.
85    ///
86    /// This refers to the number of clients that can be waiting to be served.
87    /// Exceeding this number results in the client getting an error when
88    /// attempting to connect. It should only affect servers under significant
89    /// load.
90    ///
91    /// Generally set in the 64-2048 range. Default value is 2048.
92    ///
93    /// This method should be called before `bind()` method call.
94    pub fn backlog(mut self, num: i32) -> Self {
95        self.backlog = num;
96        self
97    }
98
99    #[must_use]
100    /// Sets the maximum per-worker number of concurrent connections.
101    ///
102    /// All socket listeners will stop accepting connections when this limit is
103    /// reached for each worker.
104    ///
105    /// The default is 25,600 connections per worker.
106    pub fn maxconn(self, num: usize) -> Self {
107        super::max_concurrent_connections(num);
108        self
109    }
110
111    #[must_use]
112    /// Stops the current ntex runtime when the server is dropped.
113    ///
114    /// By default "stop runtime" is disabled.
115    pub fn stop_runtime(mut self) -> Self {
116        self.pool = self.pool.stop_runtime();
117        self
118    }
119
120    #[must_use]
121    /// Stops the server when one of the workers panics.
122    ///
123    /// By default, "stop on panic" is disabled.
124    pub fn stop_on_panic(mut self) -> Self {
125        self.pool = self.pool.stop_on_panic();
126        self
127    }
128
129    #[must_use]
130    /// Disable signal handling.
131    ///
132    /// By default, signal handling is enabled.
133    pub fn disable_signals(mut self) -> Self {
134        self.pool = self.pool.disable_signals();
135        self
136    }
137
138    #[must_use]
139    /// Enables CPU affinity for worker threads.
140    ///
141    /// By default, affinity is disabled.
142    pub fn enable_affinity(mut self) -> Self {
143        self.pool = self.pool.enable_affinity();
144        self
145    }
146
147    #[must_use]
148    /// Graceful shutdown.
149    ///
150    /// Gracefully shuts down on SIGSEGV or SIGQUIT and app panics.
151    /// Graceful shutdown is always enabled for SIGTERM.
152    /// By default, it is disabled for SIGSEGV and SIGQUIT and panics.
153    pub fn graceful_shutdown(mut self) -> Self {
154        self.pool = self.pool.graceful_shutdown();
155        self
156    }
157
158    #[must_use]
159    /// Timeout for graceful worker shutdown.
160    ///
161    /// After receiving a stop signal, workers have this much time to finish
162    /// serving requests. Workers that are still alive after the timeout are
163    /// forcefully dropped.
164    ///
165    /// By default, the shutdown timeout is set to 30 seconds.
166    pub fn shutdown_timeout<T: Into<Millis>>(mut self, timeout: T) -> Self {
167        self.pool = self.pool.shutdown_timeout(timeout);
168        self
169    }
170
171    #[must_use]
172    /// Sets the server status handler.
173    ///
174    /// The server calls this handler on every internal status update.
175    pub fn status_handler<F>(mut self, handler: F) -> Self
176    where
177        F: FnMut(ServerStatus) + Send + 'static,
178    {
179        self.accept.set_status_handler(handler);
180        self
181    }
182
183    /// Executes asynchronous configuration as part of the server building
184    /// process.
185    ///
186    /// This function is useful for moving parts of configuration to a
187    /// different module or even library.
188    pub async fn configure<F>(mut self, f: F) -> io::Result<Self>
189    where
190        F: AsyncFnOnce(ServiceConfig<Cfg>) -> io::Result<()>,
191    {
192        let cfg = ServiceConfig::new(self.token, self.backlog);
193
194        f(cfg.clone()).await?;
195
196        let (token, sockets, factory) = cfg.into_factory();
197        self.token = token;
198        self.sockets.extend(sockets);
199        self.services.push(factory);
200
201        Ok(self)
202    }
203
204    #[allow(clippy::needless_pass_by_value)]
205    /// Binds TCP listeners and registers a service factory.
206    pub fn bind<F, S, I>(
207        mut self,
208        name: impl AsRef<str>,
209        addr: impl net::ToSocketAddrs,
210        cfg: impl Into<SharedCfg>,
211        factory: F,
212    ) -> io::Result<Self>
213    where
214        F: AsyncFn(&Cfg::State) -> I + Send + Clone + 'static,
215        S: Service<Cfg::State, Io> + 'static,
216        I: IntoService<S, Cfg::State, Io> + 'static,
217    {
218        let cfg = cfg.into();
219        let sockets = bind_addr(addr, self.backlog)?;
220
221        let mut tokens = Vec::new();
222        for lst in sockets {
223            let token = self.token.next();
224            self.sockets
225                .push((token, name.as_ref().to_string(), Listener::from_tcp(lst)));
226            tokens.push((token, cfg.clone()));
227        }
228
229        self.services.push(factory::create_factory_service(
230            name.as_ref().to_string(),
231            tokens,
232            factory,
233        ));
234
235        Ok(self)
236    }
237
238    #[cfg(unix)]
239    /// Binds a Unix domain socket and registers a service factory.
240    pub fn bind_uds<F, I, S>(
241        self,
242        name: impl AsRef<str>,
243        addr: impl AsRef<std::path::Path>,
244        cfg: impl Into<SharedCfg>,
245        factory: F,
246    ) -> io::Result<Self>
247    where
248        F: AsyncFn(&Cfg::State) -> I + Send + Clone + 'static,
249        I: IntoService<S, Cfg::State, Io> + 'static,
250        S: Service<Cfg::State, Io> + 'static,
251    {
252        use std::os::unix::net::UnixListener;
253
254        // The path must not exist when we try to bind.
255        // Try to remove it to avoid bind error.
256        if let Err(e) = std::fs::remove_file(addr.as_ref()) {
257            // NotFound is expected and not an issue. Anything else is.
258            if e.kind() != std::io::ErrorKind::NotFound {
259                return Err(e);
260            }
261        }
262
263        let lst = UnixListener::bind(addr)?;
264        self.listen_uds(name, lst, cfg.into(), factory)
265    }
266
267    #[cfg(unix)]
268    /// Registers a service factory for an existing Unix domain listener.
269    ///
270    /// This is useful for socket activation, including listeners acquired
271    /// through systemd.
272    pub fn listen_uds<F, I, S>(
273        mut self,
274        name: impl AsRef<str>,
275        lst: std::os::unix::net::UnixListener,
276        cfg: impl Into<SharedCfg>,
277        factory: F,
278    ) -> io::Result<Self>
279    where
280        F: AsyncFn(&Cfg::State) -> I + Send + Clone + 'static,
281        I: IntoService<S, Cfg::State, Io> + 'static,
282        S: Service<Cfg::State, Io> + 'static,
283    {
284        let token = self.token.next();
285        self.services.push(factory::create_factory_service(
286            name.as_ref().to_string(),
287            vec![(token, cfg.into())],
288            factory,
289        ));
290        self.sockets
291            .push((token, name.as_ref().to_string(), Listener::from_uds(lst)));
292        Ok(self)
293    }
294
295    /// Registers a service factory for an existing TCP listener.
296    pub fn listen<F, S, I>(
297        mut self,
298        name: impl AsRef<str>,
299        lst: net::TcpListener,
300        cfg: impl Into<SharedCfg>,
301        factory: F,
302    ) -> io::Result<Self>
303    where
304        F: AsyncFn(&Cfg::State) -> I + Send + Clone + 'static,
305        S: Service<Cfg::State, Io> + 'static,
306        I: IntoService<S, Cfg::State, Io> + 'static,
307    {
308        let token = self.token.next();
309        self.services.push(factory::create_factory_service(
310            name.as_ref().to_string(),
311            vec![(token, cfg.into())],
312            factory,
313        ));
314        self.sockets
315            .push((token, name.as_ref().to_string(), Listener::from_tcp(lst)));
316        Ok(self)
317    }
318
319    /// Starts processing incoming connections and returns a server controller.
320    pub fn run(self) -> Server<Connection> {
321        assert!(
322            !self.sockets.is_empty(),
323            "Server should have at least one bound socket"
324        );
325        let srv = StreamServer::new(self.accept.notify(), self.state, self.services);
326        let svc = self.pool.run(srv);
327
328        let sockets = self
329            .sockets
330            .into_iter()
331            .map(|sock| {
332                log::info!("Starting \"{}\" service on {}", sock.1, sock.2);
333                (sock.0, sock.2)
334            })
335            .collect();
336        self.accept.start(sockets, svc.clone());
337
338        svc
339    }
340}
341
342impl<Cfg> fmt::Debug for ServerBuilder<Cfg> {
343    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
344        f.debug_struct("ServerBuilder")
345            .field("name", &self.name)
346            .field("token", &self.token)
347            .field("backlog", &self.backlog)
348            .field("sockets", &self.sockets)
349            .field("accept", &self.accept)
350            .field("worker-pool", &self.pool)
351            .finish()
352    }
353}
354
355/// Binds TCP listeners for every address resolved from `addr`.
356pub fn bind_addr<S: net::ToSocketAddrs>(
357    addr: S,
358    backlog: i32,
359) -> io::Result<Vec<net::TcpListener>> {
360    let mut err = None;
361    let mut succ = false;
362    let mut sockets = Vec::new();
363    for addr in addr.to_socket_addrs()? {
364        match create_tcp_listener(addr, backlog) {
365            Ok(lst) => {
366                succ = true;
367                sockets.push(lst);
368            }
369            Err(e) => err = Some(e),
370        }
371    }
372
373    if succ {
374        Ok(sockets)
375    } else if let Some(e) = err.take() {
376        Err(e)
377    } else {
378        Err(io::Error::new(
379            io::ErrorKind::InvalidInput,
380            "Cannot bind to address.",
381        ))
382    }
383}
384
385/// Creates and binds a TCP listener with the specified listen backlog.
386pub fn create_tcp_listener(addr: net::SocketAddr, backlog: i32) -> io::Result<net::TcpListener> {
387    let builder = match addr {
388        net::SocketAddr::V4(_) => Socket::new(Domain::IPV4, Type::STREAM, None)?,
389        net::SocketAddr::V6(_) => Socket::new(Domain::IPV6, Type::STREAM, None)?,
390    };
391
392    // On Windows, this allows rebinding sockets which are actively in use,
393    // which allows “socket hijacking”, so we explicitly don't set it here.
394    // https://docs.microsoft.com/en-us/windows/win32/winsock/using-so-reuseaddr-and-so-exclusiveaddruse
395    #[cfg(not(windows))]
396    builder.set_reuse_address(true)?;
397
398    builder.bind(&SockAddr::from(addr))?;
399    builder.listen(backlog)?;
400    Ok(net::TcpListener::from(builder))
401}
402
403#[cfg(test)]
404mod tests {
405    use super::*;
406
407    #[test]
408    fn test_bind_addr() {
409        let addrs: Vec<net::SocketAddr> = Vec::new();
410        assert!(bind_addr(&addrs[..], 10).is_err());
411    }
412
413    #[ntex::test]
414    async fn test_debug() {
415        let builder = ServerBuilder::default();
416        assert!(format!("{builder:?}").contains("ServerBuilder"));
417    }
418}