From ab593bbab7e0e3203d8e15e09208f3cd8bd43fb4 Mon Sep 17 00:00:00 2001 From: othelot Date: Tue, 16 Sep 2025 17:29:01 +0900 Subject: [PATCH] routing: omit the `Allow` header for non-405 method not allowed fallbacks (#3465) --- axum/src/docs/method_routing/fallback.md | 4 ++-- axum/src/routing/route.rs | 6 +++++- axum/src/routing/tests/fallback.rs | 17 +++++++++++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/axum/src/docs/method_routing/fallback.md b/axum/src/docs/method_routing/fallback.md index e6f364a8..ec78f201 100644 --- a/axum/src/docs/method_routing/fallback.md +++ b/axum/src/docs/method_routing/fallback.md @@ -48,8 +48,8 @@ async fn fallback_two() -> impl IntoResponse { /* ... */ } ## Setting the `Allow` header By default `MethodRouter` will set the `Allow` header when returning `405 Method -Not Allowed`. This is also done when the fallback is used unless the response -generated by the fallback already sets the `Allow` header. +Not Allowed`. This is also done when the fallback returns `405 Method Not Allowed` +unless the response generated by the fallback already sets the `Allow` header. This means if you use `fallback` to accept additional methods, you should make sure you set the `Allow` header correctly. diff --git a/axum/src/routing/route.rs b/axum/src/routing/route.rs index 2d724c79..deedb3a5 100644 --- a/axum/src/routing/route.rs +++ b/axum/src/routing/route.rs @@ -161,7 +161,11 @@ impl Future for RouteFuture { res = res.map(|_| Body::empty()); } } else if *this.top_level { - set_allow_header(res.headers_mut(), this.allow_header); + if res.status() == http::StatusCode::METHOD_NOT_ALLOWED { + // From https://httpwg.org/specs/rfc9110.html#field.allow: + // An origin server MUST generate an `Allow` header field in a 405 (Method Not Allowed) response and MAY do so in any other response. + set_allow_header(res.headers_mut(), this.allow_header); + } // make sure to set content-length before removing the body set_content_length(&res.size_hint(), res.headers_mut()); diff --git a/axum/src/routing/tests/fallback.rs b/axum/src/routing/tests/fallback.rs index 3c8755bb..81204077 100644 --- a/axum/src/routing/tests/fallback.rs +++ b/axum/src/routing/tests/fallback.rs @@ -329,6 +329,23 @@ async fn merge_router_with_fallback_into_empty() { assert_eq!(res.text().await, "outer"); } +#[crate::test] +async fn mna_fallback_not_405() { + let app = Router::new() + .route("/path", get(|| async { "path" })) + .method_not_allowed_fallback(|| async { (http::StatusCode::NOT_FOUND, "Not Found") }); + + let client = TestClient::new(app); + let method_not_allowed_fallback = client.post("/path").await; + + assert_eq!( + method_not_allowed_fallback.status(), + http::StatusCode::NOT_FOUND + ); + assert_eq!(method_not_allowed_fallback.headers().get(ALLOW), None); + assert_eq!(method_not_allowed_fallback.text().await, "Not Found"); +} + #[crate::test] async fn mna_fallback_with_existing_fallback() { let app = Router::new()