Skip to content

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

@ultramcu

Description

@ultramcu

The custom ServeMux in http/server.go supports basic {name} placeholders, but three Go-1.22 routing features are mishandled. In each case the behavior diverges from net/http.ServeMux in upstream Go. I have a fix + tests ready (verified against the real net/http.ServeMux as an oracle) and am happy to open a PR, but since this touches routing behavior I wanted to check the intended scope first.

All examples below: pattern(s) registered on a ServeMux, a request path, what tinygo-org/net currently does, and what upstream net/http does.

1. Multi-segment wildcard {name...} never matches

mux.Handle("/files/{path...}", h)
GET /files/a/b/c
  • Upstream: matches, r.PathValue("path") == "a/b/c".
  • tinygo-org/net: 404 — the request never reaches h.

Root cause: both patternCouldMatch and extractPathValues split on / and require len(patternParts) == len(pathParts), so a trailing {x...} can only ever match a single segment count, never the rest of the path.

2. {$} end-of-path anchor is treated as an ordinary wildcard

mux.Handle("/exact/{$}", h)
GET /exact/sub
  • Upstream: 404{$} anchors the pattern to the end, so /exact/{$} matches only /exact/, not /exact/sub; {$} is not a captured value.
  • tinygo-org/net: matches h (over-match) and sets a bogus path value r.PathValue("$") == "sub".

Root cause: {$} satisfies the generic strings.HasPrefix(part,"{") && strings.HasSuffix(part,"}") test in patternCouldMatch/extractPathValues, so it is handled as a normal capturing placeholder.

3. Method-prefixed patterns ("GET /path") don't route, and corrupt host routing

mux.Handle("GET /foo", h)
GET /foo
  • Upstream: routes GET /foo to h for GET requests.
  • tinygo-org/net: 404, and every other route in the mux breaks.

Root cause: Handle stores the entry under the full key "GET /foo" (so match looking up the bare path /foo never finds it), and because pattern[0] == 'G' != '/' it also sets mux.hosts = true — which makes handler() prepend the host to every subsequent lookup, breaking routing for the whole mux.


Notes

  • The net/ip/mac/parse files appear to be faithful ports of upstream and look correct — these three issues are all in the hand-written ServeMux, which has no direct upstream twin (upstream uses the routing-tree implementation), so I assume it's intentionally a lighter reimplementation.
  • I verified a fix locally against net/http.ServeMux (Go 1.26.3) as an oracle across ~90 cases (the three features above plus precedence interactions like {id} vs {x...} and {$} vs a trailing-slash subtree, method vs method-less, and no-regression on exact / {id} / subtree / host patterns). Fail-before/pass-after holds.

Questions before I PR

  1. Is full Go-1.22 pattern routing ({x...}, {$}, METHOD /path) in scope for tinygo-org/net's ServeMux, or is the minimal {name}-only subset intentional? (Happy to fix all three, or just a subset, per your preference.)
  2. Preferred test location/format — a server_test.go in the package that runs under tinygo test? (My local tests run on host via copied helpers since the package imports internal/*; I'd adapt them to the repo's convention.)

Thanks!

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions