index : static-web-server.git

ascending towards madness

author Jose Quintana <joseluisquintana20@gmail.com> 2023-07-19 20:58:14.0 +00:00:00
committer Jose Quintana <joseluisquintana20@gmail.com> 2023-07-19 20:58:14.0 +00:00:00
commit
949c539f50e8a82dcec395cd7deb59b6b76c1d50 [patch]
tree
18ffa677e3723969cc5a0610112e93ef6ebeb0ea
parent
1fe464ba1bcbdbc5728bc1cf4a269912f53c9fe8
download
949c539f50e8a82dcec395cd7deb59b6b76c1d50.tar.gz

refactor: logging system initialization at config level

the logging system is now initialized once the `general` settings are
determined cli/env & config-file based.
this means that we can display more tracing entries that are useful
for example when debugging advanced features usage (config-file based)
like URL rewrites/redirects, etc.

Diff

 src/server.rs       |   5 +-
 src/settings/mod.rs | 517 +++++++++++++++++++++++++++--------------------------
 src/winservice.rs   |   6 +-
 3 files changed, 271 insertions(+), 257 deletions(-)

diff --git a/src/server.rs b/src/server.rs
index bcc8cbd..855509f 100644
--- a/src/server.rs
+++ b/src/server.rs
@@ -24,7 +24,7 @@ use {
    hyper::service::{make_service_fn, service_fn},
};

use crate::{cors, helpers, logger, Settings};
use crate::{cors, helpers, Settings};
use crate::{service::RouterService, Context, Result};

/// Define a multi-thread HTTP or HTTP/2 web server.
@@ -58,9 +58,6 @@ impl Server {
    /// Run the multi-thread `Server` as standalone.
    /// It is a top-level function of [run_server_on_rt](#method.run_server_on_rt).
    pub fn run_standalone(self) -> Result {
        // Logging system initialization
        logger::init(&self.opts.general.log_level)?;

        self.run_server_on_rt(None, || {})
    }

diff --git a/src/settings/mod.rs b/src/settings/mod.rs
index 71ab5ea..8719f54 100644
--- a/src/settings/mod.rs
+++ b/src/settings/mod.rs
@@ -11,8 +11,9 @@ use globset::{Glob, GlobMatcher};
use headers::HeaderMap;
use hyper::StatusCode;
use regex::Regex;
use std::path::PathBuf;

use crate::{Context, Result};
use crate::{logger, Context, Result};

pub mod cli;
pub mod file;
@@ -22,7 +23,7 @@ pub use cli::Commands;

use cli::General;

use self::file::RedirectsKind;
use self::file::{RedirectsKind, Settings as FileSettings};

/// The `headers` file options.
pub struct Headers {
@@ -71,7 +72,9 @@ pub struct Settings {
}

impl Settings {
    /// Handles CLI and config file options and converging them into one.
    /// Reads CLI/Env and config file options returning the server settings.
    /// It also takes care to initialize the logging system with its level
    /// once the `general` settings are determined.
    pub fn get() -> Result<Settings> {
        let opts = General::parse();

@@ -143,274 +146,269 @@ impl Settings {

        // Handle "config file options" and set them when available
        // NOTE: All config file based options shouldn't be mandatory, therefore `Some()` wrapped
        if let Some(ref p) = opts.config_file {
            if p.is_file() {
                let path_resolved = p
                    .canonicalize()
                    .with_context(|| "error resolving toml config file path")?;

                let settings = file::Settings::read(&path_resolved)
                    .with_context(|| {
                        "can not read toml config file because has invalid or unsupported format/options"
                    })?;

                config_file = Some(path_resolved);

                // File-based "general" options
                if let Some(general) = settings.general {
                    if let Some(v) = general.host {
                        host = v
                    }
                    if let Some(v) = general.port {
                        port = v
                    }
                    if let Some(v) = general.root {
                        root = v
                    }
                    if let Some(ref v) = general.log_level {
                        log_level = v.name().to_lowercase();
                    }
                    if let Some(v) = general.cache_control_headers {
                        cache_control_headers = v
                    }
                    #[cfg(feature = "compression")]
                    if let Some(v) = general.compression {
                        compression = v
                    }
                    #[cfg(feature = "compression")]
                    if let Some(v) = general.compression_static {
                        compression_static = v
                    }
                    if let Some(v) = general.page404 {
                        page404 = v
                    }
                    if let Some(v) = general.page50x {
                        page50x = v
                    }
                    #[cfg(feature = "http2")]
                    if let Some(v) = general.http2 {
                        http2 = v
                    }
                    #[cfg(feature = "http2")]
                    if let Some(v) = general.http2_tls_cert {
                        http2_tls_cert = Some(v)
                    }
                    #[cfg(feature = "http2")]
                    if let Some(v) = general.http2_tls_key {
                        http2_tls_key = Some(v)
                    }
                    #[cfg(feature = "http2")]
                    if let Some(v) = general.https_redirect {
                        https_redirect = v
                    }
                    #[cfg(feature = "http2")]
                    if let Some(v) = general.https_redirect_host {
                        https_redirect_host = v
                    }
                    #[cfg(feature = "http2")]
                    if let Some(v) = general.https_redirect_from_port {
                        https_redirect_from_port = v
                    }
                    #[cfg(feature = "http2")]
                    if let Some(v) = general.https_redirect_from_hosts {
                        https_redirect_from_hosts = v
                    }
                    #[cfg(feature = "http2")]
                    match general.security_headers {
                        Some(v) => security_headers = v,
                        _ => {
                            if http2 {
                                security_headers = true;
                            }
        if let Some((settings, path_resolved)) = get_file_settings(opts.config_file)? {
            config_file = Some(path_resolved);

            // File-based "general" options
            if let Some(general) = settings.general {
                if let Some(v) = general.host {
                    host = v
                }
                if let Some(v) = general.port {
                    port = v
                }
                if let Some(v) = general.root {
                    root = v
                }
                if let Some(ref v) = general.log_level {
                    log_level = v.name().to_lowercase();
                }
                if let Some(v) = general.cache_control_headers {
                    cache_control_headers = v
                }
                #[cfg(feature = "compression")]
                if let Some(v) = general.compression {
                    compression = v
                }
                #[cfg(feature = "compression")]
                if let Some(v) = general.compression_static {
                    compression_static = v
                }
                if let Some(v) = general.page404 {
                    page404 = v
                }
                if let Some(v) = general.page50x {
                    page50x = v
                }
                #[cfg(feature = "http2")]
                if let Some(v) = general.http2 {
                    http2 = v
                }
                #[cfg(feature = "http2")]
                if let Some(v) = general.http2_tls_cert {
                    http2_tls_cert = Some(v)
                }
                #[cfg(feature = "http2")]
                if let Some(v) = general.http2_tls_key {
                    http2_tls_key = Some(v)
                }
                #[cfg(feature = "http2")]
                if let Some(v) = general.https_redirect {
                    https_redirect = v
                }
                #[cfg(feature = "http2")]
                if let Some(v) = general.https_redirect_host {
                    https_redirect_host = v
                }
                #[cfg(feature = "http2")]
                if let Some(v) = general.https_redirect_from_port {
                    https_redirect_from_port = v
                }
                #[cfg(feature = "http2")]
                if let Some(v) = general.https_redirect_from_hosts {
                    https_redirect_from_hosts = v
                }
                #[cfg(feature = "http2")]
                match general.security_headers {
                    Some(v) => security_headers = v,
                    _ => {
                        if http2 {
                            security_headers = true;
                        }
                    }
                    #[cfg(not(feature = "http2"))]
                    if let Some(v) = general.security_headers {
                        security_headers = v
                    }
                    if let Some(ref v) = general.cors_allow_origins {
                        cors_allow_origins = v.to_owned()
                    }
                    if let Some(ref v) = general.cors_allow_headers {
                        cors_allow_headers = v.to_owned()
                    }
                    if let Some(ref v) = general.cors_expose_headers {
                        cors_expose_headers = v.to_owned()
                    }
                    #[cfg(feature = "directory-listing")]
                    if let Some(v) = general.directory_listing {
                        directory_listing = v
                    }
                    #[cfg(feature = "directory-listing")]
                    if let Some(v) = general.directory_listing_order {
                        directory_listing_order = v
                    }
                    #[cfg(feature = "directory-listing")]
                    if let Some(v) = general.directory_listing_format {
                        directory_listing_format = v
                    }
                    #[cfg(feature = "basic-auth")]
                    if let Some(ref v) = general.basic_auth {
                        basic_auth = v.to_owned()
                    }
                    if let Some(v) = general.fd {
                        fd = Some(v)
                    }
                    if let Some(v) = general.threads_multiplier {
                        threads_multiplier = v
                    }
                    if let Some(v) = general.max_blocking_threads {
                        max_blocking_threads = v
                    }
                    if let Some(v) = general.grace_period {
                        grace_period = v
                    }
                    #[cfg(feature = "fallback-page")]
                    if let Some(v) = general.page_fallback {
                        page_fallback = v
                    }
                    if let Some(v) = general.log_remote_address {
                        log_remote_address = v
                    }
                    if let Some(v) = general.redirect_trailing_slash {
                        redirect_trailing_slash = v
                    }
                    if let Some(v) = general.ignore_hidden_files {
                        ignore_hidden_files = v
                    }
                    if let Some(v) = general.health {
                        health = v
                    }
                }
                #[cfg(not(feature = "http2"))]
                if let Some(v) = general.security_headers {
                    security_headers = v
                }
                if let Some(ref v) = general.cors_allow_origins {
                    cors_allow_origins = v.to_owned()
                }
                if let Some(ref v) = general.cors_allow_headers {
                    cors_allow_headers = v.to_owned()
                }
                if let Some(ref v) = general.cors_expose_headers {
                    cors_expose_headers = v.to_owned()
                }
                #[cfg(feature = "directory-listing")]
                if let Some(v) = general.directory_listing {
                    directory_listing = v
                }
                #[cfg(feature = "directory-listing")]
                if let Some(v) = general.directory_listing_order {
                    directory_listing_order = v
                }
                #[cfg(feature = "directory-listing")]
                if let Some(v) = general.directory_listing_format {
                    directory_listing_format = v
                }
                #[cfg(feature = "basic-auth")]
                if let Some(ref v) = general.basic_auth {
                    basic_auth = v.to_owned()
                }
                if let Some(v) = general.fd {
                    fd = Some(v)
                }
                if let Some(v) = general.threads_multiplier {
                    threads_multiplier = v
                }
                if let Some(v) = general.max_blocking_threads {
                    max_blocking_threads = v
                }
                if let Some(v) = general.grace_period {
                    grace_period = v
                }
                #[cfg(feature = "fallback-page")]
                if let Some(v) = general.page_fallback {
                    page_fallback = v
                }
                if let Some(v) = general.log_remote_address {
                    log_remote_address = v
                }
                if let Some(v) = general.redirect_trailing_slash {
                    redirect_trailing_slash = v
                }
                if let Some(v) = general.ignore_hidden_files {
                    ignore_hidden_files = v
                }
                if let Some(v) = general.health {
                    health = v
                }

                    // Windows-only options
                    #[cfg(windows)]
                    if let Some(v) = general.windows_service {
                        windows_service = v
                    }
                // Windows-only options
                #[cfg(windows)]
                if let Some(v) = general.windows_service {
                    windows_service = v
                }
            }

            // Logging system initialization
            logger::init(log_level.as_str())?;

            // File-based "advanced" options
            if let Some(advanced) = settings.advanced {
                // 1. Custom HTTP headers assignment
                let headers_entries = match advanced.headers {
                    Some(headers_entries) => {
                        let mut headers_vec: Vec<Headers> = Vec::new();

                // File-based "advanced" options
                if let Some(advanced) = settings.advanced {
                    // 1. Custom HTTP headers assignment
                    let headers_entries = match advanced.headers {
                        Some(headers_entries) => {
                            let mut headers_vec: Vec<Headers> = Vec::new();

                            // Compile a glob pattern for each header sources entry
                            for headers_entry in headers_entries.iter() {
                                let source = Glob::new(&headers_entry.source)
                                    .with_context(|| {
                                        format!(
                                            "can not compile glob pattern for header source: {}",
                                            &headers_entry.source
                                        )
                                    })?
                                    .compile_matcher();

                                headers_vec.push(Headers {
                                    source,
                                    headers: headers_entry.headers.to_owned(),
                                });
                            }
                            Some(headers_vec)
                        // Compile a glob pattern for each header sources entry
                        for headers_entry in headers_entries.iter() {
                            let source = Glob::new(&headers_entry.source)
                                .with_context(|| {
                                    format!(
                                        "can not compile glob pattern for header source: {}",
                                        &headers_entry.source
                                    )
                                })?
                                .compile_matcher();

                            headers_vec.push(Headers {
                                source,
                                headers: headers_entry.headers.to_owned(),
                            });
                        }
                        _ => None,
                    };

                    // 2. Rewrites assignment
                    let rewrites_entries = match advanced.rewrites {
                        Some(rewrites_entries) => {
                            let mut rewrites_vec: Vec<Rewrites> = Vec::new();

                            // Compile a glob pattern for each rewrite sources entry
                            for rewrites_entry in rewrites_entries.iter() {
                                let source = Glob::new(&rewrites_entry.source)
                                    .with_context(|| {
                                        format!(
                                            "can not compile glob pattern for rewrite source: {}",
                                            &rewrites_entry.source
                                        )
                                    })?
                                    .compile_matcher();

                                let pattern =
                                    source.glob().regex().trim_start_matches("(?-u)").to_owned();
                                tracing::debug!(
                                    "url rewrites glob pattern: {}",
                                    &rewrites_entry.source
                                );
                                tracing::debug!("url rewrites regex equivalent: {}", pattern);

                                let source = Regex::new(&pattern).with_context(|| {
                        Some(headers_vec)
                    }
                    _ => None,
                };

                // 2. Rewrites assignment
                let rewrites_entries = match advanced.rewrites {
                    Some(rewrites_entries) => {
                        let mut rewrites_vec: Vec<Rewrites> = Vec::new();

                        // Compile a glob pattern for each rewrite sources entry
                        for rewrites_entry in rewrites_entries.iter() {
                            let source = Glob::new(&rewrites_entry.source)
                                .with_context(|| {
                                    format!(
                                        "can not compile glob pattern for rewrite source: {}",
                                        &rewrites_entry.source
                                    )
                                })?
                                .compile_matcher();

                            let pattern =
                                source.glob().regex().trim_start_matches("(?-u)").to_owned();
                            tracing::debug!(
                                "url rewrites glob pattern: {}",
                                &rewrites_entry.source
                            );
                            tracing::debug!("url rewrites regex equivalent: {}", pattern);

                            let source = Regex::new(&pattern).with_context(|| {
                                    format!(
                                        "can not compile regex pattern equivalent for rewrite source: {}",
                                        &pattern
                                    )
                                })?;

                                rewrites_vec.push(Rewrites {
                                    source,
                                    destination: rewrites_entry.destination.to_owned(),
                                    redirect: rewrites_entry.redirect.to_owned(),
                                });
                            }
                            Some(rewrites_vec)
                            rewrites_vec.push(Rewrites {
                                source,
                                destination: rewrites_entry.destination.to_owned(),
                                redirect: rewrites_entry.redirect.to_owned(),
                            });
                        }
                        _ => None,
                    };

                    // 3. Redirects assignment
                    let redirects_entries = match advanced.redirects {
                        Some(redirects_entries) => {
                            let mut redirects_vec: Vec<Redirects> = Vec::new();

                            // Compile a glob pattern for each redirect sources entry
                            for redirects_entry in redirects_entries.iter() {
                                let source = Glob::new(&redirects_entry.source)
                                    .with_context(|| {
                                        format!(
                                            "can not compile glob pattern for redirect source: {}",
                                            &redirects_entry.source
                                        )
                                    })?
                                    .compile_matcher();

                                let pattern =
                                    source.glob().regex().trim_start_matches("(?-u)").to_owned();
                                tracing::debug!(
                                    "url rewrites glob pattern: {}",
                                    &redirects_entry.source
                                );
                                tracing::debug!("url rewrites regex equivalent: {}", pattern);

                                let source = Regex::new(&pattern).with_context(|| {
                        Some(rewrites_vec)
                    }
                    _ => None,
                };

                // 3. Redirects assignment
                let redirects_entries = match advanced.redirects {
                    Some(redirects_entries) => {
                        let mut redirects_vec: Vec<Redirects> = Vec::new();

                        // Compile a glob pattern for each redirect sources entry
                        for redirects_entry in redirects_entries.iter() {
                            let source = Glob::new(&redirects_entry.source)
                                .with_context(|| {
                                    format!(
                                        "can not compile glob pattern for redirect source: {}",
                                        &redirects_entry.source
                                    )
                                })?
                                .compile_matcher();

                            let pattern =
                                source.glob().regex().trim_start_matches("(?-u)").to_owned();
                            tracing::debug!(
                                "url rewrites glob pattern: {}",
                                &redirects_entry.source
                            );
                            tracing::debug!("url rewrites regex equivalent: {}", pattern);

                            let source = Regex::new(&pattern).with_context(|| {
                                    format!(
                                        "can not compile regex pattern equivalent for rewrite source: {}",
                                        &pattern
                                    )
                                })?;

                                let status_code = redirects_entry.kind.to_owned() as u16;
                                redirects_vec.push(Redirects {
                                    source,
                                    destination: redirects_entry.destination.to_owned(),
                                    kind: StatusCode::from_u16(status_code).with_context(|| {
                                        format!("invalid redirect status code: {status_code}")
                                    })?,
                                });
                            }
                            Some(redirects_vec)
                            let status_code = redirects_entry.kind.to_owned() as u16;
                            redirects_vec.push(Redirects {
                                source,
                                destination: redirects_entry.destination.to_owned(),
                                kind: StatusCode::from_u16(status_code).with_context(|| {
                                    format!("invalid redirect status code: {status_code}")
                                })?,
                            });
                        }
                        _ => None,
                    };
                        Some(redirects_vec)
                    }
                    _ => None,
                };

                    settings_advanced = Some(Advanced {
                        headers: headers_entries,
                        rewrites: rewrites_entries,
                        redirects: redirects_entries,
                    });
                }
                settings_advanced = Some(Advanced {
                    headers: headers_entries,
                    rewrites: rewrites_entries,
                    redirects: redirects_entries,
                });
            }
        } else {
            // Logging system initialization
            logger::init(log_level.as_str())?;
        }

        Ok(Settings {
@@ -474,3 +472,24 @@ impl Settings {
        })
    }
}

fn get_file_settings(file_path_opt: Option<PathBuf>) -> Result<Option<(FileSettings, PathBuf)>> {
    if let Some(ref file_path) = file_path_opt {
        if file_path.is_file() {
            let file_path_resolved = file_path
                .canonicalize()
                .with_context(|| "error resolving toml config file path")?;
            let settings = FileSettings::read(&file_path_resolved).with_context(|| {
                "can not read toml config file because has invalid or unsupported format/options"
            })?;
            return Ok(Some((settings, file_path_resolved)));
        }
        tracing::warn!(
            "config file `{}` is not a regular toml file, ignored!",
            file_path.display()
        );
    } else {
        tracing::warn!("config file was not determined, ignored!");
    }
    Ok(None)
}
diff --git a/src/winservice.rs b/src/winservice.rs
index ff7a81f..063ba0d 100644
--- a/src/winservice.rs
+++ b/src/winservice.rs
@@ -22,7 +22,7 @@ use windows_service::{
    service_manager::{ServiceManager, ServiceManagerAccess},
};

use crate::{helpers, logger, Context, Result, Server, Settings};
use crate::{helpers, Context, Result, Server, Settings};

const SERVICE_NAME: &str = "static-web-server";
const SERVICE_TYPE: ServiceType = ServiceType::OWN_PROCESS;
@@ -72,9 +72,7 @@ fn set_service_state(
}

fn run_service() -> Result {
    let opts = Settings::get()?;

    logger::init(&opts.general.log_level)?;
    let _ = Settings::get()?;

    tracing::info!("windows service: starting service setup");