You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rust Axum中如何让带/不带尾斜杠的路由指向同一处理器?

问题描述

我希望让http://0.0.0.0/foo和http://0.0.0.0/foo/都路由到同一个get_foo处理器,但实际只有/foo能正常访问,/foo/会返回404错误。怀疑是中间件的配置或挂载方式有问题,相关代码和Cargo.toml如下:

use axum::http::StatusCode;
use axum::{routing::{get}, Router};
use std::{net::SocketAddr};
use tower_http::normalize_path::NormalizePathLayer;

#[tokio::main]
async fn main() {
    let app = Router::new()
        .route("/foo", get(get_foo))
        .layer(NormalizePathLayer::trim_trailing_slash());

    let port_str = std::env::var("PORT").unwrap_or("8000".to_owned());
    let port = port_str.parse::<u16>().unwrap();
    let addr = SocketAddr::from(([0, 0, 0, 0], port));
    println!("listening on http://{}", addr);
    axum::Server::bind(&addr)
        .serve(app.into_make_service())
        .await
        .unwrap();
}

async fn get_foo() -> Result<String, StatusCode>  {
    Ok("Hello from foo.".to_owned())
}

对应的Cargo.toml:

[package]
name = "axum_trailing_slash"
version = "0.1.0"
edition = "2021"

[dependencies]
axum = { version = "0.6" }
tokio = { version = "1.0", features = ["full"] }
tower = { version = "0.4" }
tower-http = { version = "0.3", features = ["normalize-path"] }
解决方案

问题出在中间件的执行顺序:你当前用.layer()添加的NormalizePathLayer是在路由定义之后挂载的,导致Axum先进行路由匹配(此时请求路径还是/foo/,找不到对应路由),再执行中间件,自然返回404。

要解决这个问题,只需确保路径规范化中间件在路由匹配之前执行,有两种简单的修改方式:

方式一:调整中间件与路由的顺序

把NormalizePathLayer的添加放在路由定义之前,让请求先经过中间件修改路径,再进入路由匹配:

use axum::http::StatusCode;
use axum::{routing::get, Router};
use std::net::SocketAddr;
use tower_http::normalize_path::NormalizePathLayer;

#[tokio::main]
async fn main() {
    let app = Router::new()
        .layer(NormalizePathLayer::trim_trailing_slash()) // 先挂载中间件
        .route("/foo", get(get_foo)); // 再定义路由

    let port_str = std::env::var("PORT").unwrap_or("8000".to_owned());
    let port = port_str.parse::<u16>().unwrap();
    let addr = SocketAddr::from(([0, 0, 0, 0], port));
    println!("listening on http://{}", addr);
    axum::Server::bind(&addr)
        .serve(app.into_make_service())
        .await
        .unwrap();
}

async fn get_foo() -> Result<String, StatusCode> {
    Ok("Hello from foo.".to_owned())
}

方式二:改用wrap()方法包裹路由

wrap()方法会将中间件直接包裹在整个路由外层,确保请求先经过中间件处理:

use axum::http::StatusCode;
use axum::{routing::get, Router};
use std::net::SocketAddr;
use tower_http::normalize_path::NormalizePathLayer;

#[tokio::main]
async fn main() {
    let app = Router::new()
        .route("/foo", get(get_foo))
        .wrap(NormalizePathLayer::trim_trailing_slash()); // 用wrap替换layer

    let port_str = std::env::var("PORT").unwrap_or("8000".to_owned());
    let port = port_str.parse::<u16>().unwrap();
    let addr = SocketAddr::from(([0, 0, 0, 0], port));
    println!("listening on http://{}", addr);
    axum::Server::bind(&addr)
        .serve(app.into_make_service())
        .await
        .unwrap();
}

async fn get_foo() -> Result<String, StatusCode> {
    Ok("Hello from foo.".to_owned())
}

两种方式都能让/foo/被自动规范化为/foo,从而匹配到对应的处理器。

内容的提问来源于stack exchange,提问作者kindrobot

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 17:05:22