Repository: static-web-server/static-web-server (language: rust)

Issue:
fallback page not enabled
### Search for duplicate issues

- [x] I already searched, and this issue is not a duplicate.

### Issue scope

Docker / Kubernetes

### Describe the bug

Hi,

is set the fallback page via env inside the docker
  -e 'SERVER_FALLBACK_PAGE'='./index.html'
but while startup i get the info:
INFO static_web_server::info: fallback page: enabled=false, value="./index.html"
i cannot find a flag to enable it in the docs.
the code also does not look as if it is an extra flag.


### How to reproduce it

set:
  -e 'SERVER_FALLBACK_PAGE'='./index.html'
and startup

### Expected behavior

it should be switched on, and the routing should be done by my spa.

### Complementary information

i am using the latest official docker build without any extra tags except the 2

### Build target

Docker linux/amd64

### Environment and specs

docker run
  -d
  --name='keycontrol-frontend'
  --net='bridge'
  --pids-limit 2048
  -e TZ="Europe/Berlin"
  -e HOST_OS="Unraid"
  -e HOST_HOSTNAME="srv"
  -e HOST_CONTAINERNAME="keycontrol-frontend"
  -e 'SERVER_FALLBACK_PAGE'='./index.html'
  -l net.unraid.docker.managed=dockerman
  -p '8654:80/tcp'
  -v '/mnt/hypervisor/appdata/keycontrol/frontend':'/public':'rw'
  -v '/mnt/hypervisor/appdata/keycontrol/doorbird/':'/public/doorbirdfiles':'rw' 'joseluisq/static-web-server:2'
3995520e8dbd1ad7ec02327fd091dd828be86c201c9addfe2d2b0d7d5b27b44a


### Additional context

_No response_


Discussion / hints:
Thanks for reporting but this is not an issue per se.

Unfortunately, the fallback page feature docs and SWS are not clear enough.
Basically, the file path for `SERVER_FALLBACK_PAGE` (or its CLI/config option) is **not** under the root but is an independent path. That's why this example below works:

```sh
static-web-server \
    --port 8787 \
    --root ./my-public-dir \
    --page-fallback ./my-public-dir/index.html
```

See docs https://static-web-server.net/features/error-pages/#fallback-page-for-use-with-client-routers

Additionally, SWS tells nothing if a path does not exist so we will improve that via a debug log entry and clarify the docs.
hey thanks a lot i was confused that i have to use ./public/index.html and it didn't work with /public/index.html (without dot).
now it works for me too.

Fix patch:
diff --git a/docs/content/configuration/command-line-arguments.md b/docs/content/configuration/command-line-arguments.md
index c0e126bb..eaf93fd3 100644
--- a/docs/content/configuration/command-line-arguments.md
+++ b/docs/content/configuration/command-line-arguments.md
@@ -35,7 +35,7 @@ Options:
       --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. 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: ]
+          A HTML file path (not relative to the root) 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 doesn't exist then the feature is not activated [env: SERVER_FALLBACK_PAGE=] [default: ]
   -g, --log-level <LOG_LEVEL>
           Specify a logging level in lower case. Values: error, warn, info, debug or trace [env: SERVER_LOG_LEVEL=] [default: error]
   -c, --cors-allow-origins <CORS_ALLOW_ORIGINS>
@@ -89,7 +89,7 @@ Options:
       --log-forwarded-for [<LOG_FORWARDED_FOR>]
           Log the X-Forwarded-For header for remote IP information [env: SERVER_LOG_FORWARDED_FOR=] [default: false] [possible values: true, false]
       --trusted-proxies <TRUSTED_PROXIES>
-          A comma separated list of IP addresses to accept the X-Forwarded-For header from. Empty means trust all IPs [env: SERVER_TRUSTED_PROXIES] [default: ""]
+          List of IPs to use X-Forwarded-For from. The default is to trust all [env: SERVER_TRUSTED_PROXIES=]
       --redirect-trailing-slash [<REDIRECT_TRAILING_SLASH>]
           Check for a trailing slash in the requested directory URI and redirect permanently (308) to the same path with a trailing slash suffix if it is missing [env: SERVER_REDIRECT_TRAILING_SLASH=] [default: true] [possible values: true, false]
       --ignore-hidden-files [<IGNORE_HIDDEN_FILES>]
diff --git a/docs/content/configuration/environment-variables.md b/docs/content/configuration/environment-variables.md
index dc886b3c..60d4af02 100644
--- a/docs/content/configuration/environment-variables.md
+++ b/docs/content/configuration/environment-variables.md
@@ -48,7 +48,7 @@ HTML file path for 50x errors. If the path is not specified or simply doesn't ex
 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.
+A HTML file path (not relative to the root) 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 doesn't exist then the feature is not activated.
 
 ### SERVER_THREADS_MULTIPLIER
 The number of worker threads multiplier will be multiplied by the number of system CPUs using the formula: `worker threads = number of CPUs * n` where `n` is the value that changes here. When the multiplier value is 0 or 1 then the `number of CPUs` is used. The number of worker threads result should be a number between 1 and 32,768 though it is advised to keep this value on the smaller side. Default one thread per core.
diff --git a/docs/content/features/error-pages.md b/docs/content/features/error-pages.md
index 94f99319..ccf936f2 100644
--- a/docs/content/features/error-pages.md
+++ b/docs/content/features/error-pages.md
@@ -19,7 +19,12 @@ static-web-server \
 
 ## Fallback Page for use with Client Routers
 
-An 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 like `React Router` or similar. If the path is not specified or simply doesn't exist then this feature will not be active.
+It is possible to provide a HTML file to be used as fallback page when `GET` request paths dont exist.
+The fallback page will be served with a `200` status code, useful when using client routers like `React Router` or similar.
+If the path is not specified or simply doesn't exist then this feature will not be activated.
+
+!!! info "The fallback page path is not relative to the root"
+    The fallback page is an independent path, so provide a valid relative or absolute path.
 
 It can be set with the `SERVER_FALLBACK_PAGE` environment variable or with the CLI argument `--page-fallback`.
 
diff --git a/src/fallback_page.rs b/src/fallback_page.rs
index 215f0039..89630a1c 100644
--- a/src/fallback_page.rs
+++ b/src/fallback_page.rs
@@ -14,13 +14,22 @@ use std::path::Path;
 use crate::{handler::RequestHandlerOpts, helpers, http_ext::MethodExt, Error};
 
 /// Initializes fallback page processing
-pub(crate) fn init(path: &Path, handler_opts: &mut RequestHandlerOpts) {
-    handler_opts.page_fallback = helpers::read_bytes_default(path);
+pub(crate) fn init(file_path: &Path, handler_opts: &mut RequestHandlerOpts) {
+    let found = file_path.is_file();
+    if found {
+        handler_opts.page_fallback =
+            String::from_utf8_lossy(&helpers::read_bytes_default(file_path))
+                .trim()
+                .as_bytes()
+                .to_owned();
+    } else {
+        tracing::debug!("fallback page path not found or not a regular file");
+    }
 
     server_info!(
         "fallback page: enabled={}, value=\"{}\"",
-        !handler_opts.page_fallback.is_empty(),
-        path.to_string_lossy()
+        found,
+        file_path.display()
     );
 }
 
diff --git a/src/settings/cli.rs b/src/settings/cli.rs
index d4ea5059..7935828d 100644
--- a/src/settings/cli.rs
+++ b/src/settings/cli.rs
@@ -116,7 +116,7 @@ pub struct General {
     #[cfg(feature = "fallback-page")]
     #[cfg_attr(docsrs, doc(cfg(feature = "fallback-page")))]
     #[arg(long, default_value = "", value_parser = value_parser_pathbuf, 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.
+    /// A HTML file path (not relative to the root) 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 doesn't exist then the feature is not activated.
     pub page_fallback: PathBuf,
 
     #[arg(long, short = 'g', default_value = "error", env = "SERVER_LOG_LEVEL")]


Test patch: