Skip to content

fix: deduplicate Allow header methods in 405 responses - #1112

Closed
LeSingh1 wants to merge 1 commit into
go-chi:masterfrom
LeSingh1:fix/deduplicate-allow-header
Closed

LeSingh1 wants to merge 1 commit into
go-chi:masterfrom
LeSingh1:fix/deduplicate-allow-header

Conversation

@LeSingh1

Copy link
Copy Markdown

Problem

When multiple overlapping parameterized route patterns all register the same HTTP method, a request with an unregistered method produces duplicate entries in the Allow response header.

Minimal reproduction (from #996):

r.Post("/article/1-2-3", ...)
r.Post("/article/{a}", ...)
r.Post("/article/{b}-{c}", ...)
r.Post("/article/{b}-{c}-{d}", ...)

// GET /article/1-2-3 → 405 with Allow: POST, POST, POST, POST

Each parameterized node that matched during route traversal independently appended POST to rctx.methodsAllowed []methodTyp. The methodNotAllowedHandler then wrote one Allow header value per slice entry — one per matched node — instead of one per distinct method.

Fix

Replace methodsAllowed []methodTyp in Context with a methodTyp bitmask. Since methodTyp is already defined as a bitmask of power-of-two constants, each candidate node can OR its methods into the bitmask (rctx.methodsAllowed |= endpoints) rather than append to a slice. methodNotAllowedHandler then iterates reverseMethodMap and emits one Allow header entry for each bit that is set — guaranteeing uniqueness without any extra allocation.

Changes

  • context.go: methodsAllowed []methodTyp → methodsAllowed methodTyp; Reset() zeroes with = 0
  • tree.go: two append(rctx.methodsAllowed, ...) → rctx.methodsAllowed |= endpoints
  • mux.go: MethodNotAllowedHandler and methodNotAllowedHandler take a single methodTyp bitmask instead of ...methodTyp; handler iterates reverseMethodMap by bitmask
  • mux_test.go: TestMethodNotAllowedNoDuplicates — regression test from the exact scenario in Duplicate HTTP methods in 405 Allow header #996

Test plan

  • TestMethodNotAllowedNoDuplicates — asserts no duplicate values in Allow header and that POST is present
  • Existing TestMethodNotAllowed still passes (2 distinct methods, GET and HEAD, each appear once)
  • go test ./...

Fixes #996

When multiple overlapping parameterized route patterns all have the same
HTTP method registered (e.g. /{a}, /{b}-{c}, /{b}-{c}-{d}), a request
with an unregistered method would trigger methodNotAllowed on each
matching node, appending the allowed method to rctx.methodsAllowed
multiple times. The 405 handler then wrote one Allow header entry per
duplicate, producing responses like 'Allow: POST, POST, POST, POST'.

Fix: change rctx.methodsAllowed from []methodTyp to a methodTyp bitmask.
Each candidate node ORs its allowed methods into the bitmask instead of
appending to a slice, so each method appears at most once. The
methodNotAllowedHandler iterates reverseMethodMap and sets an Allow
header entry for each bit that is set.

Fixes go-chi#996
@VojtechVitek

Copy link
Copy Markdown
Contributor

Thanks everyone. I confirmed the issue and reviewed these competing PRs:

PR Age Approach
#1029 Oct 2025 Dedupe at the source in tree.go (slices.Contains before append)
#1040, #1096, #1124 Dec 2025 – Jul 2026 Near-identical: seen-map inside methodNotAllowedHandler
#1047 Feb 2026 slices.Sort + Compact in the handler
#1112 Jun 2026 Refactor methodsAllowed to a bitmask

Let's go with #1029, which looks like the cleanest zero-allocation fix.

Closing the other PRs as duplicates — thanks for the contributions, the approaches were all reasonable.

Thank you for your contribution!

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.

Duplicate HTTP methods in 405 Allow header

2 participants