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(-)
@@ -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>]
@@ -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
@@ -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.
@@ -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};
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(
);
let mut error_page_content = String::new();
let mut page_content = String::new();
let status_code = match status_code {
&StatusCode::BAD_REQUEST
@@ -48,8 +49,18 @@ pub fn error_response(
| &StatusCode::RANGE_NOT_SATISFIABLE
| &StatusCode::EXPECTATION_FAILED => {
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 => {
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);
@@ -59,9 +59,9 @@ pub struct RequestHandlerOpts {
pub cache_control_headers: bool,
pub page404: Vec<u8>,
pub page404: PathBuf,
pub page50x: Vec<u8>,
pub page50x: PathBuf,
#[cfg(feature = "fallback-page")]
#[cfg_attr(docsrs, doc(cfg(feature = "fallback-page")))]
@@ -159,9 +159,28 @@ impl Server {
let root_dir = helpers::get_valid_dirpath(&general.root)
.with_context(|| "root directory was not found or inaccessible")?;
let page404 = helpers::read_bytes_default(&general.page404);
let page50x = helpers::read_bytes_default(&general.page50x);
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()
);
}
#[cfg(feature = "fallback-page")]
@@ -101,20 +101,16 @@ pub struct General {
pub root: PathBuf,
#[arg(
long,
default_value = "./public/50x.html",
env = "SERVER_ERROR_PAGE_50X"
)]
#[arg(long, default_value = "./50x.html", env = "SERVER_ERROR_PAGE_50X")]
pub page50x: PathBuf,
#[arg(
long,
default_value = "./public/404.html",
env = "SERVER_ERROR_PAGE_404"
)]
#[arg(long, default_value = "./404.html", env = "SERVER_ERROR_PAGE_404")]
pub page404: PathBuf,
#[cfg(feature = "fallback-page")]