1
0
Fork 0

Refactor new muxer to have only one parser instance

This commit is contained in:
Julien Salleyron 2025-05-26 17:12:08 +02:00 committed by GitHub
parent 55e6d327bc
commit fa18c35a9a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 240 additions and 105 deletions

View file

@ -13,7 +13,7 @@ import (
"github.com/traefik/traefik/v3/pkg/middlewares/requestdecorator"
)
var httpFuncs = map[string]func(*matchersTree, ...string) error{
var httpFuncs = matcherBuilderFuncs{
"ClientIP": expectNParameters(clientIP, 1),
"Method": expectNParameters(method, 1),
"Host": expectNParameters(host, 1),

View file

@ -69,9 +69,11 @@ func TestClientIPMatcher(t *testing.T) {
t.Parallel()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
muxer, err := NewMuxer()
parser, err := NewSyntaxParser()
require.NoError(t, err)
muxer := NewMuxer(parser)
err = muxer.AddRoute(test.rule, "", 0, handler)
if test.expectedError {
require.Error(t, err)
@ -142,9 +144,11 @@ func TestMethodMatcher(t *testing.T) {
t.Parallel()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
muxer, err := NewMuxer()
parser, err := NewSyntaxParser()
require.NoError(t, err)
muxer := NewMuxer(parser)
err = muxer.AddRoute(test.rule, "", 0, handler)
if test.expectedError {
require.Error(t, err)
@ -259,9 +263,11 @@ func TestHostMatcher(t *testing.T) {
t.Parallel()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
muxer, err := NewMuxer()
parser, err := NewSyntaxParser()
require.NoError(t, err)
muxer := NewMuxer(parser)
err = muxer.AddRoute(test.rule, "", 0, handler)
if test.expectedError {
require.Error(t, err)
@ -358,9 +364,11 @@ func TestHostRegexpMatcher(t *testing.T) {
t.Parallel()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
muxer, err := NewMuxer()
parser, err := NewSyntaxParser()
require.NoError(t, err)
muxer := NewMuxer(parser)
err = muxer.AddRoute(test.rule, "", 0, handler)
if test.expectedError {
require.Error(t, err)
@ -431,9 +439,11 @@ func TestPathMatcher(t *testing.T) {
t.Parallel()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
muxer, err := NewMuxer()
parser, err := NewSyntaxParser()
require.NoError(t, err)
muxer := NewMuxer(parser)
err = muxer.AddRoute(test.rule, "", 0, handler)
if test.expectedError {
require.Error(t, err)
@ -523,9 +533,11 @@ func TestPathRegexpMatcher(t *testing.T) {
t.Parallel()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
muxer, err := NewMuxer()
parser, err := NewSyntaxParser()
require.NoError(t, err)
muxer := NewMuxer(parser)
err = muxer.AddRoute(test.rule, "", 0, handler)
if test.expectedError {
require.Error(t, err)
@ -594,9 +606,11 @@ func TestPathPrefixMatcher(t *testing.T) {
t.Parallel()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
muxer, err := NewMuxer()
parser, err := NewSyntaxParser()
require.NoError(t, err)
muxer := NewMuxer(parser)
err = muxer.AddRoute(test.rule, "", 0, handler)
if test.expectedError {
require.Error(t, err)
@ -680,9 +694,11 @@ func TestHeaderMatcher(t *testing.T) {
t.Parallel()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
muxer, err := NewMuxer()
parser, err := NewSyntaxParser()
require.NoError(t, err)
muxer := NewMuxer(parser)
err = muxer.AddRoute(test.rule, "", 0, handler)
if test.expectedError {
require.Error(t, err)
@ -787,9 +803,11 @@ func TestHeaderRegexpMatcher(t *testing.T) {
t.Parallel()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
muxer, err := NewMuxer()
parser, err := NewSyntaxParser()
require.NoError(t, err)
muxer := NewMuxer(parser)
err = muxer.AddRoute(test.rule, "", 0, handler)
if test.expectedError {
require.Error(t, err)
@ -875,9 +893,11 @@ func TestQueryMatcher(t *testing.T) {
t.Parallel()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
muxer, err := NewMuxer()
parser, err := NewSyntaxParser()
require.NoError(t, err)
muxer := NewMuxer(parser)
err = muxer.AddRoute(test.rule, "", 0, handler)
if test.expectedError {
require.Error(t, err)
@ -988,9 +1008,11 @@ func TestQueryRegexpMatcher(t *testing.T) {
t.Parallel()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
muxer, err := NewMuxer()
parser, err := NewSyntaxParser()
require.NoError(t, err)
muxer := NewMuxer(parser)
err = muxer.AddRoute(test.rule, "", 0, handler)
if test.expectedError {
require.Error(t, err)

View file

@ -11,7 +11,7 @@ import (
"github.com/traefik/traefik/v3/pkg/middlewares/requestdecorator"
)
var httpFuncsV2 = map[string]func(*matchersTree, ...string) error{
var httpFuncsV2 = matcherBuilderFuncs{
"Host": hostV2,
"HostHeader": hostV2,
"HostRegexp": hostRegexpV2,

View file

@ -73,9 +73,11 @@ func TestClientIPV2Matcher(t *testing.T) {
t.Parallel()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
muxer, err := NewMuxer()
parser, err := NewSyntaxParser()
require.NoError(t, err)
muxer := NewMuxer(parser)
err = muxer.AddRoute(test.rule, "v2", 0, handler)
if test.expectedError {
require.Error(t, err)
@ -149,9 +151,11 @@ func TestMethodV2Matcher(t *testing.T) {
t.Parallel()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
muxer, err := NewMuxer()
parser, err := NewSyntaxParser()
require.NoError(t, err)
muxer := NewMuxer(parser)
err = muxer.AddRoute(test.rule, "v2", 0, handler)
if test.expectedError {
require.Error(t, err)
@ -273,9 +277,11 @@ func TestHostV2Matcher(t *testing.T) {
t.Parallel()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
muxer, err := NewMuxer()
parser, err := NewSyntaxParser()
require.NoError(t, err)
muxer := NewMuxer(parser)
err = muxer.AddRoute(test.rule, "v2", 0, handler)
if test.expectedError {
require.Error(t, err)
@ -375,9 +381,11 @@ func TestHostRegexpV2Matcher(t *testing.T) {
t.Parallel()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
muxer, err := NewMuxer()
parser, err := NewSyntaxParser()
require.NoError(t, err)
muxer := NewMuxer(parser)
err = muxer.AddRoute(test.rule, "v2", 0, handler)
if test.expectedError {
require.Error(t, err)
@ -469,9 +477,11 @@ func TestPathV2Matcher(t *testing.T) {
t.Parallel()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
muxer, err := NewMuxer()
parser, err := NewSyntaxParser()
require.NoError(t, err)
muxer := NewMuxer(parser)
err = muxer.AddRoute(test.rule, "v2", 0, handler)
if test.expectedError {
require.Error(t, err)
@ -561,9 +571,11 @@ func TestPathPrefixV2Matcher(t *testing.T) {
t.Parallel()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
muxer, err := NewMuxer()
parser, err := NewSyntaxParser()
require.NoError(t, err)
muxer := NewMuxer(parser)
err = muxer.AddRoute(test.rule, "v2", 0, handler)
if test.expectedError {
require.Error(t, err)
@ -647,9 +659,11 @@ func TestHeadersMatcher(t *testing.T) {
t.Parallel()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
muxer, err := NewMuxer()
parser, err := NewSyntaxParser()
require.NoError(t, err)
muxer := NewMuxer(parser)
err = muxer.AddRoute(test.rule, "v2", 0, handler)
if test.expectedError {
require.Error(t, err)
@ -754,9 +768,11 @@ func TestHeaderRegexpV2Matcher(t *testing.T) {
t.Parallel()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
muxer, err := NewMuxer()
parser, err := NewSyntaxParser()
require.NoError(t, err)
muxer := NewMuxer(parser)
err = muxer.AddRoute(test.rule, "v2", 0, handler)
if test.expectedError {
require.Error(t, err)
@ -846,9 +862,11 @@ func TestHostRegexp(t *testing.T) {
t.Parallel()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
muxer, err := NewMuxer()
parser, err := NewSyntaxParser()
require.NoError(t, err)
muxer := NewMuxer(parser)
err = muxer.AddRoute(test.hostExp, "v2", 0, handler)
require.NoError(t, err)
@ -1513,9 +1531,11 @@ func Test_addRoute(t *testing.T) {
t.Parallel()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
muxer, err := NewMuxer()
parser, err := NewSyntaxParser()
require.NoError(t, err)
muxer := NewMuxer(parser)
err = muxer.AddRoute(test.rule, "v2", 0, handler)
if test.expectedError {
require.Error(t, err)

View file

@ -7,44 +7,27 @@ import (
"github.com/rs/zerolog/log"
"github.com/traefik/traefik/v3/pkg/rules"
"github.com/vulcand/predicate"
)
type matcherBuilderFuncs map[string]matcherBuilderFunc
type matcherBuilderFunc func(*matchersTree, ...string) error
type MatcherFunc func(*http.Request) bool
// Muxer handles routing with rules.
type Muxer struct {
routes routes
parser predicate.Parser
parserV2 predicate.Parser
parser SyntaxParser
defaultHandler http.Handler
}
// NewMuxer returns a new muxer instance.
func NewMuxer() (*Muxer, error) {
var matchers []string
for matcher := range httpFuncs {
matchers = append(matchers, matcher)
}
parser, err := rules.NewParser(matchers)
if err != nil {
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)
}
func NewMuxer(parser SyntaxParser) *Muxer {
return &Muxer{
parser: parser,
parserV2: parserV2,
defaultHandler: http.NotFoundHandler(),
}, nil
}
}
// ServeHTTP forwards the connection to the matching HTTP handler.
@ -73,36 +56,9 @@ func GetRulePriority(rule string) int {
// AddRoute add a new route to the router.
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)
if !ok {
return fmt.Errorf("error while parsing rule %s", rule)
}
var matchers matchersTree
err = matchers.addRule(buildTree(), matcherFuncs)
matchers, err := m.parser.parse(syntax, rule)
if err != nil {
return fmt.Errorf("error while adding rule %s: %w", rule, err)
return fmt.Errorf("error while parsing rule %s: %w", rule, err)
}
m.routes = append(m.routes, &route{
@ -173,7 +129,7 @@ type matchersTree struct {
// matcher is a matcher func used to match HTTP request properties.
// If matcher is not nil, it means that this matcherTree is a leaf of the tree.
// It is therefore mutually exclusive with left and right.
matcher func(*http.Request) bool
matcher MatcherFunc
// operator to combine the evaluation of left and right leaves.
operator string
// Mutually exclusive with matcher.
@ -204,9 +160,7 @@ func (m *matchersTree) match(req *http.Request) bool {
}
}
type matcherFuncs map[string]func(*matchersTree, ...string) error
func (m *matchersTree) addRule(rule *rules.Tree, funcs matcherFuncs) error {
func (m *matchersTree) addRule(rule *rules.Tree, funcs matcherBuilderFuncs) error {
switch rule.Matcher {
case "and", "or":
m.operator = rule.Matcher

View file

@ -225,9 +225,11 @@ func TestMuxer(t *testing.T) {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
muxer, err := NewMuxer()
parser, err := NewSyntaxParser()
require.NoError(t, err)
muxer := NewMuxer(parser)
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
err = muxer.AddRoute(test.rule, "", 0, handler)
if test.expectedError {
@ -378,9 +380,11 @@ func Test_addRoutePriority(t *testing.T) {
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
muxer, err := NewMuxer()
parser, err := NewSyntaxParser()
require.NoError(t, err)
muxer := NewMuxer(parser)
for _, route := range test.cases {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-From", route.xFrom)
@ -510,9 +514,11 @@ func TestEmptyHost(t *testing.T) {
t.Parallel()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
muxer, err := NewMuxer()
parser, err := NewSyntaxParser()
require.NoError(t, err)
muxer := NewMuxer(parser)
err = muxer.AddRoute(test.rule, "", 0, handler)
require.NoError(t, err)

103
pkg/muxer/http/parser.go Normal file
View file

@ -0,0 +1,103 @@
package http
import (
"errors"
"fmt"
"maps"
"slices"
"strings"
"github.com/traefik/traefik/v3/pkg/rules"
"github.com/vulcand/predicate"
)
type SyntaxParser struct {
parsers map[string]*parser
}
type Options func(map[string]matcherBuilderFuncs)
func WithMatcher(syntax, matcherName string, builderFunc func(params ...string) (MatcherFunc, error)) Options {
return func(syntaxFuncs map[string]matcherBuilderFuncs) {
syntax = strings.ToLower(syntax)
syntaxFuncs[syntax][matcherName] = func(tree *matchersTree, s ...string) error {
matcher, err := builderFunc(s...)
if err != nil {
return fmt.Errorf("building matcher: %w", err)
}
tree.matcher = matcher
return nil
}
}
}
func NewSyntaxParser(opts ...Options) (SyntaxParser, error) {
syntaxFuncs := map[string]matcherBuilderFuncs{
"v2": httpFuncsV2,
"v3": httpFuncs,
}
for _, opt := range opts {
opt(syntaxFuncs)
}
parsers := map[string]*parser{}
for syntax, funcs := range syntaxFuncs {
var err error
parsers[syntax], err = newParser(funcs)
if err != nil {
return SyntaxParser{}, err
}
}
return SyntaxParser{
parsers: parsers,
}, nil
}
func (s SyntaxParser) parse(syntax string, rule string) (matchersTree, error) {
parser, ok := s.parsers[syntax]
if !ok {
parser = s.parsers["v3"]
}
return parser.parse(rule)
}
func newParser(funcs matcherBuilderFuncs) (*parser, error) {
p, err := rules.NewParser(slices.Collect(maps.Keys(funcs)))
if err != nil {
return nil, err
}
return &parser{
parser: p,
matcherFuncs: funcs,
}, nil
}
type parser struct {
parser predicate.Parser
matcherFuncs matcherBuilderFuncs
}
func (p *parser) parse(rule string) (matchersTree, error) {
parse, err := p.parser.Parse(rule)
if err != nil {
return matchersTree{}, fmt.Errorf("parsing rule %s: %w", rule, err)
}
buildTree, ok := parse.(rules.TreeBuilder)
if !ok {
return matchersTree{}, errors.New("obtaining build tree")
}
var matchers matchersTree
err = matchers.addRule(buildTree(), p.matcherFuncs)
if err != nil {
return matchersTree{}, fmt.Errorf("adding rule %s: %w", rule, err)
}
return matchers, nil
}