Repository navigation
feat(router)!: replace httprouter with an in-house route table - #141
Merged
Merged
Conversation
Add pkg/router/internal/routetree, a segment-trie route table written from the spec in docs/plans/route-table.md, and switch Router to it. The spec was derived from SRouter's docs and tests plus black-box observation of httprouter's public behaviour; no httprouter source was read or copied. - Static and wildcard segments can share a position (/users/new next to /users/:id); matching backtracks and never depends on registration order. - Registration problems are Build errors, never panics. - Unmatched requests keep trailing-slash and cleaned-path redirects, 405 with Allow, and automatic OPTIONS. Redirects only target clean paths that match a route, which closes an open redirect in httprouter v1.3.0 (GET //evil.com/../ on /:slug redirected to http://evil.com/..). - Case-correcting redirects are dropped; parameters no longer match empty segments; mid-segment wildcards and repeated wildcard names are rejected. BREAKING CHANGE: scontext.GetPathParams, WithRouteInfo, SetRouteInfo, and router.GetParams use scontext.Params instead of httprouter.Params. This commit keeps a differential test and a benchmark suite that run httprouter side by side through its public API; the next commit removes them together with the dependency. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Remove the temporary differential test and side-by-side benchmarks that ran httprouter through its public API, keep the route table benchmarks on their own, and tidy go.mod. Count the route table in CI's coverage packages. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #141 +/- ##
==========================================
+ Coverage 99.92% 99.93% +0.01%
==========================================
Files 26 31 +5
Lines 2629 3180 +551
==========================================
+ Hits 2627 3178 +551
Misses 2 2 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Add tests for misses and route reuse under hashed child sets, routes with more parameters than the matcher's stack buffer, appending to a caller's parameter buffer, the trailing-slash hint from a sibling, ServeUnmatched called directly, TRACE, and non-ASCII query bytes in redirects. Drop the unreachable root-path branch from toggleTrailingSlash; both callers exclude the root. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The route table reports invalid and conflicting patterns as errors and never panics, so handleRoute no longer needs to recover. Build keeps its own recover as the outer safety net. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Update the prometheus/common dependency to the latest version. This ensures the project remains up-to-date with the latest bug fixes and improvements provided by the upstream library.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
SRouter no longer depends on
github.com/julienschmidt/httprouter(v1.3.0, unmaintained since 2019). Routing now runs onpkg/router/internal/routetree, a segment-trie route table written from a spec:docs/plans/route-table.md.The spec came from SRouter's docs and tests plus black-box observation of httprouter's public API. No httprouter source was read or copied, so SRouter doesn't take on its BSD-3 license.
Behaviour
Kept:
:nameand*namesyntax.Allow.OPTIONS; explicitOPTIONSroutes still win.Changed on purpose (full table in the spec):
/users/newcan be registered next to/users/:id; the static route wins, and matching backtracks. Previously this panicked at registration.Builderrors, never panics.GET //evil.com/../on a/:slugroute with301 Location: http://evil.com/.., an open redirect. It also sometimes redirects to paths that match nothing./user_:name) and repeated wildcard names are rejected.Breaking API change
scontext.GetPathParams,WithRouteInfo,SetRouteInfoandrouter.GetParamsnow usescontext.Paramsinstead ofhttprouter.Params. Code that only callsByName, indexes or ranges over the result compiles unchanged. Migration notes are indocs/context-management.md.Consumers checked
I audited every route in
go-common(168 routes) andgradethis:Everything they rely on is kept and now pinned by router-level tests (
pkg/router/route_table_test.go): explicitOPTIONShandlers, 405 for a wrong method, automaticOPTIONS, and exact-path dispatch.Benchmarks
The first commit (
dce207d) contains a side-by-side benchmark suite and a differential test that run httprouter through its public API. The second commit removes both along with the dependency. To reproduce: check outdce207d, then rungo test -run '^$' -bench Compare -benchmem -count 10 ./pkg/router/internal/routetreeandbenchstat -col /router.The runs below (Apple M4 Max) were on a busy machine, with load averages between about 7 and 20.
Memory, deterministic: bytes and allocations per request are equal or lower on every row.
Time (benchstat, n=10), from the lower-load run:
rest_catchall,github_param2and the 1,000-route flat build: inconclusive (~) under load; the catch-all andgithub_param2came in 15% and 30% faster in a quieter short run;github_param4: +2.6% (p=0.004), with both routers running at about twice their quiet-machine times.A second run at load around 10 agreed except for 404 and the GitHub build. In that run routetree's 404 time jumped to 376–722 ns between runs, and httprouter's timings varied by ±122%. These rows should be re-checked on a quiet machine.
Whole router (
./pkg/routerbenchmarks;mainand this branch alternated 8 times):Testing
go test -race ./...,go vet ./...andgolangci-lint runall pass (0 issues).internal/docchecklink check passes, and the examples build.routetreeis now in CI's-coverpkglist.httptest.Server.FuzzMatchandFuzzCleanPathfound no failures.🤖 Generated with Claude Code