trillium_server_common/config.rs
1use crate::{
2 server_handle::CompletionFuture, Acceptor, CloneCounterObserver, Server, ServerHandle, Stopper,
3};
4use async_cell::sync::AsyncCell;
5use std::{
6 marker::PhantomData,
7 net::SocketAddr,
8 sync::{Arc, RwLock},
9};
10use trillium::{Handler, HttpConfig, Info};
11
12/**
13# Primary entrypoint for configuring and running a trillium server
14
15The associated methods on this struct are intended to be chained.
16
17## Example
18```rust,no_run
19trillium_smol::config() // or trillium_async_std, trillium_tokio
20 .with_port(8080) // the default
21 .with_host("localhost") // the default
22 .with_nodelay()
23 .with_max_connections(Some(10000))
24 .without_signals()
25 .run(|conn: trillium::Conn| async move { conn.ok("hello") });
26```
27
28# Socket binding
29
30The socket binding logic is as follows:
31
32* If a LISTEN_FD environment variable is available on `cfg(unix)`
33 systems, that will be used, overriding host and port settings
34* Otherwise:
35 * Host will be selected from explicit configuration using
36 [`Config::with_host`] or else the `HOST` environment variable,
37 or else a default of "localhost".
38 * On `cfg(unix)` systems only: If the host string (as set by env var
39 or direct config) begins with `.`, `/`, or `~`, it is
40 interpreted to be a path, and trillium will bind to it as a unix
41 domain socket. Port will be ignored. The socket will be deleted
42 on clean shutdown.
43 * Port will be selected from explicit configuration using
44 [`Config::with_port`] or else the `PORT` environment variable,
45 or else a default of 8080.
46
47## Signals
48
49On `cfg(unix)` systems, `SIGTERM`, `SIGINT`, and `SIGQUIT` are all
50registered to perform a graceful shutdown on the first signal and an
51immediate shutdown on a subsequent signal. This behavior may change as
52trillium matures. To disable this behavior, use
53[`Config::without_signals`].
54
55## For runtime adapter authors
56
57In order to use this to _implement_ a trillium server, see
58[`trillium_server_common::ConfigExt`](crate::ConfigExt)
59*/
60
61#[derive(Debug)]
62pub struct Config<ServerType, AcceptorType> {
63 pub(crate) acceptor: AcceptorType,
64 pub(crate) port: Option<u16>,
65 pub(crate) host: Option<String>,
66 pub(crate) nodelay: bool,
67 pub(crate) stopper: Stopper,
68 pub(crate) observer: CloneCounterObserver,
69 pub(crate) register_signals: bool,
70 pub(crate) max_connections: Option<usize>,
71 pub(crate) info: Arc<AsyncCell<Info>>,
72 pub(crate) completion_future: CompletionFuture,
73 pub(crate) binding: RwLock<Option<ServerType>>,
74 pub(crate) server: PhantomData<ServerType>,
75 pub(crate) http_config: HttpConfig,
76}
77
78impl<ServerType, AcceptorType> Config<ServerType, AcceptorType>
79where
80 ServerType: Server,
81 AcceptorType: Acceptor<ServerType::Transport>,
82{
83 /// Starts an async runtime and runs the provided handler with
84 /// this config in that runtime. This is the appropriate
85 /// entrypoint for applications that do not need to spawn tasks
86 /// outside of trillium's web server. For applications that embed a
87 /// trillium server inside of an already-running async runtime, use
88 /// [`Config::run_async`]
89 pub fn run<H: Handler>(self, h: H) {
90 ServerType::run(self, h)
91 }
92
93 /// Runs the provided handler with this config, in an
94 /// already-running runtime. This is the appropriate entrypoint
95 /// for an application that needs to spawn async tasks that are
96 /// unrelated to the trillium application. If you do not need to spawn
97 /// other tasks, [`Config::run`] is the preferred entrypoint
98 pub async fn run_async(self, handler: impl Handler) {
99 let completion_future = self.completion_future.clone();
100 ServerType::run_async(self, handler).await;
101 completion_future.notify()
102 }
103
104 /// Spawns the server onto the async runtime, returning a
105 /// ServerHandle that can be awaited directly to return an
106 /// [`Info`] or used with [`ServerHandle::info`] and
107 /// [`ServerHandle::stop`]
108 pub fn spawn(self, handler: impl Handler) -> ServerHandle {
109 let server_handle = self.handle();
110 ServerType::spawn(self.run_async(handler));
111 server_handle
112 }
113
114 /// Returns a [`ServerHandle`] for this Config. This is useful
115 /// when spawning the server onto a runtime.
116 pub fn handle(&self) -> ServerHandle {
117 ServerHandle {
118 stopper: self.stopper.clone(),
119 info: self.info.clone(),
120 completion: self.completion_future.clone(),
121 observer: self.observer.clone(),
122 }
123 }
124
125 /// Configures the server to listen on this port. The default is
126 /// the PORT environment variable or 8080
127 pub fn with_port(mut self, port: u16) -> Self {
128 if self.has_binding() {
129 eprintln!("constructing a config with both a port and a pre-bound listener will ignore the port. this may be a panic in the future");
130 }
131 self.port = Some(port);
132 self
133 }
134
135 /// Configures the server to listen on this host or ip
136 /// address. The default is the HOST environment variable or
137 /// "localhost"
138 pub fn with_host(mut self, host: &str) -> Self {
139 if self.has_binding() {
140 eprintln!("constructing a config with both a host and a pre-bound listener will ignore the host. this may be a panic in the future");
141 }
142 self.host = Some(host.into());
143 self
144 }
145
146 /// Configures the server to NOT register for graceful-shutdown
147 /// signals with the operating system. Default behavior is for the
148 /// server to listen for SIGINT and SIGTERM and perform a graceful
149 /// shutdown.
150 pub fn without_signals(mut self) -> Self {
151 self.register_signals = false;
152 self
153 }
154
155 /// Configures the tcp listener to use TCP_NODELAY. See
156 /// <https://en.wikipedia.org/wiki/Nagle%27s_algorithm> for more
157 /// information on this setting.
158 pub fn with_nodelay(mut self) -> Self {
159 self.nodelay = true;
160 self
161 }
162
163 /// Configures the server to listen on the ip and port specified
164 /// by the provided socketaddr. This is identical to
165 /// `self.with_host(&socketaddr.ip().to_string()).with_port(socketaddr.port())`
166 pub fn with_socketaddr(self, socketaddr: SocketAddr) -> Self {
167 self.with_host(&socketaddr.ip().to_string())
168 .with_port(socketaddr.port())
169 }
170
171 /// Configures the tls acceptor for this server
172 pub fn with_acceptor<A: Acceptor<ServerType::Transport>>(
173 self,
174 acceptor: A,
175 ) -> Config<ServerType, A> {
176 Config {
177 acceptor,
178 host: self.host,
179 port: self.port,
180 nodelay: self.nodelay,
181 server: PhantomData,
182 stopper: self.stopper,
183 observer: self.observer,
184 register_signals: self.register_signals,
185 max_connections: self.max_connections,
186 info: self.info,
187 completion_future: self.completion_future,
188 binding: self.binding,
189 http_config: self.http_config,
190 }
191 }
192
193 /// use the specific [`Stopper`] provided
194 pub fn with_stopper(mut self, stopper: Stopper) -> Self {
195 self.stopper = stopper;
196 self
197 }
198
199 /// use the specified [`CloneCounterObserver`] to monitor or
200 /// modify the outstanding connection count for graceful shutdown
201 pub fn with_observer(mut self, observer: CloneCounterObserver) -> Self {
202 self.observer = observer;
203 self
204 }
205
206 /**
207 Configures the maximum number of connections to accept. The
208 default is 75% of the soft rlimit_nofile (`ulimit -n`) on unix
209 systems, and None on other sytems.
210 */
211 pub fn with_max_connections(mut self, max_connections: Option<usize>) -> Self {
212 self.max_connections = max_connections;
213 self
214 }
215
216 /// configures trillium-http performance and security tuning parameters.
217 ///
218 /// See [`HttpConfig`] for documentation
219 pub fn with_http_config(mut self, http_config: HttpConfig) -> Self {
220 self.http_config = http_config;
221 self
222 }
223
224 /// Use a pre-bound transport stream as server.
225 ///
226 /// The argument to this varies for different servers, but usually
227 /// accepts the runtime's TcpListener and, on unix platforms, the UnixListener.
228 ///
229 /// ## Note well
230 ///
231 /// Many of the other options on this config will be ignored if you provide a listener. In
232 /// particular, `host` and `port` will be ignored. All of the other options will be used.
233 ///
234 /// Additionally, cloning this config will not clone the listener.
235 pub fn with_prebound_server(mut self, server: impl Into<ServerType>) -> Self {
236 if self.host.is_some() {
237 eprintln!("constructing a config with both a host and a pre-bound listener will ignore the host. this may be a panic in the future");
238 }
239
240 if self.port.is_some() {
241 eprintln!("constructing a config with both a port and a pre-bound listener will ignore the port. this may be a panic in the future");
242 }
243
244 self.binding = RwLock::new(Some(server.into()));
245 self
246 }
247
248 fn has_binding(&self) -> bool {
249 self.binding
250 .read()
251 .as_deref()
252 .map_or(false, Option::is_some)
253 }
254}
255
256impl<ServerType: Server> Config<ServerType, ()> {
257 /// build a new config with default acceptor
258 pub fn new() -> Self {
259 Self::default()
260 }
261}
262
263impl<ServerType, AcceptorType> Clone for Config<ServerType, AcceptorType>
264where
265 ServerType: Server,
266 AcceptorType: Acceptor<ServerType::Transport> + Clone,
267{
268 fn clone(&self) -> Self {
269 if self.has_binding() {
270 eprintln!("cloning a Config with a pre-bound listener will not clone the listener. this may be a panic in the future.");
271 }
272
273 Self {
274 acceptor: self.acceptor.clone(),
275 port: self.port,
276 host: self.host.clone(),
277 server: PhantomData,
278 nodelay: self.nodelay,
279 stopper: self.stopper.clone(),
280 observer: self.observer.clone(),
281 register_signals: self.register_signals,
282 max_connections: self.max_connections,
283 info: AsyncCell::shared(),
284 completion_future: CompletionFuture::new(),
285 binding: RwLock::new(None),
286 http_config: self.http_config,
287 }
288 }
289}
290
291impl<ServerType: Server> Default for Config<ServerType, ()> {
292 fn default() -> Self {
293 #[cfg(unix)]
294 let max_connections = {
295 rlimit::getrlimit(rlimit::Resource::NOFILE)
296 .ok()
297 .and_then(|(soft, _hard)| soft.try_into().ok())
298 .map(|limit: usize| 3 * limit / 4)
299 };
300
301 #[cfg(not(unix))]
302 let max_connections = None;
303
304 log::debug!("using max connections of {:?}", max_connections);
305
306 Self {
307 acceptor: (),
308 port: None,
309 host: None,
310 server: PhantomData,
311 nodelay: false,
312 stopper: Stopper::new(),
313 observer: CloneCounterObserver::new(),
314 register_signals: cfg!(unix),
315 max_connections,
316 info: AsyncCell::shared(),
317 completion_future: CompletionFuture::new(),
318 binding: RwLock::new(None),
319 http_config: HttpConfig::default(),
320 }
321 }
322}