index : static-web-server.git

ascending towards madness

author Jose Quintana <1700322+joseluisq@users.noreply.github.com> 2023-11-08 22:47:43.0 +00:00:00
committer GitHub <noreply@github.com> 2023-11-08 22:47:43.0 +00:00:00
commit
1fa92618230cc5f4b8acfa34fe0e387de6866fb8 [patch]
tree
b3d3d9899d4afb9e387f0dd35f67e6349baec29f
parent
02c6d3e795990501717108a35ab0b452872d0898
download
1fa92618230cc5f4b8acfa34fe0e387de6866fb8.tar.gz

refactor: load 404/50x error pages content at runtime (#284)

It loads the HTML 404 (`--page404`) and 50x (`--page50x`) error page
content at runtime. This allows changing the content of those HTML
files on demand without restarting the server.

Previously, the error pages were loaded at start-up time (basically
they were static content).

**Some additional improvements:**

- If a relative path is used then it will be resolved under the root directory.
- The default error page values have been changed:
  - `--page50x=./50x.html`
  - `--page404=./404.html`

In case paths are not found then the server defaults to a generic HTML
message (as before).

Diff

 docs/content/configuration/command-line-arguments.md |  6 +--
 docs/content/configuration/config-file.md            |  5 +-
 docs/content/configuration/environment-variables.md  |  9 +++-
 src/error_page.rs                                    | 43 +++++++++++++++------
 src/handler.rs                                       |  4 +-
 src/server.rs                                        | 25 ++++++++++--
 src/settings/cli.rs                                  | 20 ++++------
 7 files changed, 76 insertions(+), 36 deletions(-)

diff --git a/docs/content/configuration/command-line-arguments.md b/docs/content/configuration/command-line-arguments.md
index f854ad4..eb9c341 100644
--- a/docs/content/configuration/command-line-arguments.md
+++ b/docs/content/configuration/command-line-arguments.md
@@ -28,9 +28,9 @@ Options:
  -d, --root <ROOT>
          Root directory path of static files [env: SERVER_ROOT=] [default: ./public]
      --page50x <PAGE50X>
          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 [env: SERVER_ERROR_PAGE_50X=] [default: ./public/50x.html]
          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. If a relative path is used then it will be resolved under the root directory [env: SERVER_ERROR_PAGE_50X=] [default: ./50x.html]
      --page404 <PAGE404>
          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 [env: SERVER_ERROR_PAGE_404=] [default: ./public/404.html]
          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. If a relative path is used then it will be resolved under the root directory [env: SERVER_ERROR_PAGE_404=] [default: ./404.html]
      --page-fallback <PAGE_FALLBACK>
          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 [env: SERVER_FALLBACK_PAGE=] [default: ]
  -g, --log-level <LOG_LEVEL>
@@ -76,7 +76,7 @@ Options:
  -q, --grace-period <GRACE_PERIOD>
          Defines a grace period in seconds after a `SIGTERM` signal is caught which will delay the server before to shut it down gracefully. The maximum value is 255 seconds [env: SERVER_GRACE_PERIOD=] [default: 0]
  -w, --config-file <CONFIG_FILE>
          Server TOML configuration file path [env: SERVER_CONFIG_FILE=]
          Server TOML configuration file path [env: SERVER_CONFIG_FILE=] [default: ./config.toml]
      --log-remote-address[=<LOG_REMOTE_ADDRESS>]
          Log incoming requests information along with its remote address if available using the `info` log level [env: SERVER_LOG_REMOTE_ADDRESS=] [default: false] [possible values: true, false]
      --redirect-trailing-slash[=<REDIRECT_TRAILING_SLASH>]
diff --git a/docs/content/configuration/config-file.md b/docs/content/configuration/config-file.md
index 4b29f15..638c580 100644
--- a/docs/content/configuration/config-file.md
+++ b/docs/content/configuration/config-file.md
@@ -26,8 +26,9 @@ cache-control-headers = true
compression = true

#### Error pages
page404 = "./public/404.html"
page50x = "./public/50x.html"
# Note: If a relative path is used then it will be resolved under the root directory.
page404 = "./404.html"
page50x = "./50x.html"

#### HTTP/2 + TLS
http2 = false
diff --git a/docs/content/configuration/environment-variables.md b/docs/content/configuration/environment-variables.md
index a32308a..bcb0da4 100644
--- a/docs/content/configuration/environment-variables.md
+++ b/docs/content/configuration/environment-variables.md
@@ -31,10 +31,12 @@ Specify a logging level in lower case. Possible values are `error`, `warn`, `inf
Log incoming requests information along with its Remote Address (IP) if available using the `info` log level. Default `false`.

### 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. Default `./public/404.html`.
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.
If a relative path is used then it will be resolved under the root directory. Default `./404.html`.

### 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. Default `./public/50x.html`
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.
If a relative path is used then it will be resolved under the root directory. Default `./50x.html`

### 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 (E.g `React Router`). If the path is not specified or simply doesn't exist then this feature will not be active.
@@ -110,10 +112,13 @@ Activate the health endpoint.

### SERVER_INDEX_FILES
List of files that will be used as an index for requests ending with the slash character (‘/’). Files are checked in the specified order. Default `index.html`.

### SERVER_MAINTENANCE_MODE
Enable the server's maintenance mode functionality.

### SERVER_MAINTENANCE_MODE_STATUS
Provide a custom HTTP status code when entering into maintenance mode. Default `503`.

### SERVER_MAINTENANCE_MODE_FILE
Provide a custom maintenance mode HTML file. If not provided then a generic message will be displayed.

diff --git a/src/error_page.rs b/src/error_page.rs
index e97211b..f86c8c5 100644
--- a/src/error_page.rs
+++ b/src/error_page.rs
@@ -9,16 +9,17 @@
use headers::{AcceptRanges, ContentLength, ContentType, HeaderMapExt};
use hyper::{Body, Method, Response, StatusCode, Uri};
use mime_guess::mime;
use std::path::Path;

use crate::{exts::http::MethodExt, Result};
use crate::{exts::http::MethodExt, helpers, Result};

/// It returns a HTTP error response which also handles available `404` or `50x` HTML content.
pub fn error_response(
    uri: &Uri,
    method: &Method,
    status_code: &StatusCode,
    page404: &[u8],
    page50x: &[u8],
    page404: &Path,
    page50x: &Path,
) -> Result<Response<Body>> {
    tracing::warn!(
        method = ?method, uri = ?uri, status = status_code.as_u16(),
@@ -26,7 +27,7 @@ pub fn error_response(
    );

    // Check for 4xx/50x status codes and handle their corresponding HTML content
    let mut error_page_content = String::new();
    let mut page_content = String::new();
    let status_code = match status_code {
        // 4xx
        &StatusCode::BAD_REQUEST
@@ -48,8 +49,18 @@ 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 && !page404.is_empty() {
                error_page_content = String::from_utf8_lossy(page404).to_string();
            if status_code == &StatusCode::NOT_FOUND {
                if page404.is_file() {
                    page_content = String::from_utf8_lossy(&helpers::read_bytes_default(page404))
                        .to_string()
                        .trim()
                        .to_owned();
                } else {
                    tracing::debug!(
                        "page404 file path not found or not a regular file: {}",
                        page404.display()
                    );
                }
            }
            status_code
        }
@@ -64,8 +75,16 @@ pub fn error_response(
        | &StatusCode::INSUFFICIENT_STORAGE
        | &StatusCode::LOOP_DETECTED => {
            // HTML content check for status codes 50x
            if !page50x.is_empty() {
                error_page_content = String::from_utf8_lossy(page50x).to_string();
            if page50x.is_file() {
                page_content = String::from_utf8_lossy(&helpers::read_bytes_default(page50x))
                    .to_string()
                    .trim()
                    .to_owned();
            } else {
                tracing::debug!(
                    "page50x file path not found or not a regular file: {}",
                    page50x.display()
                );
            }
            status_code
        }
@@ -73,8 +92,8 @@ pub fn error_response(
        _ => status_code,
    };

    if error_page_content.is_empty() {
        error_page_content = [
    if page_content.is_empty() {
        page_content = [
            "<html><head><title>",
            status_code.as_str(),
            " ",
@@ -89,10 +108,10 @@ pub fn error_response(
    }

    let mut body = Body::empty();
    let len = error_page_content.len() as u64;
    let len = page_content.len() as u64;

    if !method.is_head() {
        body = Body::from(error_page_content)
        body = Body::from(page_content)
    }

    let mut resp = Response::new(body);
diff --git a/src/handler.rs b/src/handler.rs
index 6525711..2767c43 100644
--- a/src/handler.rs
+++ b/src/handler.rs
@@ -59,9 +59,9 @@ pub struct RequestHandlerOpts {
    /// Cache control headers feature.
    pub cache_control_headers: bool,
    /// Page for 404 errors.
    pub page404: Vec<u8>,
    pub page404: PathBuf,
    /// Page for 50x errors.
    pub page50x: Vec<u8>,
    pub page50x: PathBuf,
    /// Page fallback feature.
    #[cfg(feature = "fallback-page")]
    #[cfg_attr(docsrs, doc(cfg(feature = "fallback-page")))]
diff --git a/src/server.rs b/src/server.rs
index a98e97c..e13e8d6 100644
--- a/src/server.rs
+++ b/src/server.rs
@@ -159,9 +159,28 @@ impl Server {
        let root_dir = helpers::get_valid_dirpath(&general.root)
            .with_context(|| "root directory was not found or inaccessible")?;

        // Custom error pages content
        let page404 = helpers::read_bytes_default(&general.page404);
        let page50x = helpers::read_bytes_default(&general.page50x);
        // Custom HTML error page files
        // NOTE: in the case of relative paths, they're joined to the root directory
        let mut page404 = general.page404;
        if page404.is_relative() && !page404.starts_with(&root_dir) {
            page404 = root_dir.join(page404);
        }
        if !page404.is_file() {
            tracing::debug!(
                "404 file path not found or not a regular file: {}",
                page404.display()
            );
        }
        let mut page50x = general.page50x;
        if page50x.is_relative() && !page50x.starts_with(&root_dir) {
            page50x = root_dir.join(page50x);
        }
        if !page50x.is_file() {
            tracing::debug!(
                "50x file path not found or not a regular file: {}",
                page50x.display()
            );
        }

        // Fallback page option
        #[cfg(feature = "fallback-page")]
diff --git a/src/settings/cli.rs b/src/settings/cli.rs
index 7bc1df9..71eaa2a 100644
--- a/src/settings/cli.rs
+++ b/src/settings/cli.rs
@@ -101,20 +101,16 @@ pub struct General {
    /// Root directory path of static files.
    pub root: PathBuf,

    #[arg(
        long,
        default_value = "./public/50x.html",
        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.
    #[arg(long, default_value = "./50x.html", 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.
    /// If a relative path is used then it will be resolved under the root directory.
    pub page50x: PathBuf,

    #[arg(
        long,
        default_value = "./public/404.html",
        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.
    #[arg(long, default_value = "./404.html", 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.
    /// If a relative path is used then it will be resolved under the root directory.
    pub page404: PathBuf,

    #[cfg(feature = "fallback-page")]