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
相关产品推荐
相关产品推荐

