index : static-web-server.git

ascending towards madness

author Jose Quintana <joseluisquintana20@gmail.com> 2022-05-03 21:19:51.0 +00:00:00
committer Jose Quintana <joseluisquintana20@gmail.com> 2022-05-03 21:19:51.0 +00:00:00
commit
959c3258453ae44f333d3c147bb480b9a9404479 [patch]
tree
30d60e37267b91c9a3f60307b88355c126316d70
parent
446576abf70002e05b3b2527a5a84faf4c095de6
download
959c3258453ae44f333d3c147bb480b9a9404479.tar.gz

refactor: `PathBuf` data type for cli and file options

options data types updated:

- root
- page50x
- page404
- page-fallback
- http2-tls-cert
- http2-tls-key

Diff

 docs/content/configuration/config-file.md | 16 +++++-----
 src/control_headers.rs                    |  2 +-
 src/error_page.rs                         | 12 ++++----
 src/fallback_page.rs                      |  7 ++--
 src/handler.rs                            | 12 ++++----
 src/helpers.rs                            | 15 ++++------
 src/server.rs                             | 23 ++++++++++-----
 src/settings/cli.rs                       | 28 +++++-------------
 src/settings/file.rs                      | 14 ++++-----
 src/settings/mod.rs                       | 50 ++++++++++++++++----------------
 10 files changed, 89 insertions(+), 90 deletions(-)

diff --git a/docs/content/configuration/config-file.md b/docs/content/configuration/config-file.md
index 9d0a63c..ba48223 100644
--- a/docs/content/configuration/config-file.md
+++ b/docs/content/configuration/config-file.md
@@ -13,11 +13,11 @@ Below just an example showing all features with its default values.

#### Address & Root dir
host = "::"
port = 8087
root = "docker/public"
port = 80
root = "./public"

#### Logging
log-level = "trace"
log-level = "error"

#### Cache Control headers
cache-control-headers = true
@@ -26,13 +26,13 @@ cache-control-headers = true
compression = true

#### Error pages
page404 = "docker/public/404.html"
page50x = "docker/public/50x.html"
page404 = "./public/404.html"
page50x = "./public/50x.html"

#### HTTP/2 + TLS
http2 = false
http2-tls-cert = ""
http2-tls-key = ""
# http2-tls-cert = "some.cert"
# http2-tls-key = "some.key"

#### Security headers
security-headers = true
@@ -58,7 +58,7 @@ threads-multiplier = 1
grace-period = 0

#### Page fallback for 404s
page-fallback = ""
# page-fallback = "some_page.html"


[advanced]
diff --git a/src/control_headers.rs b/src/control_headers.rs
index 2611e31..1fcfc4b 100644
--- a/src/control_headers.rs
+++ b/src/control_headers.rs
@@ -7,7 +7,7 @@ use hyper::{Body, Response};

// Cache-Control `max-age` variants
const MAX_AGE_ONE_HOUR: u64 = 60 * 60;
const MAX_AGE_ONE_DAY: u64 = 60 * 60 * 24_u64;
const MAX_AGE_ONE_DAY: u64 = 60 * 60 * 24;
const MAX_AGE_ONE_YEAR: u64 = 60 * 60 * 24 * 365;

// `Cache-Control` list of extensions
diff --git a/src/error_page.rs b/src/error_page.rs
index 9370ccf..949f066 100644
--- a/src/error_page.rs
+++ b/src/error_page.rs
@@ -8,8 +8,8 @@ use crate::Result;
pub fn error_response(
    method: &Method,
    status_code: &StatusCode,
    page404: &str,
    page50x: &str,
    page404: &[u8],
    page50x: &[u8],
) -> Result<Response<Body>> {
    tracing::warn!(method = ?method, status = status_code.as_u16(), error = ?status_code.to_owned());

@@ -36,8 +36,8 @@ pub fn error_response(
        | &StatusCode::RANGE_NOT_SATISFIABLE
        | &StatusCode::EXPECTATION_FAILED => {
            // Extra check for 404 status code and its HTML content
            if status_code == &StatusCode::NOT_FOUND {
                error_page_content = page404.to_owned();
            if status_code == &StatusCode::NOT_FOUND && !page404.is_empty() {
                error_page_content = String::from_utf8_lossy(page404).to_string();
            }
            status_code
        }
@@ -52,7 +52,9 @@ pub fn error_response(
        | &StatusCode::INSUFFICIENT_STORAGE
        | &StatusCode::LOOP_DETECTED => {
            // HTML content check for status codes 50x
            error_page_content = page50x.to_owned();
            if !page50x.is_empty() {
                error_page_content = String::from_utf8_lossy(page50x).to_string();
            }
            status_code
        }
        // other status codes
diff --git a/src/fallback_page.rs b/src/fallback_page.rs
index 51d8576..67c5ab6 100644
--- a/src/fallback_page.rs
+++ b/src/fallback_page.rs
@@ -2,9 +2,10 @@ use headers::{AcceptRanges, ContentLength, ContentType, HeaderMapExt};
use hyper::{Body, Response, StatusCode};
use mime_guess::mime;

/// Checks if a fallback response can be generated, i.e. if it is a GET request that would result in a 404 error and a fallback page is configured.
/// If a response can be generated, it is returned, else `None` is returned.
pub fn fallback_response(page_fallback: &str) -> Response<Body> {
/// Checks if a fallback response can be generated, i.e. if it is a `GET` request
/// that would result in a `404` error and a fallback page is configured.
/// If a response can be generated then is returned otherwise `None`.
pub fn fallback_response(page_fallback: &[u8]) -> Response<Body> {
    let body = Body::from(page_fallback.to_owned());
    let len = page_fallback.len() as u64;

diff --git a/src/handler.rs b/src/handler.rs
index 61eab62..dc02c39 100644
--- a/src/handler.rs
+++ b/src/handler.rs
@@ -16,9 +16,9 @@ pub struct RequestHandlerOpts {
    pub cors: Option<cors::Configured>,
    pub security_headers: bool,
    pub cache_control_headers: bool,
    pub page404: String,
    pub page50x: String,
    pub page_fallback: String,
    pub page404: Vec<u8>,
    pub page50x: Vec<u8>,
    pub page_fallback: Vec<u8>,
    pub basic_auth: String,

    // Advanced options
@@ -166,14 +166,14 @@ impl RequestHandler {
                }
                Err(status) => {
                    // Check for a fallback response
                    if !self.opts.page_fallback.is_empty()
                    if method == Method::GET
                        && status == StatusCode::NOT_FOUND
                        && method == Method::GET
                        && !self.opts.page_fallback.is_empty()
                    {
                        return Ok(fallback_page::fallback_response(&self.opts.page_fallback));
                    }

                    // Response error
                    // Otherwise return a response error
                    error_page::error_response(
                        method,
                        &status,
diff --git a/src/helpers.rs b/src/helpers.rs
index 66fc20e..c30f2e2 100644
--- a/src/helpers.rs
+++ b/src/helpers.rs
@@ -30,17 +30,14 @@ where
    }
}

/// Read the entire contents of a file into a string if valid or returns empty otherwise.
pub fn read_file_content(p: &str) -> String {
    if !p.is_empty() && Path::new(p).exists() {
        return fs::read_to_string(p).unwrap_or_default();
    }
    String::new()
}

/// Read the entire contents of a file into a bytes vector.
pub fn read_bytes(path: &Path) -> Result<Vec<u8>> {
    fs::read(path).with_context(|| format!("failed to read `{}`", path.display()))
    fs::read(path).with_context(|| format!("failed to read file `{}`", path.display()))
}

/// Read the entire contents of a file into a bytes vector or default to empty.
pub fn read_bytes_default(path: &Path) -> Vec<u8> {
    fs::read(path).unwrap_or_default()
}

/// Read an UTF-8 file from a specific path.
diff --git a/src/server.rs b/src/server.rs
index a39f298..38e5154 100644
--- a/src/server.rs
+++ b/src/server.rs
@@ -99,11 +99,11 @@ impl Server {
            .with_context(|| "root directory was not found or inaccessible")?;

        // Custom error pages content
        let page404 = helpers::read_file_content(&general.page404);
        let page50x = helpers::read_file_content(&general.page50x);
        let page404 = helpers::read_bytes_default(&general.page404);
        let page50x = helpers::read_bytes_default(&general.page50x);

        // Fallback page content
        let page_fallback = helpers::read_file_content(&general.page_fallback);
        let page_fallback = helpers::read_bytes_default(&general.page_fallback.unwrap_or_default());

        // Number of worker threads option
        let threads = self.threads;
@@ -171,7 +171,7 @@ impl Server {

            tcp_listener
                .set_nonblocking(true)
                .expect("cannot set non-blocking");
                .with_context(|| "failed to set TCP non-blocking mode")?;
            let listener = tokio::net::TcpListener::from_std(tcp_listener)
                .with_context(|| "failed to create tokio::net::TcpListener")?;
            let mut incoming = AddrIncoming::from_listener(listener).with_context(|| {
@@ -179,12 +179,21 @@ impl Server {
            })?;
            incoming.set_nodelay(true);

            let http2_tls_cert = match general.http2_tls_cert {
                Some(v) => v,
                _ => bail!("failed to initialize TLS because cert file missing"),
            };
            let http2_tls_key = match general.http2_tls_key {
                Some(v) => v,
                _ => bail!("failed to initialize TLS because key file missing"),
            };

            let tls = TlsConfigBuilder::new()
                .cert_path(&general.http2_tls_cert)
                .key_path(&general.http2_tls_key)
                .cert_path(&http2_tls_cert)
                .key_path(&http2_tls_key)
                .build()
                .with_context(|| {
                    "failed to initialize TLS, probably wrong cert/key or file missing"
                    "failed to initialize TLS probably because invalid cert or key file"
                })?;

            #[cfg(unix)]
diff --git a/src/settings/cli.rs b/src/settings/cli.rs
index 4889cef..75f5395 100644
--- a/src/settings/cli.rs
+++ b/src/settings/cli.rs
@@ -44,7 +44,7 @@ pub struct General {

    #[structopt(long, short = "d", default_value = "./public", env = "SERVER_ROOT")]
    /// Root directory path of static files.
    pub root: String,
    pub root: PathBuf,

    #[structopt(
        long,
@@ -52,7 +52,7 @@ pub struct General {
        env = "SERVER_ERROR_PAGE_50X"
    )]
    /// HTML file path for 50x errors. If the path is not specified or simply doesn't exist then the server will use a generic HTML error message.
    pub page50x: String,
    pub page50x: PathBuf,

    #[structopt(
        long,
@@ -60,11 +60,11 @@ pub struct General {
        env = "SERVER_ERROR_PAGE_404"
    )]
    /// HTML file path for 404 errors. If the path is not specified or simply doesn't exist then the server will use a generic HTML error message.
    pub page404: String,
    pub page404: PathBuf,

    #[structopt(long, default_value = "", env = "SERVER_FALLBACK_PAGE")]
    #[structopt(long, env = "SERVER_FALLBACK_PAGE")]
    /// HTML file path that is used for GET requests when the requested path doesn't exist. The fallback page is served with a 200 status code, useful when using client routers. If the path is not specified or simply doesn't exist then this feature will not be active.
    pub page_fallback: String,
    pub page_fallback: Option<PathBuf>,

    #[structopt(long, short = "g", default_value = "error", env = "SERVER_LOG_LEVEL")]
    /// Specify a logging level in lower case. Values: error, warn, info, debug or trace
@@ -98,23 +98,13 @@ pub struct General {
    /// Enable HTTP/2 with TLS support.
    pub http2: bool,

    #[structopt(
        long,
        required_if("http2", "true"),
        default_value = "",
        env = "SERVER_HTTP2_TLS_CERT"
    )]
    #[structopt(long, required_if("http2", "true"), env = "SERVER_HTTP2_TLS_CERT")]
    /// Specify the file path to read the certificate.
    pub http2_tls_cert: String,
    pub http2_tls_cert: Option<PathBuf>,

    #[structopt(
        long,
        required_if("http2", "true"),
        default_value = "",
        env = "SERVER_HTTP2_TLS_KEY"
    )]
    #[structopt(long, required_if("http2", "true"), env = "SERVER_HTTP2_TLS_KEY")]
    /// Specify the file path to read the private key.
    pub http2_tls_key: String,
    pub http2_tls_key: Option<PathBuf>,

    #[structopt(
        long,
diff --git a/src/settings/file.rs b/src/settings/file.rs
index bff1623..d0b565c 100644
--- a/src/settings/file.rs
+++ b/src/settings/file.rs
@@ -2,8 +2,8 @@

use headers::HeaderMap;
use serde::Deserialize;
use std::collections::BTreeSet;
use std::path::Path;
use std::{collections::BTreeSet, path::PathBuf};

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

@@ -53,7 +53,7 @@ pub struct General {
    // Address & Root dir
    pub host: Option<String>,
    pub port: Option<u16>,
    pub root: Option<String>,
    pub root: Option<PathBuf>,

    // Logging
    pub log_level: Option<LogLevel>,
@@ -65,13 +65,13 @@ pub struct General {
    pub compression: Option<bool>,

    // Error pages
    pub page404: Option<String>,
    pub page50x: Option<String>,
    pub page404: Option<PathBuf>,
    pub page50x: Option<PathBuf>,

    // HTTP/2 + TLS
    pub http2: Option<bool>,
    pub http2_tls_cert: Option<String>,
    pub http2_tls_key: Option<String>,
    pub http2_tls_cert: Option<PathBuf>,
    pub http2_tls_key: Option<PathBuf>,

    // Security headers
    pub security_headers: Option<bool>,
@@ -95,7 +95,7 @@ pub struct General {

    pub grace_period: Option<u8>,

    pub page_fallback: Option<String>,
    pub page_fallback: Option<PathBuf>,
}

/// Full server configuration
diff --git a/src/settings/mod.rs b/src/settings/mod.rs
index 65ebf9f..e168204 100644
--- a/src/settings/mod.rs
+++ b/src/settings/mod.rs
@@ -36,28 +36,28 @@ impl Settings {
        let opts = General::from_args();

        // Define the general CLI/file options
        let mut host = opts.host.to_owned();
        let mut host = opts.host;
        let mut port = opts.port;
        let mut root = opts.root.to_owned();
        let mut log_level = opts.log_level.to_owned();
        let mut root = opts.root;
        let mut log_level = opts.log_level;
        let mut config_file = opts.config_file.clone();
        let mut cache_control_headers = opts.cache_control_headers;
        let mut compression = opts.compression;
        let mut page404 = opts.page404.to_owned();
        let mut page50x = opts.page50x.to_owned();
        let mut page404 = opts.page404;
        let mut page50x = opts.page50x;
        let mut http2 = opts.http2;
        let mut http2_tls_cert = opts.http2_tls_cert.to_owned();
        let mut http2_tls_key = opts.http2_tls_key.to_owned();
        let mut http2_tls_cert = opts.http2_tls_cert;
        let mut http2_tls_key = opts.http2_tls_key;
        let mut security_headers = opts.security_headers;
        let mut cors_allow_origins = opts.cors_allow_origins.to_owned();
        let mut cors_allow_headers = opts.cors_allow_headers.to_owned();
        let mut cors_allow_origins = opts.cors_allow_origins;
        let mut cors_allow_headers = opts.cors_allow_headers;
        let mut directory_listing = opts.directory_listing;
        let mut directory_listing_order = opts.directory_listing_order;
        let mut basic_auth = opts.basic_auth.to_owned();
        let mut basic_auth = opts.basic_auth;
        let mut fd = opts.fd;
        let mut threads_multiplier = opts.threads_multiplier;
        let mut grace_period = opts.grace_period;
        let mut page_fallback = opts.page_fallback.to_owned();
        let mut page_fallback = opts.page_fallback;

        // Define the advanced file options
        let mut settings_advanced: Option<Advanced> = None;
@@ -79,14 +79,14 @@ impl Settings {

                // Assign the corresponding file option values
                if let Some(general) = settings.general {
                    if let Some(ref v) = general.host {
                        host = v.to_owned()
                    if let Some(v) = general.host {
                        host = v
                    }
                    if let Some(v) = general.port {
                        port = v
                    }
                    if let Some(ref v) = general.root {
                        root = v.to_owned()
                    if let Some(v) = general.root {
                        root = v
                    }
                    if let Some(ref v) = general.log_level {
                        log_level = v.name().to_lowercase();
@@ -97,20 +97,20 @@ impl Settings {
                    if let Some(v) = general.compression {
                        compression = v
                    }
                    if let Some(ref v) = general.page404 {
                        page404 = v.to_owned()
                    if let Some(v) = general.page404 {
                        page404 = v
                    }
                    if let Some(ref v) = general.page50x {
                        page50x = v.to_owned()
                    if let Some(v) = general.page50x {
                        page50x = v
                    }
                    if let Some(v) = general.http2 {
                        http2 = v
                    }
                    if let Some(ref v) = general.http2_tls_cert {
                        http2_tls_cert = v.to_owned()
                    if let Some(v) = general.http2_tls_cert {
                        http2_tls_cert = Some(v)
                    }
                    if let Some(ref v) = general.http2_tls_key {
                        http2_tls_key = v.to_owned()
                    if let Some(v) = general.http2_tls_key {
                        http2_tls_key = Some(v)
                    }
                    if let Some(v) = general.security_headers {
                        security_headers = v
@@ -139,8 +139,8 @@ impl Settings {
                    if let Some(v) = general.grace_period {
                        grace_period = v
                    }
                    if let Some(ref v) = general.page_fallback {
                        page_fallback = v.to_owned()
                    if let Some(v) = general.page_fallback {
                        page_fallback = Some(v)
                    }
                }