static_web_server/
server.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2// This file is part of Static Web Server.
3// See https://static-web-server.net/ for more information
4// Copyright (C) 2019-present Jose Quintana <joseluisq.net>
5
6//! Server module intended to construct a multi-threaded HTTP or HTTP/2 web server.
7//!
8
9use 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
55/// Define a multi-threaded HTTP or HTTP/2 web server.
56pub struct Server {
57    opts: Settings,
58    worker_threads: usize,
59    max_blocking_threads: usize,
60}
61
62impl Server {
63    /// Create a new multi-threaded server instance.
64    pub fn new(opts: Settings) -> Result<Server> {
65        // Configure number of worker threads
66        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    /// Run the multi-threaded `Server` as standalone.
85    /// This is a top-level function of [run_server_on_rt](#method.run_server_on_rt).
86    ///
87    /// It accepts an optional [`cancel`] parameter to shut down the server
88    /// gracefully on demand as a complement to the termination signals handling.
89    ///
90    /// [`cancel`]: <https://docs.rs/tokio/latest/tokio/sync/watch/struct.Receiver.html>
91    pub fn run_standalone(self, cancel: Option<Receiver<()>>) -> Result {
92        self.run_server_on_rt(cancel, || {})
93    }
94
95    /// Run the multi-threaded `Server` which will be used by a Windows service.
96    /// This is a top-level function of [run_server_on_rt](#method.run_server_on_rt).
97    ///
98    /// It accepts an optional [`cancel`] parameter to shut down the server
99    /// gracefully on demand and an optional `cancel_fn` that will be executed
100    /// right after the server is shut down.
101    ///
102    /// [`cancel`]: <https://docs.rs/tokio/latest/tokio/sync/watch/struct.Receiver.html>
103    #[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    /// Build and run the multi-threaded `Server` on the Tokio runtime.
112    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    /// Run the inner Hyper `HyperServer` (HTTP1/HTTP2) forever on the current thread
136    // using the given configuration.
137    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        // Config "general" options
145        let general = self.opts.general;
146        // Config-file "advanced" options
147        let advanced_opts = self.opts.advanced;
148
149        server_info!("log level: {}", general.log_level);
150
151        // Config file option
152        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        // Determine TCP listener either file descriptor or TCP socket
163        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        // Number of worker threads option
189        let threads = self.worker_threads;
190        server_info!("runtime worker threads: {}", threads);
191
192        // Maximum number of blocking threads
193        server_info!(
194            "runtime max blocking threads: {}",
195            general.max_blocking_threads
196        );
197
198        // Check for a valid root directory
199        let root_dir = helpers::get_valid_dirpath(&general.root)
200            .with_context(|| "root directory was not found or inaccessible")?;
201
202        // Custom HTML error page files
203        // NOTE: in the case of relative paths, they're joined to the root directory
204        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        // Log remote address option
226        let log_remote_address = general.log_remote_address;
227
228        // Log the X-Real-IP header.
229        let log_x_real_ip = general.log_x_real_ip;
230
231        // Log the X-Forwarded-For header.
232        let log_forwarded_for = general.log_forwarded_for;
233
234        // Trusted IPs for remote addresses.
235        let trusted_proxies = general.trusted_proxies;
236
237        // Log redirect trailing slash option
238        let redirect_trailing_slash = general.redirect_trailing_slash;
239        server_info!(
240            "redirect trailing slash: enabled={}",
241            redirect_trailing_slash
242        );
243
244        // Ignore hidden files option
245        let ignore_hidden_files = general.ignore_hidden_files;
246        server_info!("ignore hidden files: enabled={}", ignore_hidden_files);
247
248        // Disable symlinks option
249        let disable_symlinks = general.disable_symlinks;
250        server_info!("disable symlinks: enabled={}", disable_symlinks);
251
252        // Grace period option
253        let grace_period = general.grace_period;
254        server_info!("grace period before graceful shutdown: {}s", grace_period);
255
256        // Index files option
257        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        // Request handler options, some settings will be filled in by modules
268        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        // Directory listing options
285        #[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        // Fallback page option
294        #[cfg(feature = "fallback-page")]
295        fallback_page::init(&general.page_fallback, &mut handler_opts);
296
297        // Health endpoint option
298        health::init(general.health, &mut handler_opts);
299
300        // Log remote address option
301        log_addr::init(general.log_remote_address, &mut handler_opts);
302
303        // Metrics endpoint option (experimental)
304        #[cfg(all(unix, feature = "experimental"))]
305        metrics::init(general.experimental_metrics, &mut handler_opts);
306
307        // CORS option
308        cors::init(
309            &general.cors_allow_origins,
310            &general.cors_allow_headers,
311            &general.cors_expose_headers,
312            &mut handler_opts,
313        );
314
315        // `Basic` HTTP Authentication Schema option
316        #[cfg(feature = "basic-auth")]
317        basic_auth::init(&general.basic_auth, &mut handler_opts);
318
319        // Maintenance mode option
320        maintenance_mode::init(
321            general.maintenance_mode,
322            general.maintenance_mode_status,
323            general.maintenance_mode_file,
324            &mut handler_opts,
325        );
326
327        // Check pre-compressed files based on the `Accept-Encoding` header
328        #[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        // Auto compression based on the `Accept-Encoding` header
338        #[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        // Cache control headers option
352        control_headers::init(general.cache_control_headers, &mut handler_opts);
353
354        // Security Headers option
355        security_headers::init(general.security_headers, &mut handler_opts);
356
357        // In-Memory cache option
358        #[cfg(feature = "experimental")]
359        mem_cache::cache::init(&mut handler_opts)?;
360
361        // Create a service router for Hyper
362        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        // Windows ctrl+c listening
370        #[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        // Run the corresponding HTTP Server asynchronously with its given options
383        #[cfg(feature = "http2")]
384        if general.http2 {
385            // HTTP to HTTPS redirect option
386            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            // HTTP/2 + TLS
402            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            // HTTP to HTTPS redirect server
476            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                // Allowed redirect hosts
500                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                // Redirect options
510                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                // HTTP/2 server task
556                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                // HTTP/1 redirect server task
564                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        // HTTP/1
596
597        #[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                // http1_cancel_recv
626                Arc::new(Mutex::new(_cancel_recv))
627            } else {
628                // http1_ctrlc_recv
629                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}