traefik/pkg/muxer/http/mux.go
Tom Moulard c38d405cfd
Remove containous/mux from HTTP muxer
Co-authored-by: Simon Delicata <simon.delicata@traefik.io>
2022-12-22 17:16:04 +01:00

199 lines
4.8 KiB
Go

package http
import (
"fmt"
"net/http"
"sort"
"github.com/rs/zerolog/log"
"github.com/traefik/traefik/v2/pkg/rules"
"github.com/vulcand/predicate"
)
// Muxer handles routing with rules.
type Muxer struct {
routes routes
parser predicate.Parser
}
// 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)
}
return &Muxer{
parser: parser,
}, nil
}
// ServeHTTP forwards the connection to the matching HTTP handler.
// Serves 404 if no handler is found.
func (m *Muxer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
for _, route := range m.routes {
if route.matchers.match(req) {
route.handler.ServeHTTP(rw, req)
return
}
}
http.NotFoundHandler().ServeHTTP(rw, req)
}
// 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)
}
buildTree, ok := parse.(rules.TreeBuilder)
if !ok {
return fmt.Errorf("error while parsing rule %s", rule)
}
var matchers matchersTree
err = matchers.addRule(buildTree())
if err != nil {
return fmt.Errorf("error while adding rule %s: %w", rule, err)
}
if priority == 0 {
priority = len(rule)
}
m.routes = append(m.routes, &route{
handler: handler,
matchers: matchers,
priority: priority,
})
sort.Sort(m.routes)
return nil
}
// ParseDomains extract domains from rule.
func ParseDomains(rule string) ([]string, 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)
}
parse, err := parser.Parse(rule)
if err != nil {
return nil, fmt.Errorf("error while parsing rule %s: %w", rule, err)
}
buildTree, ok := parse.(rules.TreeBuilder)
if !ok {
return nil, fmt.Errorf("error while parsing rule %s", rule)
}
return buildTree().ParseMatchers([]string{"Host"}), nil
}
// routes implements sort.Interface.
type routes []*route
// Len implements sort.Interface.
func (r routes) Len() int { return len(r) }
// Swap implements sort.Interface.
func (r routes) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
// Less implements sort.Interface.
func (r routes) Less(i, j int) bool { return r[i].priority > r[j].priority }
// route holds the matchers to match HTTP route,
// and the handler that will serve the request.
type route struct {
// matchers tree structure reflecting the rule.
matchers matchersTree
// handler responsible for handling the route.
handler http.Handler
// priority is used to disambiguate between two (or more) rules that would all match for a given request.
// Computed from the matching rule length, if not user-set.
priority int
}
// matchersTree represents the matchers tree structure.
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
// operator to combine the evaluation of left and right leaves.
operator string
// Mutually exclusive with matcher.
left *matchersTree
right *matchersTree
}
func (m *matchersTree) match(req *http.Request) bool {
if m == nil {
// This should never happen as it should have been detected during parsing.
log.Warn().Msg("Rule matcher is nil")
return false
}
if m.matcher != nil {
return m.matcher(req)
}
switch m.operator {
case "or":
return m.left.match(req) || m.right.match(req)
case "and":
return m.left.match(req) && m.right.match(req)
default:
// This should never happen as it should have been detected during parsing.
log.Warn().Str("operator", m.operator).Msg("Invalid rule operator")
return false
}
}
func (m *matchersTree) addRule(rule *rules.Tree) error {
switch rule.Matcher {
case "and", "or":
m.operator = rule.Matcher
m.left = &matchersTree{}
err := m.left.addRule(rule.RuleLeft)
if err != nil {
return fmt.Errorf("error while adding rule %s: %w", rule.Matcher, err)
}
m.right = &matchersTree{}
return m.right.addRule(rule.RuleRight)
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...)
if err != nil {
return fmt.Errorf("error while adding rule %s: %w", rule.Matcher, err)
}
if rule.Not {
matcherFunc := m.matcher
m.matcher = func(req *http.Request) bool {
return !matcherFunc(req)
}
}
}
return nil
}