🔥 feat: Route Domain() func for host-based routing#4100
🔥 feat: Route Domain() func for host-based routing#4100
Conversation
Co-authored-by: gaby <[email protected]>
- errcheck: handle errors from io.ReadAll(resp.Body) with require.NoError - httpNoBody: use http.NoBody instead of nil in httptest.NewRequest - fieldalignment: reorder struct fields to minimize padding - errcheck: add error handling in benchmarks for app.Test Co-authored-by: Copilot <[email protected]>
Co-authored-by: gaby <[email protected]>
Co-authored-by: gaby <[email protected]>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #4100 +/- ##
==========================================
+ Coverage 91.04% 91.17% +0.13%
==========================================
Files 119 120 +1
Lines 11340 11545 +205
==========================================
+ Hits 10324 10526 +202
Misses 644 644
- Partials 372 375 +3
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
⚠️ Performance Alert ⚠️
Possible performance regression was detected for benchmark.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.50.
| Benchmark suite | Current: 694cabf | Previous: 92ada8a | Ratio |
|---|---|---|---|
BenchmarkDecoderedirectionMsgs - MB/s |
114.43 MB/s |
47.86 MB/s |
2.39 |
This comment was automatically generated by workflow using github-action-benchmark.
|
…main Co-authored-by: gaby <[email protected]>
Addressed in ed8cbb8:
|
|
Co-authored-by: gaby <[email protected]>
Addressed in 1f1dbd7:
|
|
/gemini review |
There was a problem hiding this comment.
Code Review
This is an excellent pull request that introduces host-based routing, a highly requested feature. The implementation is clean, non-intrusive, and aligns well with Fiber's existing API and performance goals. The "zero impact" on non-domain routes is a key achievement, and the comprehensive test suite, including benchmarks, provides strong confidence in the changes. The documentation is also very thorough. I have a couple of minor suggestions to improve code and documentation clarity.
There was a problem hiding this comment.
Pull request overview
Adds host-based routing to Fiber via a new Domain(host string) Router API, implemented as a wrapper-based router (domainRouter) to avoid impacting non-domain routes while enabling :param capture via DomainParam().
Changes:
- Extends the
Routerinterface withDomain(host string) Router, and addsDomain()methods onAppandGroup. - Introduces
domain.gowith domain pattern parsing/matching, handler wrapping, andDomainParam()retrieval. - Adds comprehensive domain routing tests/benchmarks and updates API + “What’s New” documentation.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| router.go | Adds Domain(host string) Router to the exported Router interface. |
| app.go | Adds (*App).Domain() to create a domain-scoped router. |
| group.go | Adds (*Group).Domain() for domain scoping within groups. |
| domain.go | Implements domainRouter, domain pattern parsing/matching, and DomainParam(). |
| domain_test.go | Adds tests + benchmarks for domain routing behavior and performance. |
| docs/api/app.md | Documents Domain / DomainParam API and examples. |
| docs/whats_new.md | Adds a “Domain routing” section with examples. |
Comments suppressed due to low confidence (1)
domain.go:66
- The
//nolint:gocriticannotation and its rationale look out of sync with the current signature (the function returns unnamed results). Consider removing the nolint or updating it to the actual linter warning being suppressed to avoid masking unrelated issues.
func (m *domainMatcher) match(hostname string) (bool, []string) { //nolint:gocritic // named returns conflict with nonamedreturns linter
// Domain names are case-insensitive per RFC 4343
hostname = utilsstrings.ToLower(hostname)
|
@copilot apply changes based on the comments in this thread |
…lear stale params, handle *App in Use Co-authored-by: gaby <[email protected]>
Applied all review changes in b1465fa:
|
|
@copilot apply changes based on the comments in this thread |
…lt per request Co-authored-by: gaby <[email protected]>
Applied both changes in 694cabf:
|
|
…tests Co-authored-by: gaby <[email protected]>
All changes applied in ec2f74d:
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
domain.go:77
- If a domain parameter part is just ":" (e.g., pattern ":.example.com"), the parameter name would be an empty string after removing the ":" prefix at line 76. This could lead to confusing behavior where DomainParam(c, "") would be needed to access the value. Consider adding validation to reject parameter names that are empty after removing the ":" prefix, with a clear error message.
for i, part := range parts {
if part != "" && part[0] == ':' {
m.paramIdx = append(m.paramIdx, i)
m.paramNames = append(m.paramNames, part[1:])
}
domain.go
Outdated
| // domainCheckPool reduces allocations for domain check results in the hot path. | ||
| var domainCheckPool = sync.Pool{ | ||
| New: func() any { | ||
| return &domainCheckResult{} | ||
| }, | ||
| } | ||
|
|
||
| // acquireDomainCheck returns a domainCheckResult from the pool. | ||
| func acquireDomainCheck() *domainCheckResult { | ||
| //nolint:errcheck,forcetypeassert // pool always returns *domainCheckResult | ||
| return domainCheckPool.Get().(*domainCheckResult) | ||
| } |
There was a problem hiding this comment.
The domainCheckResult objects acquired from the sync.Pool are never returned to the pool. This defeats the purpose of using a sync.Pool to "minimize allocations" as stated in the comment. Each request that matches a domain route will allocate a new domainCheckResult that gets stored in c.Locals() but never returned to the pool, causing memory waste.
To fix this, you should either:
- Return the domainCheckResult to the pool after the request completes (requires hooking into request lifecycle cleanup), or
- Remove the sync.Pool entirely and just allocate domainCheckResult directly since it's not being reused anyway.
The simplest fix is option 2 - remove the sync.Pool and domainCheckPool variable, and change line 193 in wrapHandlers from "check := acquireDomainCheck()" to "check := &domainCheckResult{...}" initializing it directly.
There was a problem hiding this comment.
Removed sync.Pool, domainCheckPool, and acquireDomainCheck() entirely in ed6d14e. domainCheckResult is now allocated directly with &domainCheckResult{matched: matched, values: append([]string(nil), values...)} since the objects are stored in c.Locals() for the request lifetime and can't be returned to the pool.
…ctly Co-authored-by: gaby <[email protected]>
Description
Adds
Domain()to theRouterinterface for host-based routing with parameter support. Domain filtering is implemented via handler wrapping—zero changes to the core router—so there is no performance impact on routes that don't use it.Domain parameters are kept separate from path parameters (accessed via
DomainParam()) per maintainer guidance to not mix path and domain routing. All string operations use Fiber's utility packages (utils.TrimRight,utils.TrimSpace,utilsstrings.ToLower) for optimal performance. Domain parameter counts are bounded by the samemaxParamslimit used in the core path router to prevent OOM from unbounded allocations.The
domainLocalsKeyuses an unexported typed struct key to prevent collisions with user or middleware locals. All handlers in a domain-scoped route are individually wrapped with domain checking, with the hostname match computed once per request perdomainRouterand cached inc.Locals()via a directly allocateddomainCheckResultto avoid redundant parsing for multi-handler routes. Static domain matches (no params) clear any previously stored domain params to prevent stale value leaks. Fully-qualified domain names with trailing dots (e.g.,api.example.com.) are normalized viautils.TrimRightconsistent with Fiber'sSubdomains()host normalization (RFC 3986). Sub-app mounting viaDomain(...).Use(*App)is explicitly unsupported with a clear panic message. The domain router is verified compatible with all 17 handler types defined inadapter.go.Changes introduced
domain.go—domainRouterimplementing fullRouterinterface, domain pattern parser/matcher using fiber utils (utils.TrimRight,utils.TrimSpace,utilsstrings.ToLower), trailing dot normalization (RFC 3986),maxParamsbound check consistent with path router,DomainParam()accessor,domainRegisteringforRouteChainsupport, typed struct key for Locals storage, per-request cached domain match result to avoid redundant hostname parsing, stale param clearing, explicit*Appmount rejectionrouter.go—Domain(host string) Routeradded toRouterinterfaceapp.go/group.go—Domain()method onAppandGroupdocs/api/app.md— Detailed API documentation forDomainandDomainParamwith full examples following the same formatting asGroup,Route, andRouteChainsectionsdocs/whats_new.md— Domain routing section with examplesBenchmark_Domain_Match,Benchmark_Domain_Route,Benchmark_Domain_NoImpactincluded to verify zero overhead on non-domain routes.docs/api/app.mdupdated withDomainandDomainParamsections with detailed examples;docs/whats_new.mdupdated with Domain routing section.vhoststyle with:paramsyntax consistent with Fiber's path parameters.Routerinterface;DomainParam()is a standalone function avoiding Ctx interface bloat. Domain parameter count bounded bymaxParamsconsistent with path router. Typed locals key prevents collisions. Per-request cached match avoids redundant work.adapter.go.Type of change
Checklist
/docs/directory for Fiber's documentation.Commit formatting
Please use emojis in commit messages for an easy way to identify the purpose or intention of a commit. Check out the emoji cheatsheet here: CONTRIBUTING.md
Original prompt
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.