Axum 注册带 .json 后缀的参数路由,启动就报 Invalid route
Axum 0.8.9 不支持在同一段混用参数捕获和 .json 后缀。捕获完整文件名,在 handler 按原协议验证 ID,保留路由与权限边界。
Axum 应用还没开始监听,就在注册 /project/{project_id}.json 时 panic,提示:
Invalid route "/{project_id}.json": Only one parameter is allowed per path segment
错误文字看起来像写了两个参数,实际只写了一个。Axum 0.8.9 这条匹配路径不能把捕获参数与 .json 静态后缀放在同一个段里;把相同规则移进 nest,并不会改变这个限制。
如果日志中的路由没有外层 /project,也可能只是嵌套路由显示内部路径。先核对触发 panic 的具体注册语句,不要按错误中的短路径去修改浏览器地址。
捕获完整文件名,在 handler 中检查后缀
需要继续支持 /project/abc.json 时,可以先捕获整个最后一段,再明确验证后缀和项目 ID。下面的示意只允许 ASCII 字母、数字、下划线与连字符作为 ID:
use axum::{
extract::Path,
http::StatusCode,
routing::get,
Router,
};
async fn project_file(
Path(filename): Path<String>,
) -> Result<String, StatusCode> {
let id = filename
.strip_suffix(".json")
.filter(|id| {
!id.is_empty()
&& id.bytes().all(|b| {
b.is_ascii_alphanumeric() || b == b'_' || b == b'-'
})
})
.ok_or(StatusCode::NOT_FOUND)?;
Ok(id.to_owned())
}
fn app() -> Router {
Router::new().route("/project/{filename}", get(project_file))
}
这个 handler 只演示路径解析,成功时返回 ID 文本;合入业务时应把已经验证的 ID 交给原查询逻辑,并保留原来的 JSON 响应、认证、权限和缺失记录处理。
示例中的 ID 规则不是 Axum 的内置约束。项目若原本支持其他字符,应按已有协议实现验证,而不是随意缩窄合法 ID。Path<String> 已进行路径参数解码,不应再次无条件 URL 解码。
abc.json.json 会先去掉最后的 .json,剩余 ID 中仍有点号,因而在这个规则下被拒绝。不能只检查字符串包含 .json,再把剩余任意内容当作数据库身份。
不必扩大成多段通配符
/{*filename} 会捕获后续多个路径段,改变路由覆盖范围。若这里本来只接受一个文件名段,就使用普通 /{filename};不要为消除启动异常,把其他子路径也接进同一个 handler。
调整捕获范围后,还要检查同层静态路由与已有文件路由。原本不匹配的扩展名现在可能先进入 handler,必须明确返回对应错误,不能掉进默认成功响应。
Axum 0.8.9 的限制有明确版本范围。后续路由能力变化,应以实际使用的正式依赖和路径匹配行为为准;为了这一个写法直接改用未发布的 Git 分支,会同时改变依赖和兼容边界,不是必要步骤。
验收包含正常 .json、缺少后缀、错误扩展名、空 ID、重复后缀和已有静态路由。确认应用能够启动,外部 URL 保持契约,合法 ID 仍通过原权限检查,非法路径没有被新的整段捕获悄悄接受。


