Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for infix wildcard #46

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open

Conversation

tigerwill90
Copy link
Owner

@tigerwill90 tigerwill90 commented Oct 24, 2024

Introduction

This pull request introduces support for infix catch-all wildcard parameters, enhancing the routing flexibility. Developers can now define routes with catch-all parameters (*{param}) not only at the end but also in the middle of the route path. This allows for more dynamic and versatile URL matching patterns, catering to a wider range of application requirements.

Background and Motivation

Previously, the router supported:

  • Named Parameters: {param} matching exactly one non-empty path segment.
  • Catch-All Parameters: *{param} matching zero or more segments, but only at the end of a route path.

This limitation restricted the ability to define routes that require matching variable-length path segments within the middle of a URL. Infix catch-all parameters allow for more complex and descriptive routing patterns, such as /*/track (a feature needed for Wiztrack that was not supported by gin).

Breaking Change

One subtle breaking change has been introduced: catch-all wildcards no longer match empty segments, including at the end of a route. All matches now have to be explicit. However, it's now possible to define routes such as /path/ and /path/*{any} to reproduce the same empty match behavior (which was previously not allowed).

Handling Empty Segments

To match both routes with and without additional path segments, you can register two routes:

// Matches the path with no additional segments
f.Handle("GET", "/path/", handlerForEmpty)

// Matches the path with one or more additional segments
f.Handle("GET", "/path/*{any}", handlerForCatchAll)

This explicit approach ensures that you only handle empty segments when you intend to.

Security Consideration

A common scenario is serving static content using a route like /static/*{filepath}. In many routers (e.g., standard http.ServeMux, Gin, Echo), this route matches empty segments by default, potentially allowing directory listing, of the base folder which can be a security concern.

By not matching empty segments with catch-all wildcards, we can at least prevents unintended directory listing.

Future Considerations

Serving static files and handling directory listings are common needs. In the future, we plan to introduce an API helper to simplify serving static content.

Infix Wildcards

Catch-all wildcards can now be placed anywhere in the path, not just at the end.

Example:

Pattern: /assets/*{path}/thumbnail

- /assets/images/thumbnail           matches, {path} = "images"
- /assets/photos/2021/thumbnail      matches, {path} = "photos/2021"
- /assets//thumbnail                 does not match (empty {path} not matched)

They can also be followed or preceded by named parameters. For example, /foo/*{bar}/{baz} is valid and allows for flexible route definitions.

Note: Catch-all wildcards cannot be consecutive. For example, /foo/*{any1}/*{any2} is not allowed. Since catch-all wildcards match at least one segment, the above pattern is equivalent to /foo/{param}/*{catchAll}, which is allowed and more explicit about which segment matches each parameter.


Differential Benchmark

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          11.28µ ± 4%    12.14µ ± 6%   +7.63% (p=0.000 n=10)
GithubParamsAll-16    75.66n ± 2%    84.42n ± 3%  +11.58% (p=0.000 n=10)
OverlappingRoute-16   91.59n ± 1%    96.75n ± 2%   +5.63% (p=0.000 n=10)
StaticParallel-16     9.582n ± 5%   10.740n ± 2%  +12.09% (p=0.000 n=10)
CatchAll-16           31.39n ± 6%    35.53n ± 2%  +13.19% (p=0.000 n=10)
CatchAllParallel-16   5.973n ± 5%    5.737n ± 8%        ~ (p=0.289 n=10)
CloneWith-16          63.58n ± 2%    69.98n ± 1%  +10.07% (p=0.000 n=10)
geomean               70.82n         76.40n        +7.89%

Copy link

codecov bot commented Oct 24, 2024

Codecov Report

Attention: Patch coverage is 96.18321% with 10 lines in your changes missing coverage. Please review.

Project coverage is 90.46%. Comparing base (3bea8fb) to head (e0a3c7c).

Files with missing lines Patch % Lines
fox.go 91.52% 3 Missing and 2 partials ⚠️
node.go 89.74% 3 Missing and 1 partial ⚠️
tree.go 99.37% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master      #46      +/-   ##
==========================================
+ Coverage   90.14%   90.46%   +0.32%     
==========================================
  Files          18       18              
  Lines        2202     2832     +630     
==========================================
+ Hits         1985     2562     +577     
- Misses        158      210      +52     
- Partials       59       60       +1     

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

@tigerwill90 tigerwill90 self-assigned this Oct 24, 2024
@tigerwill90 tigerwill90 removed the wip label Oct 27, 2024
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.

2 participants