Search code examples
rustrust-axum

Route paths with or without of trailing slashes in rust/axum


I want to route both http://0.0.0.0/foo and http://0.0.0.0/foo/ to the same get_foo handler. However, in practice, only /foo gets routed and /foo/ 404s. I suspect I'm setting up/attaching the middleware wrong:

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())
}

... and accompanying 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"] }

Solution

  • From the docs of Router::layer:

    Middleware added with this method will run after routing and thus cannot be used to rewrite the request URI. See “Rewriting request URI in middleware” for more details and a workaround.

    The workaround is to wrap the middleware around the entire Router (this works because Router implements Service):

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

    With axum>=0.7 you might need to use a fully qualified path with app.into_make_service like:

    use axum::extract::Request;
    
    axum::Server::bind(&addr)
        .serve(ServiceExt::<Request>::into_make_service(app))
        .await
        .unwrap();