DEBUG.WIKI
雨云;给项目一台云服务器
晚安云;香港 CN2 ¥15/月
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →

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 仍通过原权限检查,非法路径没有被新的整段捕获悄悄接受。