Skip to content

Fix duplicate methods in the Allow header after merging MethodRouters - #3836

Merged
mladedav merged 2 commits into
tokio-rs:mainfrom
zelinewang:fix/method-router-merge-duplicate-allow-header
Jul 16, 2026
Merged

mladedav merged 2 commits into
tokio-rs:mainfrom
zelinewang:fix/method-router-merge-duplicate-allow-header

Conversation

@zelinewang

Copy link
Copy Markdown
Contributor

Motivation

get registers a handler for both GET and HEAD, so a get route's Allow
header value is GET,HEAD. When two MethodRouters are merged, their Allow
header values are joined together. The join did not remove duplicates, so
merging a get route with a separately built head route produced an Allow
header of GET,HEAD,HEAD:

use axum::routing::{get, head, MethodRouter};

// GET (also serves HEAD) merged with an explicit HEAD handler
let router: MethodRouter = get(|| async {}).merge(head(|| async {}));
// a 405/OPTIONS response now carries `Allow: GET,HEAD,HEAD`

This is reachable through Router::merge as well, whenever the merged routers
have a get route on one side and an explicit head route on the other for the
same path. The builder path already guards against this: append_allow_header
skips a method that is already present, which is why get(..).head(..) on a
single router correctly yields GET,HEAD. Only the merge path was missing the
same guard.

Solution

Route the Bytes + Bytes case of AllowHeader::merge through the existing
append_allow_header helper, one method at a time, so the merged value reuses
the same de-duplication the builder already performs. append_allow_header only
copies the bytes of the method it is given, so its parameter is relaxed from
&'static str to &str to accept the method names split out of the other
header value.

Behavior is otherwise unchanged: None/Skip states merge as before, and
merging routers with disjoint methods still concatenates them in order (the
existing allow_header_when_merging test, which expects PUT,PATCH,GET,HEAD,
still passes). A regression test covers merging a get route with a separate
head route and asserts the Allow header is GET,HEAD.

`MethodRouter::merge` concatenated the two `Allow` header values without
de-duplication, while the per-router builder path (`append_allow_header`)
already avoids duplicates. Because `get` registers both `GET` and `HEAD`,
merging a `get` route with a separately built `head` route produced
`Allow: GET,HEAD,HEAD`.

Route the merge through the same de-duplicating helper so each method is
listed once. Add a regression test covering the merge case.
@zelinewang
zelinewang force-pushed the fix/method-router-merge-duplicate-allow-header branch from af91ccd to 64c9fa0 Compare July 15, 2026 00:21

@mladedav mladedav left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have it said it seems weird that a codex bot gave thumbs up to the PR, out of curiosity, do you happen to know why? Did you use it to open the PR?

Comment thread axum/src/routing/method_routing.rs Resolved
Comment thread axum/src/routing/method_routing.rs Resolved
Comment thread axum/src/routing/method_routing.rs Resolved
Comment thread axum/CHANGELOG.md Resolved
@zelinewang

Copy link
Copy Markdown
Contributor Author

fixed all four. that +1 is just the codex github app on my account, didn't ask it to do anything. and I do use AI in my workflow for finding and drafting fixes, everything here was verified locally before the PR went up, it works pretty fast and with good quality as long as thoroughly tested

@mladedav
mladedav merged commit b7e3788 into tokio-rs:main Jul 16, 2026
17 checks passed
dihannahdi added a commit to dihannahdi/axum that referenced this pull request Jul 16, 2026
The only conflict was the CHANGELOG. tokio-rs#3836 landed an Allow-header fix in
the same Unreleased block, so I kept both fixed entries.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants