1use hyper::server::Server as HyperServer;
10use listenfd::ListenFd;
11use std::net::{IpAddr, SocketAddr, TcpListener};
12use std::sync::Arc;
13use tokio::sync::{watch::Receiver, Mutex};
14
15use crate::handler::{RequestHandler, RequestHandlerOpts};
16
17#[cfg(all(unix, feature = "experimental"))]
18use crate::metrics;
19#[cfg(any(unix, windows))]
20use crate::signals;
21
22#[cfg(feature = "http2")]
23use {
24 crate::tls::{TlsAcceptor, TlsConfigBuilder},
25 crate::{error, error_page, https_redirect},
26 hyper::server::conn::{AddrIncoming, AddrStream},
27 hyper::service::{make_service_fn, service_fn},
28};
29
30#[cfg(feature = "directory-listing")]
31use crate::directory_listing;
32#[cfg(feature = "fallback-page")]
33use crate::fallback_page;
34
35#[cfg(any(
36 feature = "compression",
37 feature = "compression-deflate",
38 feature = "compression-gzip",
39 feature = "compression-brotli",
40 feature = "compression-zstd",
41))]
42use crate::{compression, compression_static};
43
44#[cfg(feature = "basic-auth")]
45use crate::basic_auth;
46
47#[cfg(feature = "experimental")]
48use crate::mem_cache;
49
50use crate::{
51 control_headers, cors, health, helpers, log_addr, maintenance_mode, security_headers, Settings,
52};
53use crate::{service::RouterService, Context, Result};
54
55pub struct Server {
57 opts: Settings,
58 worker_threads: usize,
59 max_blocking_threads: usize,
60}
61
62impl Server {
63 pub fn new(opts: Settings) -> Result<Server> {
65 let cpus = std::thread::available_parallelism()
67 .with_context(|| {
68 "unable to get current platform cpus or lack of permissions to query available parallelism"
69 })?
70 .get();
71 let worker_threads = match opts.general.threads_multiplier {
72 0 | 1 => cpus,
73 n => cpus * n,
74 };
75 let max_blocking_threads = opts.general.max_blocking_threads;
76
77 Ok(Server {
78 opts,
79 worker_threads,
80 max_blocking_threads,
81 })
82 }
83
84 pub fn run_standalone(self, cancel: Option<Receiver<()>>) -> Result {
92 self.run_server_on_rt(cancel, || {})
93 }
94
95 #[cfg(windows)]
104 pub fn run_as_service<F>(self, cancel: Option<Receiver<()>>, cancel_fn: F) -> Result
105 where
106 F: FnOnce(),
107 {
108 self.run_server_on_rt(cancel, cancel_fn)
109 }
110
111 pub fn run_server_on_rt<F>(self, cancel_recv: Option<Receiver<()>>, cancel_fn: F) -> Result
113 where
114 F: FnOnce(),
115 {
116 tracing::debug!(%self.worker_threads, "initializing tokio runtime with multi-threaded scheduler");
117
118 tokio::runtime::Builder::new_multi_thread()
119 .worker_threads(self.worker_threads)
120 .max_blocking_threads(self.max_blocking_threads)
121 .thread_name("static-web-server")
122 .enable_all()
123 .build()?
124 .block_on(async {
125 tracing::trace!("tokio runtime initialized");
126 if let Err(err) = self.start_server(cancel_recv, cancel_fn).await {
127 tracing::error!("server failed to start up: {:?}", err);
128 std::process::exit(1)
129 }
130 });
131
132 Ok(())
133 }
134
135 async fn start_server<F>(self, _cancel_recv: Option<Receiver<()>>, _cancel_fn: F) -> Result
138 where
139 F: FnOnce(),
140 {
141 tracing::trace!("starting web server");
142 server_info!("{} {}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"));
143
144 let general = self.opts.general;
146 let advanced_opts = self.opts.advanced;
148
149 server_info!("log level: {}", general.log_level);
150
151 let config_file = general.config_file;
153 if config_file.is_file() {
154 server_info!("config file used: {}", config_file.display());
155 } else {
156 tracing::debug!(
157 "config file path not found or not a regular file: {}",
158 config_file.display()
159 );
160 }
161
162 let (tcp_listener, addr_str);
164 match general.fd {
165 Some(fd) => {
166 addr_str = format!("@FD({fd})");
167 tcp_listener = ListenFd::from_env()
168 .take_tcp_listener(fd)?
169 .with_context(|| "failed to convert inherited 'fd' into a 'tcp' listener")?;
170 server_info!(
171 "converted inherited file descriptor {} to a 'tcp' listener",
172 fd
173 );
174 }
175 None => {
176 let ip = general
177 .host
178 .parse::<IpAddr>()
179 .with_context(|| format!("failed to parse {} address", general.host))?;
180 let addr = SocketAddr::from((ip, general.port));
181 tcp_listener = TcpListener::bind(addr)
182 .with_context(|| format!("failed to bind to {addr} address"))?;
183 addr_str = addr.to_string();
184 server_info!("server bound to tcp socket {}", addr_str);
185 }
186 }
187
188 let threads = self.worker_threads;
190 server_info!("runtime worker threads: {}", threads);
191
192 server_info!(
194 "runtime max blocking threads: {}",
195 general.max_blocking_threads
196 );
197
198 let root_dir = helpers::get_valid_dirpath(&general.root)
200 .with_context(|| "root directory was not found or inaccessible")?;
201
202 let mut page404 = general.page404;
205 if page404.is_relative() && !page404.starts_with(&root_dir) {
206 page404 = root_dir.join(page404);
207 }
208 if !page404.is_file() {
209 tracing::debug!(
210 "404 file path not found or not a regular file: {}",
211 page404.display()
212 );
213 }
214 let mut page50x = general.page50x;
215 if page50x.is_relative() && !page50x.starts_with(&root_dir) {
216 page50x = root_dir.join(page50x);
217 }
218 if !page50x.is_file() {
219 tracing::debug!(
220 "50x file path not found or not a regular file: {}",
221 page50x.display()
222 );
223 }
224
225 let log_remote_address = general.log_remote_address;
227
228 let log_x_real_ip = general.log_x_real_ip;
230
231 let log_forwarded_for = general.log_forwarded_for;
233
234 let trusted_proxies = general.trusted_proxies;
236
237 let redirect_trailing_slash = general.redirect_trailing_slash;
239 server_info!(
240 "redirect trailing slash: enabled={}",
241 redirect_trailing_slash
242 );
243
244 let ignore_hidden_files = general.ignore_hidden_files;
246 server_info!("ignore hidden files: enabled={}", ignore_hidden_files);
247
248 let disable_symlinks = general.disable_symlinks;
250 server_info!("disable symlinks: enabled={}", disable_symlinks);
251
252 let grace_period = general.grace_period;
254 server_info!("grace period before graceful shutdown: {}s", grace_period);
255
256 let index_files = general
258 .index_files
259 .split(',')
260 .map(|s| s.trim().to_owned())
261 .collect::<Vec<_>>();
262 if index_files.is_empty() {
263 bail!("index files list is empty, provide at least one index file")
264 }
265 server_info!("index files: {}", general.index_files);
266
267 let mut handler_opts = RequestHandlerOpts {
269 root_dir,
270 page404: page404.clone(),
271 page50x: page50x.clone(),
272 log_remote_address,
273 log_x_real_ip,
274 log_forwarded_for,
275 trusted_proxies,
276 redirect_trailing_slash,
277 ignore_hidden_files,
278 disable_symlinks,
279 index_files,
280 advanced_opts,
281 ..Default::default()
282 };
283
284 #[cfg(feature = "directory-listing")]
286 directory_listing::init(
287 general.directory_listing,
288 general.directory_listing_order,
289 general.directory_listing_format,
290 &mut handler_opts,
291 );
292
293 #[cfg(feature = "fallback-page")]
295 fallback_page::init(&general.page_fallback, &mut handler_opts);
296
297 health::init(general.health, &mut handler_opts);
299
300 log_addr::init(general.log_remote_address, &mut handler_opts);
302
303 #[cfg(all(unix, feature = "experimental"))]
305 metrics::init(general.experimental_metrics, &mut handler_opts);
306
307 cors::init(
309 &general.cors_allow_origins,
310 &general.cors_allow_headers,
311 &general.cors_expose_headers,
312 &mut handler_opts,
313 );
314
315 #[cfg(feature = "basic-auth")]
317 basic_auth::init(&general.basic_auth, &mut handler_opts);
318
319 maintenance_mode::init(
321 general.maintenance_mode,
322 general.maintenance_mode_status,
323 general.maintenance_mode_file,
324 &mut handler_opts,
325 );
326
327 #[cfg(any(
329 feature = "compression",
330 feature = "compression-deflate",
331 feature = "compression-gzip",
332 feature = "compression-brotli",
333 feature = "compression-zstd",
334 ))]
335 compression_static::init(general.compression_static, &mut handler_opts);
336
337 #[cfg(any(
339 feature = "compression",
340 feature = "compression-deflate",
341 feature = "compression-gzip",
342 feature = "compression-brotli",
343 feature = "compression-zstd",
344 ))]
345 compression::init(
346 general.compression,
347 general.compression_level,
348 &mut handler_opts,
349 );
350
351 control_headers::init(general.cache_control_headers, &mut handler_opts);
353
354 security_headers::init(general.security_headers, &mut handler_opts);
356
357 #[cfg(feature = "experimental")]
359 mem_cache::cache::init(&mut handler_opts)?;
360
361 let router_service = RouterService::new(RequestHandler {
363 opts: Arc::from(handler_opts),
364 });
365
366 #[cfg(windows)]
367 let (sender, receiver) = tokio::sync::watch::channel(());
368
369 #[cfg(windows)]
371 let ctrlc_task = tokio::spawn(async move {
372 if !general.windows_service {
373 server_info!("installing graceful shutdown ctrl+c signal handler");
374 tokio::signal::ctrl_c()
375 .await
376 .expect("failed to install ctrl+c signal handler");
377 server_info!("installing graceful shutdown ctrl+c signal handler");
378 let _ = sender.send(());
379 }
380 });
381
382 #[cfg(feature = "http2")]
384 if general.http2 {
385 let https_redirect = general.https_redirect;
387 server_info!("http to https redirect: enabled={}", https_redirect);
388 server_info!(
389 "http to https redirect host: {}",
390 general.https_redirect_host
391 );
392 server_info!(
393 "http to https redirect from port: {}",
394 general.https_redirect_from_port
395 );
396 server_info!(
397 "http to https redirect from hosts: {}",
398 general.https_redirect_from_hosts
399 );
400
401 tcp_listener
403 .set_nonblocking(true)
404 .with_context(|| "failed to set TCP non-blocking mode")?;
405 let listener = tokio::net::TcpListener::from_std(tcp_listener)
406 .with_context(|| "failed to create tokio::net::TcpListener")?;
407 let mut incoming = AddrIncoming::from_listener(listener).with_context(|| {
408 "failed to create an AddrIncoming from the current tokio::net::TcpListener"
409 })?;
410 incoming.set_nodelay(true);
411
412 let http2_tls_cert = match general.http2_tls_cert {
413 Some(v) => v,
414 _ => bail!("failed to initialize TLS because cert file missing"),
415 };
416 let http2_tls_key = match general.http2_tls_key {
417 Some(v) => v,
418 _ => bail!("failed to initialize TLS because key file missing"),
419 };
420
421 let tls = TlsConfigBuilder::new()
422 .cert_path(&http2_tls_cert)
423 .key_path(&http2_tls_key)
424 .build()
425 .with_context(|| {
426 "failed to initialize TLS probably because invalid cert or key file"
427 })?;
428
429 #[cfg(unix)]
430 let signals = signals::create_signals()
431 .with_context(|| "failed to register termination signals")?;
432 #[cfg(unix)]
433 let handle = signals.handle();
434
435 let http2_server =
436 HyperServer::builder(TlsAcceptor::new(tls, incoming)).serve(router_service);
437
438 #[cfg(unix)]
439 let http2_cancel_recv = Arc::new(Mutex::new(_cancel_recv));
440 #[cfg(unix)]
441 let redirect_cancel_recv = http2_cancel_recv.clone();
442
443 #[cfg(unix)]
444 let http2_server = http2_server.with_graceful_shutdown(signals::wait_for_signals(
445 signals,
446 grace_period,
447 http2_cancel_recv,
448 ));
449
450 #[cfg(windows)]
451 let http2_cancel_recv = Arc::new(Mutex::new(_cancel_recv));
452 #[cfg(windows)]
453 let redirect_cancel_recv = http2_cancel_recv.clone();
454
455 #[cfg(windows)]
456 let http2_ctrlc_recv = Arc::new(Mutex::new(Some(receiver)));
457 #[cfg(windows)]
458 let redirect_ctrlc_recv = http2_ctrlc_recv.clone();
459
460 #[cfg(windows)]
461 let http2_server = http2_server.with_graceful_shutdown(async move {
462 if general.windows_service {
463 signals::wait_for_ctrl_c(http2_cancel_recv, grace_period).await;
464 } else {
465 signals::wait_for_ctrl_c(http2_ctrlc_recv, grace_period).await;
466 }
467 });
468
469 server_info!(
470 parent: tracing::info_span!("Server::start_server", ?addr_str, ?threads),
471 "http2 server is listening on https://{}",
472 addr_str
473 );
474
475 if general.https_redirect {
477 let ip = general
478 .host
479 .parse::<IpAddr>()
480 .with_context(|| format!("failed to parse {} address", general.host))?;
481 let addr = SocketAddr::from((ip, general.https_redirect_from_port));
482 let tcp_listener = TcpListener::bind(addr)
483 .with_context(|| format!("failed to bind to {addr} address"))?;
484 server_info!(
485 parent: tracing::info_span!("Server::start_server", ?addr, ?threads),
486 "http1 redirect server is listening on http://{}",
487 addr
488 );
489 tcp_listener
490 .set_nonblocking(true)
491 .with_context(|| "failed to set TCP non-blocking mode")?;
492
493 #[cfg(unix)]
494 let redirect_signals = signals::create_signals()
495 .with_context(|| "failed to register termination signals")?;
496 #[cfg(unix)]
497 let redirect_handle = redirect_signals.handle();
498
499 let redirect_allowed_hosts = general
501 .https_redirect_from_hosts
502 .split(',')
503 .map(|s| s.trim().to_owned())
504 .collect::<Vec<_>>();
505 if redirect_allowed_hosts.is_empty() {
506 bail!("https redirect allowed hosts is empty, provide at least one host or IP")
507 }
508
509 let redirect_opts = Arc::new(https_redirect::RedirectOpts {
511 https_hostname: general.https_redirect_host,
512 https_port: general.port,
513 allowed_hosts: redirect_allowed_hosts,
514 });
515
516 let server_redirect = HyperServer::from_tcp(tcp_listener)
517 .unwrap()
518 .tcp_nodelay(true)
519 .serve(make_service_fn(move |_: &AddrStream| {
520 let redirect_opts = redirect_opts.clone();
521 let page404 = page404.clone();
522 let page50x = page50x.clone();
523 async move {
524 Ok::<_, error::Error>(service_fn(move |req| {
525 let redirect_opts = redirect_opts.clone();
526 let page404 = page404.clone();
527 let page50x = page50x.clone();
528 async move {
529 let uri = req.uri();
530 let method = req.method();
531 match https_redirect::redirect_to_https(&req, redirect_opts) {
532 Ok(resp) => Ok(resp),
533 Err(status) => error_page::error_response(
534 uri, method, &status, &page404, &page50x,
535 ),
536 }
537 }
538 }))
539 }
540 }));
541
542 #[cfg(unix)]
543 let server_redirect = server_redirect.with_graceful_shutdown(
544 signals::wait_for_signals(redirect_signals, grace_period, redirect_cancel_recv),
545 );
546 #[cfg(windows)]
547 let server_redirect = server_redirect.with_graceful_shutdown(async move {
548 if general.windows_service {
549 signals::wait_for_ctrl_c(redirect_cancel_recv, grace_period).await;
550 } else {
551 signals::wait_for_ctrl_c(redirect_ctrlc_recv, grace_period).await;
552 }
553 });
554
555 let server_task = tokio::spawn(async move {
557 if let Err(err) = http2_server.await {
558 tracing::error!("http2 server failed to start up: {:?}", err);
559 std::process::exit(1)
560 }
561 });
562
563 let redirect_server_task = tokio::spawn(async move {
565 if let Err(err) = server_redirect.await {
566 tracing::error!("http1 redirect server failed to start up: {:?}", err);
567 std::process::exit(1)
568 }
569 });
570
571 server_info!("press ctrl+c to shut down the servers");
572
573 #[cfg(windows)]
574 tokio::try_join!(ctrlc_task, server_task, redirect_server_task)?;
575 #[cfg(unix)]
576 tokio::try_join!(server_task, redirect_server_task)?;
577
578 #[cfg(unix)]
579 redirect_handle.close();
580 } else {
581 server_info!("press ctrl+c to shut down the server");
582 http2_server.await?;
583 }
584
585 #[cfg(unix)]
586 handle.close();
587
588 #[cfg(windows)]
589 _cancel_fn();
590
591 server_warn!("termination signal caught, shutting down the server execution");
592 return Ok(());
593 }
594
595 #[cfg(unix)]
598 let signals =
599 signals::create_signals().with_context(|| "failed to register termination signals")?;
600 #[cfg(unix)]
601 let handle = signals.handle();
602
603 tcp_listener
604 .set_nonblocking(true)
605 .with_context(|| "failed to set TCP non-blocking mode")?;
606
607 let http1_server = HyperServer::from_tcp(tcp_listener)
608 .unwrap()
609 .tcp_nodelay(true)
610 .serve(router_service);
611
612 #[cfg(unix)]
613 let http1_cancel_recv = Arc::new(Mutex::new(_cancel_recv));
614
615 #[cfg(unix)]
616 let http1_server = http1_server.with_graceful_shutdown(signals::wait_for_signals(
617 signals,
618 grace_period,
619 http1_cancel_recv,
620 ));
621
622 #[cfg(windows)]
623 let http1_server = http1_server.with_graceful_shutdown(async move {
624 let http1_cancel_recv = if general.windows_service {
625 Arc::new(Mutex::new(_cancel_recv))
627 } else {
628 Arc::new(Mutex::new(Some(receiver)))
630 };
631 signals::wait_for_ctrl_c(http1_cancel_recv, grace_period).await;
632 });
633
634 server_info!(
635 parent: tracing::info_span!("Server::start_server", ?addr_str, ?threads),
636 "http1 server is listening on http://{}",
637 addr_str
638 );
639
640 server_info!("press ctrl+c to shut down the server");
641
642 #[cfg(unix)]
643 http1_server.await?;
644
645 #[cfg(windows)]
646 let http1_server_task = tokio::spawn(async move {
647 if let Err(err) = http1_server.await {
648 tracing::error!("http1 server failed to start up: {:?}", err);
649 std::process::exit(1)
650 }
651 });
652 #[cfg(windows)]
653 tokio::try_join!(ctrlc_task, http1_server_task)?;
654
655 #[cfg(windows)]
656 _cancel_fn();
657
658 #[cfg(unix)]
659 handle.close();
660
661 server_warn!("termination signal caught, shutting down the server execution");
662 Ok(())
663 }
664}