Skip to main content

ntex_server/net/
builder.rs

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