trillium_server_common/server.rs
1use crate::{RuntimeTrait, Swansong, Transport, UdpTransport};
2use listenfd::ListenFd;
3#[cfg(unix)]
4use std::os::unix::net::UnixListener;
5use std::{future::Future, io::Result, net::TcpListener};
6use trillium::Info;
7
8/// The server trait, for standard network-based server implementations.
9pub trait Server: Sized + Send + Sync + 'static {
10 /// the individual byte stream that http
11 /// will be communicated over. This is often an async "stream"
12 /// like TcpStream or UnixStream. See [`Transport`]
13 type Transport: Transport;
14
15 /// The [`RuntimeTrait`] for this `Server`.
16 type Runtime: RuntimeTrait;
17
18 /// The async UDP socket type for this server. Used by QUIC adapters
19 /// for HTTP/3 support. Runtimes that do not support UDP should set
20 /// this to `()`.
21 type UdpTransport: UdpTransport;
22
23 /// Asynchronously return a single `Self::Transport` from a
24 /// `Self::Listener`. Must be implemented.
25 fn accept(&mut self) -> impl Future<Output = Result<Self::Transport>> + Send;
26
27 /// Populate [`Info`] with data from this server, such as the bound socket address.
28 /// Called during server startup before any connections are accepted.
29 fn init(&self, info: &mut Info) {
30 let _ = info;
31 }
32
33 /// After the server has shut down, perform any housekeeping, eg
34 /// unlinking a unix socket.
35 fn clean_up(self) -> impl Future<Output = ()> + Send {
36 async {}
37 }
38
39 /// Return this `Server`'s `Runtime`
40 fn runtime() -> Self::Runtime;
41
42 /// Build a listener from the config. The default logic for this
43 /// is described elsewhere. To override the default logic, server
44 /// implementations could potentially implement this directly. To
45 /// use this default logic, implement
46 /// [`Server::from_tcp`] and
47 /// [`Server::from_unix`].
48 fn from_host_and_port(host: &str, port: u16) -> Self {
49 match resolve_listener(host, port).unwrap() {
50 PreboundListener::Tcp(tcp_listener) => Self::from_tcp(tcp_listener),
51 #[cfg(unix)]
52 PreboundListener::Unix(unix_listener) => Self::from_unix(unix_listener),
53 }
54 }
55
56 /// Build a Self::Listener from a tcp listener. This is called by
57 /// the [`Server::from_host_and_port`] default implementation, and
58 /// is mandatory if the default implementation is used.
59 fn from_tcp(tcp_listener: TcpListener) -> Self {
60 let _ = tcp_listener;
61 unimplemented!()
62 }
63
64 /// Build a `Self` from a unix listener. This is called by
65 /// the [`Server::from_host_and_port`] default implementation. You
66 /// will want to tag an implementation of this with `#[cfg(unix)]`.
67 #[cfg(unix)]
68 fn from_unix(unix_listener: UnixListener) -> Self {
69 let _ = unix_listener;
70 unimplemented!()
71 }
72
73 /// Implementation hook for listening for any os signals and
74 /// stopping the provided [`Swansong`]. The returned future will be
75 /// spawned using [`RuntimeTrait::spawn`]
76 fn handle_signals(_swansong: Swansong) -> impl Future<Output = ()> + Send {
77 async {}
78 }
79}
80
81/// A listener claimed by [`resolve_listener`] before it has been adopted into a runtime: either a
82/// freshly-bound (or inherited) TCP listener, or, on unix, a Unix-domain-socket listener.
83#[derive(Debug)]
84pub(crate) enum PreboundListener {
85 Tcp(TcpListener),
86 #[cfg(unix)]
87 Unix(UnixListener),
88}
89
90/// Resolve a `host`/`port` into a freshly-claimed std listener following trillium's 12-factor
91/// binding rules:
92///
93/// - on unix, a `host` beginning with `/`, `.`, or `~` is treated as a path and bound as a
94/// Unix-domain socket (the `port` is ignored);
95/// - otherwise, a listener inherited via the `LISTEN_FDS` socket-activation protocol at fd index 0
96/// (unix listener preferred, then TCP) is adopted if present;
97/// - otherwise a TCP listener is bound to `host:port`.
98///
99/// This is the fallible core of the default [`Server::from_host_and_port`] (which `.unwrap()`s the
100/// result, preserving its panic-on-failure contract) and of the multi-listener builder's `bind_env`
101/// (which propagates the error). TCP listeners are set non-blocking; the Unix path mirrors the
102/// historical `from_host_and_port` behavior and does not.
103pub(crate) fn resolve_listener(host: &str, port: u16) -> Result<PreboundListener> {
104 #[cfg(unix)]
105 if host.starts_with(['/', '.', '~']) {
106 log::debug!("using unix listener at {host}");
107 let unix_listener = UnixListener::bind(host)?;
108 log::debug!("listening at {:?}", unix_listener.local_addr());
109 return Ok(PreboundListener::Unix(unix_listener));
110 }
111
112 let mut listen_fd = ListenFd::from_env();
113
114 #[cfg(unix)]
115 if let Ok(Some(unix_listener)) = listen_fd.take_unix_listener(0) {
116 log::debug!(
117 "using unix listener from systemfd environment {:?}",
118 unix_listener.local_addr()
119 );
120 return Ok(PreboundListener::Unix(unix_listener));
121 }
122
123 let tcp_listener = match listen_fd.take_tcp_listener(0).ok().flatten() {
124 Some(tcp_listener) => {
125 log::debug!(
126 "using tcp listener from systemfd environment, listening at {:?}",
127 tcp_listener.local_addr()
128 );
129 tcp_listener
130 }
131 None => {
132 log::debug!("using tcp listener at {host}:{port}");
133 let tcp_listener = TcpListener::bind((host, port))?;
134 log::debug!("listening at {:?}", tcp_listener.local_addr());
135 tcp_listener
136 }
137 };
138
139 tcp_listener.set_nonblocking(true)?;
140 Ok(PreboundListener::Tcp(tcp_listener))
141}