Skip to content

http: fix ServeMux {wildcard...}, {$}, and method-prefixed patterns#56

Open
ultramcu wants to merge 1 commit into
tinygo-org:mainfrom
ultramcu:fix/servemux-routing
Open

http: fix ServeMux {wildcard...}, {$}, and method-prefixed patterns#56
ultramcu wants to merge 1 commit into
tinygo-org:mainfrom
ultramcu:fix/servemux-routing

Conversation

@ultramcu
Copy link
Copy Markdown

@ultramcu ultramcu commented Jun 4, 2026

Fixes #55.

The hand-rolled ServeMux in http/server.go mishandled three Go-1.22 routing features, each diverging from net/http.ServeMux. Per @deadprogram's note on #55 that the divergence wasn't intentional, this fixes all three.

Bugs fixed

  1. {name...} multi-segment wildcard never matched. mux.Handle("/files/{path...}", h); GET /files/a/b/c returned 404. patternCouldMatch/extractPathValues split on / and required equal segment counts, so a trailing {x...} could only match a single segment. It now consumes the remainder — including the empty remainder (/files/PathValue("path") == ""), matching net/http.

  2. {$} end-anchor treated as an ordinary capture. /exact/{$} over-matched /exact/sub and set a bogus PathValue("$") == "sub". It now matches only the exact /exact/ and is never captured as a value.

  3. Method-prefixed patterns ("GET /foo") didn't route and corrupted host routing. The entry was stored under the full "GET /foo" key (so a bare-path lookup missed it), and because the pattern's first byte wasn't /, mux.hosts was flipped to true, breaking routing for the whole mux. Handle now parses an optional leading method, keys match decisions on the stripped path, and selects entries by method plus net/http-compatible specificity.

Verification

Verified against the real net/http.ServeMux as an oracle (on host) across the three features above, precedence interactions ({id} vs {x...} single/deep, {$} vs a trailing-slash subtree, method vs method-less), and no-regression on exact / {id} / subtree / host patterns. Fail-before/pass-after holds and the winning pattern is order-independent (transitive) and agrees with net/http on every legal registration set tested.

http/server_test.go adds table-driven coverage for all three features plus the precedence and no-regression cases. (Note: it runs under the TinyGo test harness; the package can't be go test-ed standalone on host since there's no module-root go.mod, so verification was done with copied helpers against the net/http oracle.)

Scope notes

  • 404 vs 405 on method mismatch. A POST to a GET-only route returns 404, not net/http's 405 Method Not Allowed + Allow header. TinyGo's ServeMux has no 405/Allow machinery; adding it would mean tracking the method set per path and is left out of scope. The routing decision (match vs no-match) matches net/http.
  • No registration-time conflict detection. Overlapping patterns are resolved by specificity ranking rather than rejected at registration as net/http does (net/http panics on genuinely-ambiguous pairs). This preserves the existing hand-rolled matcher's leniency.
  • Kept minimal. The repo's http/pattern.go (the full upstream matcher) is intentionally left unused; this fix stays within the existing hand-rolled ServeMux rather than rewiring it.
  • Patterns mixing a wildcard with a trailing-slash subtree (e.g. /a/{id}/) remain unsupported, as before (not a regression).

The hand-rolled ServeMux diverged from net/http.ServeMux on three
Go-1.22 routing features:

- {name...} multi-segment wildcards never matched: patternCouldMatch and
  extractPathValues split on "/" and required equal segment counts, so a
  trailing {x...} could only ever match a single segment. It now consumes
  the remainder (including the empty remainder, e.g. /files/ -> path="").

- {$} end-of-path anchors were treated as ordinary captures: /exact/{$}
  over-matched /exact/sub and set a bogus PathValue("$"). It now matches
  only the exact path and is never captured as a value.

- Method-prefixed patterns ("GET /foo") did not route and corrupted host
  routing: the entry was stored under the full key so a bare-path lookup
  missed it, and a non-"/" first byte set mux.hosts=true, breaking every
  other route. Handle now parses an optional leading method, keys match
  decisions on the stripped path, and selects entries by method plus
  net/http-compatible specificity.

Verified against net/http.ServeMux as an oracle (host) across the three
features plus precedence interactions and no-regression on exact, {id},
subtree and host patterns; fail-before/pass-after holds.
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.

http: ServeMux mishandles {wildcard...}, {$}, and method-prefixed patterns

1 participant