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