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
- 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.)
- 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!
The custom
ServeMuxinhttp/server.gosupports basic{name}placeholders, but three Go-1.22 routing features are mishandled. In each case the behavior diverges fromnet/http.ServeMuxin upstream Go. I have a fix + tests ready (verified against the realnet/http.ServeMuxas 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 upstreamnet/httpdoes.1. Multi-segment wildcard
{name...}never matchesr.PathValue("path") == "a/b/c".h.Root cause: both
patternCouldMatchandextractPathValuessplit on/and requirelen(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{$}anchors the pattern to the end, so/exact/{$}matches only/exact/, not/exact/sub;{$}is not a captured value.h(over-match) and sets a bogus path valuer.PathValue("$") == "sub".Root cause:
{$}satisfies the genericstrings.HasPrefix(part,"{") && strings.HasSuffix(part,"}")test inpatternCouldMatch/extractPathValues, so it is handled as a normal capturing placeholder.3. Method-prefixed patterns (
"GET /path") don't route, and corrupt host routingGET /footohfor GET requests.Root cause:
Handlestores the entry under the full key"GET /foo"(somatchlooking up the bare path/foonever finds it), and becausepattern[0] == 'G' != '/'it also setsmux.hosts = true— which makeshandler()prepend the host to every subsequent lookup, breaking routing for the whole mux.Notes
net/ip/mac/parsefiles appear to be faithful ports of upstream and look correct — these three issues are all in the hand-writtenServeMux, which has no direct upstream twin (upstream uses the routing-tree implementation), so I assume it's intentionally a lighter reimplementation.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
{x...},{$},METHOD /path) in scope for tinygo-org/net'sServeMux, or is the minimal{name}-only subset intentional? (Happy to fix all three, or just a subset, per your preference.)server_test.goin the package that runs undertinygo test? (My local tests run on host via copied helpers since the package importsinternal/*; I'd adapt them to the repo's convention.)Thanks!