Skip to content

Commit 308d842

Browse files
authored
Merge pull request #38 from ThorstenHans/feature/custom-404
2 parents 10bea9e + 27cd4d8 commit 308d842

File tree

2 files changed

+77
-2
lines changed

2 files changed

+77
-2
lines changed

readme.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,27 @@ value is set, the default behavior is to return a 404 Not Found response. This b
5252
is useful for Single Page Applications that use view routers on the front-end like React and Vue.
5353

5454
```toml
55-
# For more on configuring a component, see: https://spin.fermyon.dev/configuration/
55+
# For more on configuring a component, see: https://developer.fermyon.com/spin/writing-apps#adding-environment-variables-to-components
5656
[[component]]
5757
source = "target/wasm32-wasi/release/spin_static_fs.wasm"
5858
id = "fs"
5959
files = [{ source = "test", destination = "/" }]
6060
environment = { FALLBACK_PATH = "index.html" }
6161
```
6262

63+
### Using a custom 404 document
64+
65+
You can configure a `CUSTOM_404_PATH` environment variable and point to a file that will be served instead of returning a plain 404 Not Found response. Consider the following sample where the `spin-fileserver` component is configured to serve all files from the `test` folder. The desired page must exist in the `test` folder to send a custom 404 HTML page (here, `404.html`) instead of a plain 404 Not Found response.
66+
67+
```toml
68+
# For more on configuring a component, see: https://developer.fermyon.com/spin/writing-apps#adding-environment-variables-to-components
69+
[[component]]
70+
source = "target/wasm32-wasi/release/spin_static_fs.wasm"
71+
id = "fs"
72+
files = [{ source = "test", destination = "/" }]
73+
environment = { CUSTOM_404_PATH = "404.html" }
74+
```
75+
6376
### Building from source and using
6477

6578
Prerequisites:

src/lib.rs

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ const BROTLI_ENCODING: &str = "br";
2828
const PATH_INFO_HEADER: &str = "spin-path-info";
2929
// Environment variable for the fallback path
3030
const FALLBACK_PATH_ENV: &str = "FALLBACK_PATH";
31+
/// Environment variable for the custom 404 path
32+
const CUSTOM_404_PATH_ENV: &str = "CUSTOM_404_PATH";
3133
/// Directory fallback path (trying to map `/about/` -> `/about/index.html`).
3234
const DIRECTORY_FALLBACK_PATH: &str = "index.html";
3335

@@ -112,7 +114,16 @@ impl FileServer {
112114
}
113115
}
114116

115-
// return the path if it exists
117+
if path.exists() {
118+
return Some(path);
119+
}
120+
121+
// check if user configured a custom 404 path
122+
// if so, check if that path exists and return it instead of sending a plain 404
123+
if let Ok(custom_404) = std::env::var(CUSTOM_404_PATH_ENV) {
124+
path = PathBuf::from(custom_404);
125+
}
126+
116127
if path.exists() {
117128
Some(path)
118129
} else {
@@ -198,6 +209,8 @@ impl FileServer {
198209

199210
#[cfg(test)]
200211
mod tests {
212+
use std::{fs, path::Path};
213+
201214
use http::header::{ACCEPT_ENCODING, IF_NONE_MATCH};
202215

203216
use super::*;
@@ -298,6 +311,55 @@ mod tests {
298311
assert_eq!(rsp.status, 404);
299312
}
300313

314+
#[test]
315+
fn test_serve_custom_404() {
316+
// reuse existing asset as custom 404 doc
317+
let custom_404_path = "hello-test.txt";
318+
let expected_status = 200;
319+
let expected_body =
320+
fs::read(Path::new(custom_404_path)).expect("Could not read custom 404 file");
321+
322+
std::env::set_var(CUSTOM_404_PATH_ENV, custom_404_path);
323+
324+
let req = spin_http::Request {
325+
method: spin_http::Method::Get,
326+
uri: "http://thisistest.com".to_string(),
327+
headers: vec![(
328+
PATH_INFO_HEADER.to_string(),
329+
"not-existent-file".to_string(),
330+
)],
331+
params: vec![],
332+
body: None,
333+
};
334+
let rsp = <super::SpinHttp as spin_http::SpinHttp>::handle_http_request(req);
335+
std::env::remove_var(CUSTOM_404_PATH_ENV);
336+
assert_eq!(rsp.status, expected_status);
337+
assert_eq!(rsp.body, expected_body.into());
338+
}
339+
340+
#[test]
341+
fn test_serve_non_existing_custom_404() {
342+
// provide a invalid path
343+
let custom_404_path = "non-existing-404.html";
344+
let expected_status = 404;
345+
346+
std::env::set_var(CUSTOM_404_PATH_ENV, custom_404_path);
347+
348+
let req = spin_http::Request {
349+
method: spin_http::Method::Get,
350+
uri: "http://thisistest.com".to_string(),
351+
headers: vec![(
352+
PATH_INFO_HEADER.to_string(),
353+
"not-existent-file".to_string(),
354+
)],
355+
params: vec![],
356+
body: None,
357+
};
358+
let rsp = <super::SpinHttp as spin_http::SpinHttp>::handle_http_request(req);
359+
std::env::remove_var(CUSTOM_404_PATH_ENV);
360+
assert_eq!(rsp.status, expected_status);
361+
}
362+
301363
#[test]
302364
fn test_serve_file_not_found_with_fallback_path() {
303365
//NOTE: this test must not run in parallel to other tests because of it's use of an environment variable

0 commit comments

Comments
 (0)