1
0
Fork 0

Bring back v2 rule matchers

This commit is contained in:
Romain 2024-01-23 11:34:05 +01:00 committed by GitHub
parent 21da705ec9
commit 683e2ee5c6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
54 changed files with 3773 additions and 114 deletions

View file

@ -73,7 +73,7 @@ func TestClientIPMatcher(t *testing.T) {
muxer, err := NewMuxer()
require.NoError(t, err)
err = muxer.AddRoute(test.rule, 0, handler)
err = muxer.AddRoute(test.rule, "", 0, handler)
if test.expectedError {
require.Error(t, err)
return
@ -147,7 +147,7 @@ func TestMethodMatcher(t *testing.T) {
muxer, err := NewMuxer()
require.NoError(t, err)
err = muxer.AddRoute(test.rule, 0, handler)
err = muxer.AddRoute(test.rule, "", 0, handler)
if test.expectedError {
require.Error(t, err)
return
@ -265,7 +265,7 @@ func TestHostMatcher(t *testing.T) {
muxer, err := NewMuxer()
require.NoError(t, err)
err = muxer.AddRoute(test.rule, 0, handler)
err = muxer.AddRoute(test.rule, "", 0, handler)
if test.expectedError {
require.Error(t, err)
return
@ -365,7 +365,7 @@ func TestHostRegexpMatcher(t *testing.T) {
muxer, err := NewMuxer()
require.NoError(t, err)
err = muxer.AddRoute(test.rule, 0, handler)
err = muxer.AddRoute(test.rule, "", 0, handler)
if test.expectedError {
require.Error(t, err)
return
@ -439,7 +439,7 @@ func TestPathMatcher(t *testing.T) {
muxer, err := NewMuxer()
require.NoError(t, err)
err = muxer.AddRoute(test.rule, 0, handler)
err = muxer.AddRoute(test.rule, "", 0, handler)
if test.expectedError {
require.Error(t, err)
return
@ -532,7 +532,7 @@ func TestPathRegexpMatcher(t *testing.T) {
muxer, err := NewMuxer()
require.NoError(t, err)
err = muxer.AddRoute(test.rule, 0, handler)
err = muxer.AddRoute(test.rule, "", 0, handler)
if test.expectedError {
require.Error(t, err)
return
@ -604,7 +604,7 @@ func TestPathPrefixMatcher(t *testing.T) {
muxer, err := NewMuxer()
require.NoError(t, err)
err = muxer.AddRoute(test.rule, 0, handler)
err = muxer.AddRoute(test.rule, "", 0, handler)
if test.expectedError {
require.Error(t, err)
return
@ -692,7 +692,7 @@ func TestHeaderMatcher(t *testing.T) {
muxer, err := NewMuxer()
require.NoError(t, err)
err = muxer.AddRoute(test.rule, 0, handler)
err = muxer.AddRoute(test.rule, "", 0, handler)
if test.expectedError {
require.Error(t, err)
return
@ -800,7 +800,7 @@ func TestHeaderRegexpMatcher(t *testing.T) {
muxer, err := NewMuxer()
require.NoError(t, err)
err = muxer.AddRoute(test.rule, 0, handler)
err = muxer.AddRoute(test.rule, "", 0, handler)
if test.expectedError {
require.Error(t, err)
return
@ -889,7 +889,7 @@ func TestQueryMatcher(t *testing.T) {
muxer, err := NewMuxer()
require.NoError(t, err)
err = muxer.AddRoute(test.rule, 0, handler)
err = muxer.AddRoute(test.rule, "", 0, handler)
if test.expectedError {
require.Error(t, err)
return
@ -1003,7 +1003,7 @@ func TestQueryRegexpMatcher(t *testing.T) {
muxer, err := NewMuxer()
require.NoError(t, err)
err = muxer.AddRoute(test.rule, 0, handler)
err = muxer.AddRoute(test.rule, "", 0, handler)
if test.expectedError {
require.Error(t, err)
return

View file

@ -0,0 +1,226 @@
package http
import (
"fmt"
"net/http"
"strings"
"github.com/gorilla/mux"
"github.com/rs/zerolog/log"
"github.com/traefik/traefik/v3/pkg/ip"
"github.com/traefik/traefik/v3/pkg/middlewares/requestdecorator"
)
var httpFuncsV2 = map[string]func(*matchersTree, ...string) error{
"Host": hostV2,
"HostHeader": hostV2,
"HostRegexp": hostRegexpV2,
"ClientIP": clientIPV2,
"Path": pathV2,
"PathPrefix": pathPrefixV2,
"Method": methodsV2,
"Headers": headersV2,
"HeadersRegexp": headersRegexpV2,
"Query": queryV2,
}
func pathV2(tree *matchersTree, paths ...string) error {
for _, path := range paths {
if !strings.HasPrefix(path, "/") {
return fmt.Errorf("path %q does not start with a '/'", path)
}
}
tree.matcher = func(req *http.Request) bool {
for _, path := range paths {
if req.URL.Path == path {
return true
}
}
return false
}
return nil
}
func pathPrefixV2(tree *matchersTree, paths ...string) error {
for _, path := range paths {
if !strings.HasPrefix(path, "/") {
return fmt.Errorf("path %q does not start with a '/'", path)
}
}
tree.matcher = func(req *http.Request) bool {
for _, path := range paths {
if strings.HasPrefix(req.URL.Path, path) {
return true
}
}
return false
}
return nil
}
func hostV2(tree *matchersTree, hosts ...string) error {
for i, host := range hosts {
if !IsASCII(host) {
return fmt.Errorf("invalid value %q for \"Host\" matcher, non-ASCII characters are not allowed", host)
}
hosts[i] = strings.ToLower(host)
}
tree.matcher = func(req *http.Request) bool {
reqHost := requestdecorator.GetCanonizedHost(req.Context())
if len(reqHost) == 0 {
// If the request is an HTTP/1.0 request, then a Host may not be defined.
if req.ProtoAtLeast(1, 1) {
log.Ctx(req.Context()).Warn().Msgf("Could not retrieve CanonizedHost, rejecting %s", req.Host)
}
return false
}
flatH := requestdecorator.GetCNAMEFlatten(req.Context())
if len(flatH) > 0 {
for _, host := range hosts {
if strings.EqualFold(reqHost, host) || strings.EqualFold(flatH, host) {
return true
}
log.Ctx(req.Context()).Debug().Msgf("CNAMEFlattening: request %s which resolved to %s, is not matched to route %s", reqHost, flatH, host)
}
return false
}
for _, host := range hosts {
if reqHost == host {
return true
}
// Check for match on trailing period on host
if last := len(host) - 1; last >= 0 && host[last] == '.' {
h := host[:last]
if reqHost == h {
return true
}
}
// Check for match on trailing period on request
if last := len(reqHost) - 1; last >= 0 && reqHost[last] == '.' {
h := reqHost[:last]
if h == host {
return true
}
}
}
return false
}
return nil
}
func clientIPV2(tree *matchersTree, clientIPs ...string) error {
checker, err := ip.NewChecker(clientIPs)
if err != nil {
return fmt.Errorf("could not initialize IP Checker for \"ClientIP\" matcher: %w", err)
}
strategy := ip.RemoteAddrStrategy{}
tree.matcher = func(req *http.Request) bool {
ok, err := checker.Contains(strategy.GetIP(req))
if err != nil {
log.Ctx(req.Context()).Warn().Err(err).Msg("\"ClientIP\" matcher: could not match remote address")
return false
}
return ok
}
return nil
}
func methodsV2(tree *matchersTree, methods ...string) error {
route := mux.NewRouter().NewRoute()
route.Methods(methods...)
if err := route.GetError(); err != nil {
return err
}
tree.matcher = func(req *http.Request) bool {
return route.Match(req, &mux.RouteMatch{})
}
return nil
}
func headersV2(tree *matchersTree, headers ...string) error {
route := mux.NewRouter().NewRoute()
route.Headers(headers...)
if err := route.GetError(); err != nil {
return err
}
tree.matcher = func(req *http.Request) bool {
return route.Match(req, &mux.RouteMatch{})
}
return nil
}
func queryV2(tree *matchersTree, query ...string) error {
var queries []string
for _, elem := range query {
queries = append(queries, strings.SplitN(elem, "=", 2)...)
}
route := mux.NewRouter().NewRoute()
route.Queries(queries...)
if err := route.GetError(); err != nil {
return err
}
tree.matcher = func(req *http.Request) bool {
return route.Match(req, &mux.RouteMatch{})
}
return nil
}
func hostRegexpV2(tree *matchersTree, hosts ...string) error {
router := mux.NewRouter()
for _, host := range hosts {
if !IsASCII(host) {
return fmt.Errorf("invalid value %q for HostRegexp matcher, non-ASCII characters are not allowed", host)
}
tmpRt := router.Host(host)
if tmpRt.GetError() != nil {
return tmpRt.GetError()
}
}
tree.matcher = func(req *http.Request) bool {
return router.Match(req, &mux.RouteMatch{})
}
return nil
}
func headersRegexpV2(tree *matchersTree, headers ...string) error {
route := mux.NewRouter().NewRoute()
route.HeadersRegexp(headers...)
if err := route.GetError(); err != nil {
return err
}
tree.matcher = func(req *http.Request) bool {
return route.Match(req, &mux.RouteMatch{})
}
return nil
}

File diff suppressed because it is too large Load diff

View file

@ -12,8 +12,9 @@ import (
// Muxer handles routing with rules.
type Muxer struct {
routes routes
parser predicate.Parser
routes routes
parser predicate.Parser
parserV2 predicate.Parser
}
// NewMuxer returns a new muxer instance.
@ -28,8 +29,19 @@ func NewMuxer() (*Muxer, error) {
return nil, fmt.Errorf("error while creating parser: %w", err)
}
var matchersV2 []string
for matcher := range httpFuncsV2 {
matchersV2 = append(matchersV2, matcher)
}
parserV2, err := rules.NewParser(matchersV2)
if err != nil {
return nil, fmt.Errorf("error while creating v2 parser: %w", err)
}
return &Muxer{
parser: parser,
parser: parser,
parserV2: parserV2,
}, nil
}
@ -53,10 +65,26 @@ func GetRulePriority(rule string) int {
}
// AddRoute add a new route to the router.
func (m *Muxer) AddRoute(rule string, priority int, handler http.Handler) error {
parse, err := m.parser.Parse(rule)
if err != nil {
return fmt.Errorf("error while parsing rule %s: %w", rule, err)
func (m *Muxer) AddRoute(rule string, syntax string, priority int, handler http.Handler) error {
var parse interface{}
var err error
var matcherFuncs map[string]func(*matchersTree, ...string) error
switch syntax {
case "v2":
parse, err = m.parserV2.Parse(rule)
if err != nil {
return fmt.Errorf("error while parsing rule %s: %w", rule, err)
}
matcherFuncs = httpFuncsV2
default:
parse, err = m.parser.Parse(rule)
if err != nil {
return fmt.Errorf("error while parsing rule %s: %w", rule, err)
}
matcherFuncs = httpFuncs
}
buildTree, ok := parse.(rules.TreeBuilder)
@ -65,7 +93,7 @@ func (m *Muxer) AddRoute(rule string, priority int, handler http.Handler) error
}
var matchers matchersTree
err = matchers.addRule(buildTree())
err = matchers.addRule(buildTree(), matcherFuncs)
if err != nil {
return fmt.Errorf("error while adding rule %s: %w", rule, err)
}
@ -87,6 +115,9 @@ func ParseDomains(rule string) ([]string, error) {
for matcher := range httpFuncs {
matchers = append(matchers, matcher)
}
for matcher := range httpFuncsV2 {
matchers = append(matchers, matcher)
}
parser, err := rules.NewParser(matchers)
if err != nil {
@ -166,25 +197,27 @@ func (m *matchersTree) match(req *http.Request) bool {
}
}
func (m *matchersTree) addRule(rule *rules.Tree) error {
type matcherFuncs map[string]func(*matchersTree, ...string) error
func (m *matchersTree) addRule(rule *rules.Tree, funcs matcherFuncs) error {
switch rule.Matcher {
case "and", "or":
m.operator = rule.Matcher
m.left = &matchersTree{}
err := m.left.addRule(rule.RuleLeft)
err := m.left.addRule(rule.RuleLeft, funcs)
if err != nil {
return fmt.Errorf("error while adding rule %s: %w", rule.Matcher, err)
}
m.right = &matchersTree{}
return m.right.addRule(rule.RuleRight)
return m.right.addRule(rule.RuleRight, funcs)
default:
err := rules.CheckRule(rule)
if err != nil {
return fmt.Errorf("error while checking rule %s: %w", rule.Matcher, err)
}
err = httpFuncs[rule.Matcher](m, rule.Value...)
err = funcs[rule.Matcher](m, rule.Value...)
if err != nil {
return fmt.Errorf("error while adding rule %s: %w", rule.Matcher, err)
}

View file

@ -231,7 +231,7 @@ func TestMuxer(t *testing.T) {
require.NoError(t, err)
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
err = muxer.AddRoute(test.rule, 0, handler)
err = muxer.AddRoute(test.rule, "", 0, handler)
if test.expectedError {
require.Error(t, err)
return
@ -394,7 +394,7 @@ func Test_addRoutePriority(t *testing.T) {
route.priority = GetRulePriority(route.rule)
}
err := muxer.AddRoute(route.rule, route.priority, handler)
err := muxer.AddRoute(route.rule, "", route.priority, handler)
require.NoError(t, err, route.rule)
}
@ -519,7 +519,7 @@ func TestEmptyHost(t *testing.T) {
muxer, err := NewMuxer()
require.NoError(t, err)
err = muxer.AddRoute(test.rule, 0, handler)
err = muxer.AddRoute(test.rule, "", 0, handler)
require.NoError(t, err)
// RequestDecorator is necessary for the host rule