static_web_server/server/
mod.rs1use std::net::TcpListener;
9use std::sync::Arc;
10use tokio::sync::watch::Receiver;
11
12use crate::handler::RequestHandler;
13use crate::service::RouterService;
14use crate::{Context, Result, Settings};
15
16mod http1;
17mod listener;
18mod opts;
19
20#[cfg(feature = "tls")]
21mod http1_tls;
22#[cfg(feature = "http2")]
23mod http2;
24#[cfg(feature = "tls")]
25mod redirect;
26#[cfg(unix)]
27mod uds;
28
29#[cfg(feature = "tls")]
31pub(crate) struct TlsConfig {
32 pub tls_cert: std::path::PathBuf,
34 pub tls_key: std::path::PathBuf,
36 pub https_redirect: bool,
38 pub https_redirect_host: String,
40 pub https_redirect_from_port: u16,
42 pub https_redirect_from_hosts: String,
44 pub host: String,
46 pub port: u16,
48 pub page404: std::path::PathBuf,
50 pub page50x: std::path::PathBuf,
52}
53
54pub(crate) struct ShutdownCtx {
57 pub grace_period: u8,
59 pub cancel_recv: Option<Receiver<()>>,
61 #[cfg(windows)]
62 pub windows_service: bool,
64 #[cfg(windows)]
65 pub ctrl_c_recv: Receiver<()>,
67 #[cfg(windows)]
68 pub ctrlc_task: tokio::task::JoinHandle<crate::Result<()>>,
70}
71
72pub struct Server {
74 opts: Settings,
75 worker_threads: usize,
76 max_blocking_threads: usize,
77 pre_bound_listener: Option<(TcpListener, String)>,
81}
82
83impl Server {
84 pub fn new(opts: Settings) -> Result<Server> {
86 let cpus = std::thread::available_parallelism()
87 .with_context(|| {
88 "unable to get current platform cpus or lack of permissions to query available parallelism"
89 })?
90 .get();
91 let worker_threads = match opts.general.threads_multiplier {
92 0 | 1 => cpus,
93 n => cpus * n,
94 };
95 let max_blocking_threads = opts.general.max_blocking_threads;
96 Ok(Server {
97 opts,
98 worker_threads,
99 max_blocking_threads,
100 pre_bound_listener: None,
101 })
102 }
103
104 pub fn with_pre_bound_listener(mut self, listener: std::net::TcpListener) -> Self {
109 let addr = listener
110 .local_addr()
111 .map(|a| a.to_string())
112 .unwrap_or_else(|_| "pre-bound".into());
113 self.pre_bound_listener = Some((listener, addr));
114 self
115 }
116
117 pub fn run_standalone(self, cancel: Option<Receiver<()>>) -> Result {
124 self.run_server_on_rt(cancel, || {}, true)
125 }
126
127 #[cfg(windows)]
135 pub fn run_as_service<F>(self, cancel: Option<Receiver<()>>, cancel_fn: F) -> Result
136 where
137 F: FnOnce(),
138 {
139 self.run_server_on_rt(cancel, cancel_fn, true)
140 }
141
142 pub fn run_server_on_rt<F>(
147 self,
148 cancel_recv: Option<Receiver<()>>,
149 cancel_fn: F,
150 exit_on_error: bool,
151 ) -> Result
152 where
153 F: FnOnce(),
154 {
155 tracing::debug!(
156 %self.worker_threads,
157 "initializing tokio runtime with multi-threaded scheduler"
158 );
159
160 let rt = tokio::runtime::Builder::new_multi_thread()
161 .worker_threads(self.worker_threads)
162 .max_blocking_threads(self.max_blocking_threads)
163 .thread_name("static-web-server")
164 .enable_all()
165 .build()?;
166
167 let res = rt.block_on(async {
168 tracing::trace!("tokio runtime initialized");
169 self.start_server(cancel_recv, cancel_fn).await
170 });
171
172 if let Err(err) = &res {
173 tracing::error!("server failed to start up: {:?}", err);
174 if exit_on_error {
175 std::process::exit(1)
176 }
177 }
178 res
179 }
180
181 async fn start_server<F>(self, cancel_recv: Option<Receiver<()>>, cancel_fn: F) -> Result
186 where
187 F: FnOnce(),
188 {
189 tracing::trace!("starting web server");
190 tracing::info!(
191 name = env!("CARGO_PKG_NAME"),
192 version = env!("CARGO_PKG_VERSION"),
193 "starting Static Web Server"
194 );
195
196 let general = self.opts.general;
197 let advanced = self.opts.advanced;
198 let pre_bound = self.pre_bound_listener;
199
200 tracing::info!(log_level = %general.log_level, "log level");
201 if general.config_file.is_file() {
202 tracing::info!(path = %general.config_file.display(), "config file used");
203 } else {
204 tracing::debug!(
205 "config file path not found or not a regular file: {}",
206 general.config_file.display()
207 );
208 }
209
210 #[cfg(unix)]
215 let unix_listener_info = if let Some(path) = general.unix_socket.as_ref() {
216 use crate::server::listener::create_unix_listener;
217
218 Some(create_unix_listener(
219 path,
220 general.unix_socket_mode,
221 general.unix_socket_force,
222 )?)
223 } else {
224 None
225 };
226
227 #[cfg(unix)]
233 let tcp_listener_info = if unix_listener_info.is_none() {
234 Some(match pre_bound {
235 Some(pre) => pre,
236 None => crate::server::listener::create_tcp_listener(&general)?,
237 })
238 } else {
239 None
240 };
241 #[cfg(not(unix))]
242 let tcp_listener_info = Some(match pre_bound {
243 Some(pre) => pre,
244 None => crate::server::listener::create_tcp_listener(&general)?,
245 });
246
247 tracing::info!(
248 worker_threads = self.worker_threads,
249 "runtime worker threads"
250 );
251 tracing::info!(
252 max_blocking_threads = general.max_blocking_threads,
253 "runtime max blocking threads"
254 );
255 tracing::info!(
256 grace_period_seconds = general.grace_period,
257 "grace period before graceful shutdown"
258 );
259
260 let opts_result = opts::init(&general, advanced)?;
262 let router_service = RouterService::new(RequestHandler {
263 opts: Arc::from(opts_result.handler_opts),
264 });
265
266 #[cfg(windows)]
268 let (sender, ctrl_c_recv) = tokio::sync::watch::channel(());
269 #[cfg(windows)]
270 let windows_service = general.windows_service;
271 #[cfg(windows)]
272 let ctrlc_task = tokio::spawn(async move {
273 if !windows_service {
274 tracing::info!("installing graceful shutdown ctrl+c signal handler");
275 if let Err(err) = tokio::signal::ctrl_c().await {
276 return Err(
277 crate::Error::new(err).context("failed to install ctrl+c signal handler")
278 );
279 }
280 tracing::info!("graceful shutdown ctrl+c signal received");
281 let _ = sender.send(());
282 }
283 Ok::<_, crate::Error>(())
284 });
285
286 let ctx = ShutdownCtx {
287 grace_period: general.grace_period,
288 cancel_recv,
289 #[cfg(windows)]
290 windows_service,
291 #[cfg(windows)]
292 ctrl_c_recv,
293 #[cfg(windows)]
294 ctrlc_task,
295 };
296
297 #[cfg(unix)]
301 if let Some((unix_listener, socket_path, addr_str)) = unix_listener_info {
302 return uds::run(
303 unix_listener,
304 socket_path,
305 router_service,
306 &addr_str,
307 self.worker_threads,
308 ctx,
309 cancel_fn,
310 )
311 .await;
312 }
313
314 let (tcp_listener, addr_str) = tcp_listener_info.unwrap();
317
318 #[cfg(feature = "tls")]
320 if general.tls {
321 let tls_cert = general
322 .tls_cert
323 .ok_or_else(|| anyhow!("TLS cert file path is required when --tls is enabled"))?;
324 let tls_key = general
325 .tls_key
326 .ok_or_else(|| anyhow!("TLS key file path is required when --tls is enabled"))?;
327
328 let tls_cfg = TlsConfig {
329 tls_cert,
330 tls_key,
331 https_redirect: general.https_redirect,
332 https_redirect_host: general.https_redirect_host,
333 https_redirect_from_port: general.https_redirect_from_port,
334 https_redirect_from_hosts: general.https_redirect_from_hosts,
335 host: general.host,
336 port: general.port,
337 page404: opts_result.page404,
338 page50x: opts_result.page50x,
339 };
340
341 #[cfg(feature = "http2")]
343 if general.http2 {
344 return http2::run(
345 tcp_listener,
346 router_service,
347 &addr_str,
348 self.worker_threads,
349 tls_cfg,
350 ctx,
351 cancel_fn,
352 )
353 .await;
354 }
355
356 return http1_tls::run(
358 tcp_listener,
359 router_service,
360 &addr_str,
361 self.worker_threads,
362 tls_cfg,
363 ctx,
364 cancel_fn,
365 )
366 .await;
367 }
368
369 http1::run(
371 tcp_listener,
372 router_service,
373 &addr_str,
374 self.worker_threads,
375 ctx,
376 cancel_fn,
377 )
378 .await
379 }
380}