Skip to content

Commit 95f70b1

Browse files
author
Justin Pflueger
authored
Merge pull request #27 from jpflueger/feature/fallback-path
Add fallback path capability
2 parents 208628b + ae24db6 commit 95f70b1

File tree

3 files changed

+57
-8
lines changed

3 files changed

+57
-8
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@ lint:
1212

1313
.PHONY: test-unit
1414
test-unit:
15-
RUST_LOG=$(LOG_LEVEL) cargo test --target=$$(rustc -vV | sed -n 's|host: ||p')
15+
RUST_LOG=$(LOG_LEVEL) cargo test --target=$$(rustc -vV | sed -n 's|host: ||p') -- --test-threads=1

readme.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,22 @@ Currently, this file server has a single cache header that it can set through
4444
the `CACHE_CONTROL` environment variable. If no value is set, the default
4545
`max-age=60` is used instead for all media types.
4646

47+
### Setting the fallback path
48+
49+
You can configure a `FALLBACK_PATH` environment variable that points to a file that
50+
will be returned instead of the default 404 Not Found response. If no environment
51+
value is set, the default behavior is to return a 404 Not Found response. This behavior
52+
is useful for Single Page Applications that use view routers on the front-end like React and Vue.
53+
54+
```toml
55+
# For more on configuring a component, see: https://spin.fermyon.dev/configuration/
56+
[[component]]
57+
source = "target/wasm32-wasi/release/spin_static_fs.wasm"
58+
id = "fs"
59+
files = [{ source = "test", destination = "/" }]
60+
environment = { FALLBACK_PATH = "index.html" }
61+
```
62+
4763
### Building from source and using
4864

4965
Prerequisites:

src/lib.rs

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ const BROTLI_LEVEL: u32 = 3;
2525
const BROTLI_ENCODING: &str = "br";
2626
/// The path info header.
2727
const PATH_INFO_HEADER: &str = "spin-path-info";
28+
// Environment variable for the fallback path
29+
const FALLBACK_PATH_ENV: &str = "FALLBACK_PATH";
2830

2931
/// Common Content Encodings
3032
#[derive(Debug, Eq, PartialEq)]
@@ -77,12 +79,19 @@ fn serve(req: Request) -> Result<Response> {
7779
_ => path,
7880
};
7981

82+
// read from the fallback path if the variable exists
8083
let body = match FileServer::read(path, &enc) {
84+
// requested file was found
8185
Ok(b) => Some(b),
82-
Err(e) => {
83-
eprintln!("Cannot read file: {:?}", e);
84-
return not_found();
85-
}
86+
Err(e) => match std::env::var(FALLBACK_PATH_ENV) {
87+
// try to read the fallback path
88+
Ok(fallback_path) => FileServer::read(fallback_path.as_str(), &enc).ok(),
89+
// fallback path config not found
90+
Err(_) => {
91+
eprintln!("Cannot read file: {e:?}");
92+
None
93+
}
94+
},
8695
};
8796

8897
let etag = FileServer::get_etag(body.clone());
@@ -149,10 +158,14 @@ impl FileServer {
149158
.ok_or(anyhow!("cannot get headers for response"))?;
150159
FileServer::append_headers(path, enc, etag, headers)?;
151160

152-
if etag == if_none_match {
153-
return Ok(res.status(StatusCode::NOT_MODIFIED).body(None)?);
161+
if body.is_some() {
162+
if etag == if_none_match {
163+
return Ok(res.status(StatusCode::NOT_MODIFIED).body(None)?);
164+
}
165+
Ok(res.status(StatusCode::OK).body(body)?)
166+
} else {
167+
not_found()
154168
}
155-
Ok(res.status(StatusCode::OK).body(body)?)
156169
}
157170

158171
fn get_etag(body: Option<Bytes>) -> String {
@@ -264,6 +277,26 @@ mod tests {
264277
assert_eq!(rsp.status, 404);
265278
}
266279

280+
#[test]
281+
fn test_serve_file_not_found_with_fallback_path() {
282+
//NOTE: this test must not run in parallel to other tests because of it's use of an environment variable
283+
// hence the `--test-threads=1` in the `make test` target
284+
std::env::set_var(FALLBACK_PATH_ENV, "hello-test.txt");
285+
let req = spin_http::Request {
286+
method: spin_http::Method::Get,
287+
uri: "http://thisistest.com".to_string(),
288+
headers: vec![(
289+
PATH_INFO_HEADER.to_string(),
290+
"not-existent-file".to_string(),
291+
)],
292+
params: vec![],
293+
body: None,
294+
};
295+
let rsp = <super::SpinHttp as spin_http::SpinHttp>::handle_http_request(req);
296+
std::env::remove_var(FALLBACK_PATH_ENV);
297+
assert_eq!(rsp.status, 200);
298+
}
299+
267300
#[test]
268301
fn test_serve_index() {
269302
let req = spin_http::Request {

0 commit comments

Comments
 (0)