use actix_web::http::header::{CACHE_CONTROL, CONTENT_TYPE}; use actix_web::{HttpRequest, HttpResponse}; pub struct Asset { pub path: &'static str, pub content_type: &'static str, pub bytes: &'static [u8], } include!(concat!(env!("OUT_DIR"), "/assets.rs")); pub fn count() -> usize { ASSETS.len() } /// 非 SPA 的首段路径:这些前缀只服务于接口,拼错时给 JSON 404, /// 避免把已移除的旧路径回落到 index.html 变成 200。 const API_PREFIXES: [&str; 2] = ["api", "auth"]; pub async fn serve(request: HttpRequest) -> HttpResponse { let raw = request.path().trim_start_matches('/'); let path = if raw.is_empty() { "index.html" } else { raw }; let looks_like_api = 'inline_looks_like_api: { if path.starts_with(".well-known/") { break 'inline_looks_like_api true; } let first = path.split('/').next().unwrap_or_default(); API_PREFIXES.contains(&first) }; if looks_like_api { return api_not_found(); } if lookup(path).is_none() && path .rsplit('/') .next() .and_then(|name| name.rsplit_once('.')) .is_some_and(|(_, ext)| { !ext.is_empty() && ext.len() <= 5 && ext.chars().all(|c| c.is_ascii_alphanumeric()) }) { return api_not_found(); } match lookup(path).or_else(|| lookup("index.html")) { Some(asset) => { let cache_control = if asset.path.starts_with("assets/") { "public, max-age=31536000, immutable" } else { "no-cache" }; HttpResponse::Ok() .insert_header((CONTENT_TYPE, asset.content_type)) .insert_header((CACHE_CONTROL, cache_control)) .body(asset.bytes) } None => HttpResponse::ServiceUnavailable() .insert_header((CONTENT_TYPE, "text/plain; charset=utf-8")) .body( "view/dist has no build output; run `just identity-web-build` and rebuild kenapi\n", ), } } fn lookup(path: &str) -> Option<&'static Asset> { ASSETS.iter().find(|asset| asset.path == path) } fn api_not_found() -> HttpResponse { HttpResponse::NotFound().json( export::rest::AppResponseData::::err(404, "unknown api route") .with_error_code("not_found"), ) }