From 1fa92618230cc5f4b8acfa34fe0e387de6866fb8 Mon Sep 17 00:00:00 2001 From: Jose Quintana <1700322+joseluisq@users.noreply.github.com> Date: Wed, 8 Nov 2023 23:47:43 +0100 Subject: [PATCH] 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). --- 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 directory path of static files [env: SERVER_ROOT=] [default: ./public] --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 - 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 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 @@ -76,7 +76,7 @@ Options: -q, --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 - 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 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[=] 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> { 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 = [ "", 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")] -- libgit2 1.7.2