Skip to content

Commit 7144188

Browse files
committed
fix(core): 修复 FullPath(**) 与 DFS 终止逻辑导致 405\n\n- 保留 ** 捕获完整路径但允许继续匹配子节点;子路由不匹配时回退父节点处理器\n- URL 终止时仍尝试所有子节点,避免漏匹配 <path:**>\n- HEAD 无处理器时回退 GET 并清空响应体,避免静态资源 405\n\n验证:file_server 正常;silent crate 单元测试与文档测试全部通过
1 parent 0f00c0b commit 7144188

File tree

2 files changed

+34
-18
lines changed

2 files changed

+34
-18
lines changed
Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::collections::HashMap;
22

3+
use crate::core::res_body::ResBody;
34
use crate::{Request, Response, Result, SilentError};
45
use async_trait::async_trait;
56
use http::{Method, StatusCode};
@@ -17,17 +18,29 @@ pub trait Handler: Send + Sync + 'static {
1718
#[async_trait]
1819
impl Handler for HashMap<Method, Arc<dyn Handler>> {
1920
async fn call(&self, req: Request) -> Result<Response> {
20-
match self.clone().get(req.method()) {
21-
None => Err(SilentError::business_error(
22-
StatusCode::METHOD_NOT_ALLOWED,
23-
"method not allowed".to_string(),
24-
)),
25-
Some(handler) => {
26-
let mut pre_res = Response::empty();
27-
pre_res.configs = req.configs();
28-
pre_res.copy_from_response(handler.call(req).await?);
29-
Ok(pre_res)
30-
}
21+
let method = req.method().clone();
22+
// 直接命中匹配的方法
23+
if let Some(handler) = self.clone().get(&method) {
24+
let mut pre_res = Response::empty();
25+
pre_res.configs = req.configs();
26+
pre_res.copy_from_response(handler.call(req).await?);
27+
return Ok(pre_res);
3128
}
29+
30+
// 特殊处理:HEAD 无显式处理器时回退到 GET,并清空响应体
31+
if method == http::Method::HEAD
32+
&& let Some(get_handler) = self.clone().get(&http::Method::GET)
33+
{
34+
let mut pre_res = Response::empty();
35+
pre_res.configs = req.configs();
36+
pre_res.copy_from_response(get_handler.call(req).await?);
37+
pre_res.set_body(ResBody::None);
38+
return Ok(pre_res);
39+
}
40+
41+
Err(SilentError::business_error(
42+
StatusCode::METHOD_NOT_ALLOWED,
43+
"method not allowed".to_string(),
44+
))
3245
}
3346
}

silent/src/route/route_tree.rs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,8 @@ impl RouteTree {
112112
(true, last_path)
113113
}
114114
SpecialPath::FullPath(key) => {
115-
// ** 通配符:总是匹配,参数记录完整剩余路径
115+
// ** 通配符:记录完整剩余路径,且允许继续尝试子结点匹配。
116+
// 若后续子结点无法匹配,dfs_match 中会在有处理器时回退到当前结点。
116117
let p = path.strip_prefix('/').unwrap_or(path);
117118
req.set_path_params(key, PathParam::Path(p.to_string()));
118119
(true, last_path)
@@ -137,17 +138,19 @@ impl RouteTree {
137138
stack.push(self);
138139

139140
if last_path.is_empty() {
140-
// URL 已完全匹配:仅尝试 path 为空字符串的子结点
141+
// URL 已完全匹配:仍尝试所有子结点,让特殊路径(如 <path:**>)有机会匹配空余路径
141142
for child in &self.children {
142-
if !child.path.is_empty() {
143-
continue;
144-
}
145143
if child.dfs_match(req, last_path, stack) {
146144
return true;
147145
}
148146
}
149-
// 无子结点匹配,则当前为终点
150-
return true;
147+
// 无子结点匹配:仅当当前结点存在处理器时才认为匹配成功
148+
if self.has_handler {
149+
return true;
150+
} else {
151+
stack.pop();
152+
return false;
153+
}
151154
}
152155

153156
// 继续匹配子路由

0 commit comments

Comments
 (0)