use std::borrow::Cow;
use axum::http::Uri;
use axum::http::{HeaderMap, Method, StatusCode, header};
use axum::response::{IntoResponse, Response};
const DEV_HINT: &str = r#"
filebrowser-ng
filebrowser-ng
The frontend has not been built yet.
In development, run just dev-web in a second terminal and open
http://localhost:8080 (Trunk serves the app and proxies /api to this backend).
If you hit this page directly from the backend, make sure Trunk's output
directory is visible — the server looks in target/web or $FBNG_DIST.
For the production single binary: just build.
"#;
/// Serve the frontend: static assets, with a fallback to index.html so that
/// client-side routes (e.g. `/s/`) work.
pub(super) async fn fallback(method: Method, uri: Uri, headers: HeaderMap) -> Response {
let path = uri.path();
if path.starts_with("/api/") {
return (StatusCode::NOT_FOUND, "unknown endpoint").into_response();
}
if method != Method::GET && method != Method::HEAD {
return StatusCode::METHOD_NOT_ALLOWED.into_response();
}
let rel = path.trim_start_matches('/');
let asset = if rel.is_empty() {
crate::assets::get_asset("index.html")
} else {
crate::assets::get_asset(rel)
};
match asset {
Some((bytes, mime, cache, etag)) => {
// The embedded assets carry a content hash, so a repeat visit can
// be answered without sending the body again.
if let Some(tag) = &etag
&& headers
.get(header::IF_NONE_MATCH)
.and_then(|v| v.to_str().ok())
.is_some_and(|v| v.split(',').any(|t| t.trim() == tag || t.trim() == "*"))
{
return (
StatusCode::NOT_MODIFIED,
[(header::CACHE_CONTROL, cache), (header::ETAG, tag.clone())],
)
.into_response();
}
let mut res = (
[(header::CONTENT_TYPE, mime), (header::CACHE_CONTROL, cache)],
body(bytes),
)
.into_response();
if let Some(tag) = etag
&& let Ok(v) = tag.parse()
{
res.headers_mut().insert(header::ETAG, v);
}
res
}
None => match crate::assets::get_asset("index.html") {
Some((bytes, mime, _, _)) => (
[
(header::CONTENT_TYPE, mime),
(header::CACHE_CONTROL, "no-cache".to_string()),
],
body(bytes),
)
.into_response(),
None => DEV_HINT.into_response(),
},
}
}
/// Asset bytes as a response body, without copying the embedded ones.
fn body(bytes: Cow<'static, [u8]>) -> axum::body::Bytes {
match bytes {
Cow::Borrowed(b) => axum::body::Bytes::from_static(b),
Cow::Owned(v) => axum::body::Bytes::from(v),
}
}