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), } }