Skip to content

Add support for hostname routing - #48

Merged
tigerwill90 merged 35 commits into
masterfrom
feat/support-domain-routing
Nov 18, 2024
Merged

tigerwill90 merged 35 commits into
masterfrom
feat/support-domain-routing

Conversation

@tigerwill90

@tigerwill90 tigerwill90 commented Nov 15, 2024 •

Copy link
Copy Markdown
Collaborator

Summary

This PR introduces hostname support that allow routes to be match by domain names. Fox can now handle routes both with and without specific hostname, bringing flexibility to environments with varying domain requirements.

Key feature

  • Hostname-based routing: Routes can now be defined with specific hostnames, which Fox will prioritize when matching incoming requests.
  • Fallback to No-Hostname Routes: When no exact hostname match is found, Fox will fall back to matching routes registered without hostnames (like the http.ServerMux).
  • Wildcard Support: Hostnames support limited wildcards, allowing for flexible routing based on subdomains. Notably:
    • Wildcards can match individual label (or segment) in the hostname, e.g., {sub}.example.com/ or {sub1}.{sub2}.example.com/. Catch-all wildcard such as *{any}.exemple.com/ are not supported for security reason.

Validation & Restrictions

Any characters, or combination of bits (as octets), are permitted in DNS names. However, there is a preferred form that is required by most applications as described in RFC 3696. This preferred form has been the only one permitted in the names of top-level domains, or TLDs. In general, it is also the only form permitted in most second-level names registered in TLDs.

In consequence, hostnames are validated to conform to the LDH (letters, digits, hyphens) standard. This ensures compatibility and avoids security risks, particularly with homograph attacks. User that need to support more flexible (non ASCII) domain should convert label in "punycode" form (see Internationalized Domain Names) before registering the route. Wildcard segments within hostnames, such as {a}.b.c/, are exempt from LDH validation since they act as placeholders rather than actual domain labels. As such, they do not count toward the hard limit of 63 characters per label, nor the 255-character limit for the full hostname (including periods).

The DNS specification also permits a trailing period to be used to denote the root, e.g., a.b.c and a.b.c. are equivalent, but the latter is more explicit and is required to be accepted by applications. Fox will reject route registered with trailing period. However, the router will automatically strip any trailing period from incoming request host so it can match the route regardless of an trailing period. Note that FQDN (with trailing period) does not play well with golang TLS stdlib (see traefik/traefik#9157 (comment)).

Priority rules

The router can transition instantly and transparently, at runtime, from path-only mode to hostname-prioritized mode without any additional configuration or action. If any route with a hostname is registered, the router automatically switches to prioritize hostname matching. Conversely, if no hostname-specific routes are registered, the router reverts to path-priority mode, ensuring optimal and adaptive routing behavior.

  • If the routing tree for a given method has no routes registered with hostnames, the router will perform a path-based lookup only.
  • If the routing tree for a given method includes at least one route with a hostname, the router will prioritize lookup based on the request host and path. If no match is found, the router will then fall back to a path-only lookup.
  • Trailing slash handling (redirect or ignore) is mode-specific, either for hostname-prioritized or path-prioritized mode. Therefore, if no exact match is found for a domain-based lookup but a trailing slash adjustment is possible, Fox will perform the redirect (or ignore the trailing slash) without falling back to path-only lookup.

Exemple

f := fox.New()
f.MustHandle(http.MethodGet, "{sub}.tf1.fr/ads/orange-as", func(c fox.Context) {
	fmt.Println(c.Param("sub"))
})

Performance

Fox vs. Standard Mux with a static hostname (adproxy.tf1.fr) is approximately 35% faster, though the performance gap narrows for very long domain names. Note that standard mux does not support wildcard in hostname.

goos: linux
goarch: amd64
pkg: github.com/tigerwill90/fox
cpu: Intel(R) Core(TM) i9-9900K CPU @ 3.60GHz
BenchmarkFoxStaticAll-16           52015             22089 ns/op               0 B/op          0 allocs/op
BenchmarkMuxStaticAll-16           36931             33935 ns/op               0 B/op          0 allocs/op

Very few routers support hostname matching. When comparing Fox to Gorilla Mux with a static hostname (adproxy.tf1.fr), Fox is approximately 96% faster:

goos: linux
goarch: amd64
pkg: github.com/tigerwill90/fox
cpu: Intel(R) Core(TM) i9-9900K CPU @ 3.60GHz
BenchmarkFoxStaticAll-16                   52173             22964 ns/op               0 B/op          0 allocs/op
BenchmarkGorillaMuxStaticAll-16             2659            601338 ns/op          135783 B/op       1256 allocs/op

Comparing to master branch, the new hostname routing feature introduces a modest 5% performance overhead on path-only route.

goos: linux
goarch: amd64
pkg: github.com/tigerwill90/fox
cpu: Intel(R) Core(TM) i9-9900K CPU @ 3.60GHz
                    │   old.txt    │               new.txt                │
                    │    sec/op    │    sec/op      vs base               │
StaticAll-16          12.29µ ±  5%    12.98µ ±  1%  +5.58% (p=0.002 n=10)
GithubParamsAll-16    86.77n ±  4%    88.31n ±  1%  +1.77% (p=0.002 n=10)
OverlappingRoute-16   95.69n ±  1%   101.50n ±  1%  +6.07% (p=0.000 n=10)
StaticParallel-16     10.92n ±  4%    11.34n ±  1%  +3.80% (p=0.011 n=10)
CatchAll-16           36.03n ±  3%    39.17n ±  3%  +8.71% (p=0.000 n=10)
CatchAllParallel-16   5.946n ± 10%    6.367n ± 16%       ~ (p=0.280 n=10)
CloneWith-16          67.81n ±  1%    69.75n ±  1%  +2.87% (p=0.001 n=10)
geomean               77.10n          81.03n        +5.10%

Other Notable Changes

This PR introduces a significant performance improvement for infix wildcard handling. Previously, route lookups with infix wildcards were not zero-allocation in all cases. See the lookup function implementation for details.

The limitation stemmed from the need to create an inode during the lookup phase, as references to inode objects had to remain valid until the function completed to avoid subtle race conditions. This prevented the use of object pooling during lookups.

To resolve this, inode objects are now precomputed during the route insertion, update, or deletion phases. This approach leverages the fact that we can determine the split points in advance, avoiding allocations entirely during the lookup phase.

Results:

  • Performance Boost: Lookup performance for infix wildcards has improved by ~63%.
// BenchmarkFoxInfixCatchAllOld-16    	 3068812	       473.9 ns/op	     320 B/op	       4 allocs/op
// BenchmarkFoxInfixCatchAllNew-16    	 5877748	       176.8 ns/op	       0 B/op	       0 allocs/op
  • Comparison with Gorilla Mux: Fox is now ~92% faster than Gorilla Mux for infix wildcard handling.
// BenchmarkGorillaMuxInfixCatchAll-16    	  644200	      1977 ns/op	    1191 B/op	       8 allocs/op
// BenchmarkFoxInfixCatchAll-16    	         5877748	       176.8 ns/op	       0 B/op	       0 allocs/op

@codecov

codecov Bot commented Nov 15, 2024 •

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 95.18900% with 28 lines in your changes missing coverage. Please review.

Project coverage is 91.98%. Comparing base (161ea5b) to head (17c4d81).
Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
internal/netutil/netutil.go 69.56% 11 Missing and 3 partials ⚠️
tree.go 98.25% 5 Missing and 1 partial ⚠️
node.go 86.11% 4 Missing and 1 partial ⚠️
internal/iterutil/iterutil.go 57.14% 2 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master      #48      +/-   ##
==========================================
+ Coverage   90.75%   91.98%   +1.23%     
==========================================
  Files          18       19       +1     
  Lines        2779     3131     +352     
==========================================
+ Hits         2522     2880     +358     
+ Misses        196      194       -2     
+ Partials       61       57       -4     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.


🚨 Try these New Features:

@tigerwill90
tigerwill90 requested a review from pawndev November 15, 2024 18:49
@tigerwill90 tigerwill90 changed the title Add Support for hostname routing Add support for hostname routing Nov 15, 2024
@tigerwill90
tigerwill90 marked this pull request as ready for review November 15, 2024 23:48
@tigerwill90
tigerwill90 merged commit 4b593ae into master Nov 18, 2024
@tigerwill90
tigerwill90 deleted the feat/support-domain-routing branch November 18, 2024 20:37
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.

1 participant