mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-10-27 16:00:49 +08:00
fix(web): 完善后端 404 响应并修复 /api 未匹配路由被静态兜底遮蔽
- 静态资源缺失时返回 404 HTML,移除"前端未构建"占位页
- /api 未匹配路由返回统一的 404 JSON
- 静态兜底由 /{*path} 改为 fallback_service,避免在 axum 0.8 下遮蔽嵌套 /api 的 fallback 并导致鉴权被绕过
This commit is contained in:
@@ -4,9 +4,11 @@ use axum::{
|
|||||||
routing::{delete, put},
|
routing::{delete, put},
|
||||||
};
|
};
|
||||||
use axum::{
|
use axum::{
|
||||||
|
http::StatusCode,
|
||||||
middleware,
|
middleware,
|
||||||
|
response::IntoResponse,
|
||||||
routing::{any, get, patch, post},
|
routing::{any, get, patch, post},
|
||||||
Router,
|
Json, Router,
|
||||||
};
|
};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tower_http::{
|
use tower_http::{
|
||||||
@@ -363,6 +365,7 @@ pub fn create_router(state: Arc<AppState>) -> Router {
|
|||||||
.merge(protected_routes)
|
.merge(protected_routes)
|
||||||
.merge(stream_routes)
|
.merge(stream_routes)
|
||||||
.merge(upload_routes)
|
.merge(upload_routes)
|
||||||
|
.fallback(api_not_found)
|
||||||
.layer(middleware::from_fn_with_state(
|
.layer(middleware::from_fn_with_state(
|
||||||
state.clone(),
|
state.clone(),
|
||||||
auth_middleware,
|
auth_middleware,
|
||||||
@@ -384,3 +387,14 @@ pub fn create_router(state: Arc<AppState>) -> Router {
|
|||||||
None => main_router,
|
None => main_router,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn api_not_found() -> impl IntoResponse {
|
||||||
|
(
|
||||||
|
StatusCode::NOT_FOUND,
|
||||||
|
Json(super::ErrorResponse {
|
||||||
|
success: false,
|
||||||
|
code: None,
|
||||||
|
message: "Not Found".to_string(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -48,7 +48,9 @@ where
|
|||||||
{
|
{
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/", get(index_handler))
|
.route("/", get(index_handler))
|
||||||
.route("/{*path}", get(static_handler))
|
// GET-only fallback: a `/{*path}` wildcard would shadow fallbacks
|
||||||
|
// nested under other routers (e.g. the /api 404 handler) in axum 0.8.
|
||||||
|
.fallback_service(get(static_handler))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn index_handler() -> Response<Body> {
|
async fn index_handler() -> Response<Body> {
|
||||||
@@ -63,33 +65,25 @@ async fn static_handler(uri: Uri) -> Response<Body> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !path.contains('.') {
|
if !path.contains('.') {
|
||||||
|
// SPA fallback: extensionless paths are frontend routes.
|
||||||
if let Some(response) = try_serve_file("index.html") {
|
if let Some(response) = try_serve_file("index.html") {
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
not_found_response()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn not_found_response() -> Response<Body> {
|
||||||
Response::builder()
|
Response::builder()
|
||||||
.status(StatusCode::OK)
|
.status(StatusCode::NOT_FOUND)
|
||||||
.header(header::CONTENT_TYPE, "text/html; charset=utf-8")
|
.header(header::CONTENT_TYPE, "text/html; charset=utf-8")
|
||||||
.body(Body::from(placeholder_html()))
|
.body(Body::from(not_found_html()))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serve_file(path: &str) -> Response<Body> {
|
fn serve_file(path: &str) -> Response<Body> {
|
||||||
try_serve_file(path).unwrap_or_else(|| {
|
try_serve_file(path).unwrap_or_else(|| not_found_response())
|
||||||
if path == "index.html" {
|
|
||||||
Response::builder()
|
|
||||||
.status(StatusCode::OK)
|
|
||||||
.header(header::CONTENT_TYPE, "text/html; charset=utf-8")
|
|
||||||
.body(Body::from(placeholder_html()))
|
|
||||||
.unwrap()
|
|
||||||
} else {
|
|
||||||
Response::builder()
|
|
||||||
.status(StatusCode::NOT_FOUND)
|
|
||||||
.body(Body::from("Not Found"))
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_serve_file(path: &str) -> Option<Response<Body>> {
|
fn try_serve_file(path: &str) -> Option<Response<Body>> {
|
||||||
@@ -149,41 +143,23 @@ fn static_response(path: &str, data: Vec<u8>) -> Response<Body> {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn placeholder_html() -> String {
|
pub fn not_found_html() -> String {
|
||||||
r#"<!DOCTYPE html>
|
r#"<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<title>404 Not Found</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>One-KVM</title>
|
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
width: 35em;
|
||||||
display: flex;
|
margin: 0 auto;
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 100vh;
|
|
||||||
margin: 0;
|
|
||||||
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
.container {
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 2rem;
|
font-family: Tahoma, Verdana, Arial, sans-serif;
|
||||||
}
|
}
|
||||||
h1 { font-size: 2.5rem; margin-bottom: 1rem; }
|
|
||||||
p { color: #888; font-size: 1.1rem; }
|
|
||||||
.version { color: #666; margin-top: 2rem; font-size: 0.9rem; }
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<h1>404 Not Found</h1>
|
||||||
<h1>One-KVM</h1>
|
|
||||||
<p>Frontend not built yet.</p>
|
|
||||||
<p>Please build the frontend or access the API directly.</p>
|
|
||||||
<div class="version">v{{VERSION}}</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
</body>
|
||||||
</html>"#
|
</html>"#
|
||||||
.replace("{{VERSION}}", env!("CARGO_PKG_VERSION"))
|
.to_string()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user