Compute HTTPRoute priorities
Co-authored-by: Romain <rtribotte@users.noreply.github.com>
This commit is contained in:
parent
e9bd2b45ac
commit
c0a2e6b4b6
5 changed files with 429 additions and 347 deletions
|
@ -128,12 +128,12 @@ func (p *Provider) loadHTTPRoute(ctx context.Context, client Client, listener ga
|
|||
},
|
||||
}
|
||||
|
||||
hostRule := hostRule(hostnames)
|
||||
|
||||
for _, routeRule := range route.Spec.Rules {
|
||||
rule, priority := buildRouterRule(hostnames, routeRule.Matches)
|
||||
router := dynamic.Router{
|
||||
RuleSyntax: "v3",
|
||||
Rule: routerRule(routeRule, hostRule),
|
||||
Rule: rule,
|
||||
Priority: priority,
|
||||
EntryPoints: []string{listener.EPName},
|
||||
}
|
||||
if listener.Protocol == gatev1.HTTPSProtocolType {
|
||||
|
@ -418,12 +418,17 @@ func loadHTTPServers(client Client, namespace string, backendRef gatev1.HTTPBack
|
|||
return lb, nil
|
||||
}
|
||||
|
||||
func hostRule(hostnames []gatev1.Hostname) string {
|
||||
func buildHostRule(hostnames []gatev1.Hostname) (string, int) {
|
||||
var rules []string
|
||||
var priority int
|
||||
|
||||
for _, hostname := range hostnames {
|
||||
host := string(hostname)
|
||||
|
||||
if priority < len(host) {
|
||||
priority = len(host)
|
||||
}
|
||||
|
||||
wildcard := strings.Count(host, "*")
|
||||
if wildcard == 0 {
|
||||
rules = append(rules, fmt.Sprintf("Host(`%s`)", host))
|
||||
|
@ -436,86 +441,123 @@ func hostRule(hostnames []gatev1.Hostname) string {
|
|||
|
||||
switch len(rules) {
|
||||
case 0:
|
||||
return ""
|
||||
return "", 0
|
||||
case 1:
|
||||
return rules[0]
|
||||
return rules[0], priority
|
||||
default:
|
||||
return fmt.Sprintf("(%s)", strings.Join(rules, " || "))
|
||||
return fmt.Sprintf("(%s)", strings.Join(rules, " || ")), priority
|
||||
}
|
||||
}
|
||||
|
||||
func routerRule(routeRule gatev1.HTTPRouteRule, hostRule string) string {
|
||||
var rule string
|
||||
// buildRouterRule builds the route rule and computes its priority.
|
||||
// The current priority computing is rather naive but aims to fulfill Conformance tests suite requirement.
|
||||
// The priority is computed to match the following precedence order:
|
||||
//
|
||||
// * "Exact" path match. (+100000)
|
||||
// * "Prefix" path match with largest number of characters. (+10000) PathRegex (+1000)
|
||||
// * Method match. (not implemented)
|
||||
// * Largest number of header matches. (+100 each) or with PathRegex (+10 each)
|
||||
// * Largest number of query param matches. (not implemented)
|
||||
//
|
||||
// In case of multiple matches for a route, the maximum priority among all matches is retain.
|
||||
func buildRouterRule(hostnames []gatev1.Hostname, routeMatches []gatev1.HTTPRouteMatch) (string, int) {
|
||||
var matchesRules []string
|
||||
var maxPriority int
|
||||
|
||||
for _, match := range routeRule.Matches {
|
||||
for _, match := range routeMatches {
|
||||
path := ptr.Deref(match.Path, gatev1.HTTPPathMatch{
|
||||
Type: ptr.To(gatev1.PathMatchPathPrefix),
|
||||
Value: ptr.To("/"),
|
||||
})
|
||||
pathType := ptr.Deref(path.Type, gatev1.PathMatchPathPrefix)
|
||||
pathValue := ptr.Deref(path.Value, "/")
|
||||
|
||||
var priority int
|
||||
var matchRules []string
|
||||
switch pathType {
|
||||
case gatev1.PathMatchExact:
|
||||
matchRules = append(matchRules, fmt.Sprintf("Path(`%s`)", pathValue))
|
||||
case gatev1.PathMatchPathPrefix:
|
||||
matchRules = append(matchRules, buildPathMatchPathPrefixRule(pathValue))
|
||||
case gatev1.PathMatchRegularExpression:
|
||||
matchRules = append(matchRules, fmt.Sprintf("PathRegexp(`%s`)", pathValue))
|
||||
}
|
||||
|
||||
matchRules = append(matchRules, headerRules(match.Headers)...)
|
||||
pathRule, pathPriority := buildPathRule(path)
|
||||
matchRules = append(matchRules, pathRule)
|
||||
priority += pathPriority
|
||||
|
||||
headerRules, headersPriority := buildHeaderRules(match.Headers)
|
||||
matchRules = append(matchRules, headerRules...)
|
||||
priority += headersPriority
|
||||
|
||||
matchesRules = append(matchesRules, strings.Join(matchRules, " && "))
|
||||
}
|
||||
|
||||
// If no matches are specified, the default is a prefix
|
||||
// path match on "/", which has the effect of matching every
|
||||
// HTTP request.
|
||||
if len(routeRule.Matches) == 0 {
|
||||
matchesRules = append(matchesRules, "PathPrefix(`/`)")
|
||||
}
|
||||
|
||||
if hostRule != "" {
|
||||
if len(matchesRules) == 0 {
|
||||
return hostRule
|
||||
if priority > maxPriority {
|
||||
maxPriority = priority
|
||||
}
|
||||
rule += hostRule + " && "
|
||||
}
|
||||
|
||||
if len(matchesRules) == 1 {
|
||||
return rule + matchesRules[0]
|
||||
hostRule, hostPriority := buildHostRule(hostnames)
|
||||
|
||||
matchesRulesStr := strings.Join(matchesRules, " || ")
|
||||
|
||||
if hostRule == "" && matchesRulesStr == "" {
|
||||
return "PathPrefix(`/`)", 1
|
||||
}
|
||||
|
||||
if len(rule) == 0 {
|
||||
return strings.Join(matchesRules, " || ")
|
||||
if hostRule != "" && matchesRulesStr == "" {
|
||||
return hostRule, hostPriority
|
||||
}
|
||||
|
||||
return rule + "(" + strings.Join(matchesRules, " || ") + ")"
|
||||
// Enforce that, at the same priority,
|
||||
// the route with fewer matches (more specific) matches first.
|
||||
maxPriority -= len(matchesRules) * 10
|
||||
if maxPriority < 1 {
|
||||
maxPriority = 1
|
||||
}
|
||||
|
||||
if hostRule == "" {
|
||||
return matchesRulesStr, maxPriority
|
||||
}
|
||||
|
||||
// A route with a host should match over the same route with no host.
|
||||
maxPriority += hostPriority
|
||||
return hostRule + " && " + "(" + matchesRulesStr + ")", maxPriority
|
||||
}
|
||||
|
||||
func headerRules(headers []gatev1.HTTPHeaderMatch) []string {
|
||||
var headerRules []string
|
||||
func buildPathRule(pathMatch gatev1.HTTPPathMatch) (string, int) {
|
||||
pathType := ptr.Deref(pathMatch.Type, gatev1.PathMatchPathPrefix)
|
||||
pathValue := ptr.Deref(pathMatch.Value, "/")
|
||||
|
||||
switch pathType {
|
||||
case gatev1.PathMatchExact:
|
||||
return fmt.Sprintf("Path(`%s`)", pathValue), 100000
|
||||
|
||||
case gatev1.PathMatchPathPrefix:
|
||||
// PathPrefix(`/`) rule is a catch-all,
|
||||
// here we ensure it would be evaluated last.
|
||||
if pathValue == "/" {
|
||||
return "PathPrefix(`/`)", 1
|
||||
}
|
||||
|
||||
pv := strings.TrimSuffix(pathValue, "/")
|
||||
return fmt.Sprintf("(Path(`%[1]s`) || PathPrefix(`%[1]s/`))", pv), 10000 + len(pathValue)*100
|
||||
|
||||
case gatev1.PathMatchRegularExpression:
|
||||
return fmt.Sprintf("PathRegexp(`%s`)", pathValue), 1000 + len(pathValue)*100
|
||||
|
||||
default:
|
||||
return "PathPrefix(`/`)", 1
|
||||
}
|
||||
}
|
||||
|
||||
func buildHeaderRules(headers []gatev1.HTTPHeaderMatch) ([]string, int) {
|
||||
var rules []string
|
||||
var priority int
|
||||
for _, header := range headers {
|
||||
typ := ptr.Deref(header.Type, gatev1.HeaderMatchExact)
|
||||
switch typ {
|
||||
case gatev1.HeaderMatchExact:
|
||||
headerRules = append(headerRules, fmt.Sprintf("Header(`%s`,`%s`)", header.Name, header.Value))
|
||||
rules = append(rules, fmt.Sprintf("Header(`%s`,`%s`)", header.Name, header.Value))
|
||||
priority += 100
|
||||
case gatev1.HeaderMatchRegularExpression:
|
||||
headerRules = append(headerRules, fmt.Sprintf("HeaderRegexp(`%s`,`%s`)", header.Name, header.Value))
|
||||
rules = append(rules, fmt.Sprintf("HeaderRegexp(`%s`,`%s`)", header.Name, header.Value))
|
||||
priority += 10
|
||||
}
|
||||
}
|
||||
return headerRules
|
||||
}
|
||||
|
||||
func buildPathMatchPathPrefixRule(path string) string {
|
||||
if path == "/" {
|
||||
return "PathPrefix(`/`)"
|
||||
}
|
||||
|
||||
path = strings.TrimSuffix(path, "/")
|
||||
return fmt.Sprintf("(Path(`%[1]s`) || PathPrefix(`%[1]s/`))", path)
|
||||
return rules, priority
|
||||
}
|
||||
|
||||
// createRequestHeaderModifier does not enforce/check the configuration,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue