Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 65 additions & 5 deletions internal/provider/adc/translator/httproute.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package translator
import (
"encoding/json"
"fmt"
"sort"
"strings"

"github.com/api7/gopkg/pkg/log"
Expand Down Expand Up @@ -341,6 +342,33 @@ func (t *Translator) translateBackendRef(tctx *provider.TranslateContext, ref ga
return t.translateEndpointSlice(portName, weight, endpointSlices)
}

// calculateMatchPriority calculate the priority of HTTPRouteMatch, according to the Gateway API specification
// the higher the return value, the higher the priority
func calculateMatchPriority(match *gatewayv1.HTTPRouteMatch) int64 {
var score int64 = 0

// 1. Exact path matches have the highest priority
if match.Path != nil && match.Path.Type != nil && *match.Path.Type == gatewayv1.PathMatchExact {
score += 10000
} else if match.Path != nil && match.Path.Type != nil && *match.Path.Type == gatewayv1.PathMatchPathPrefix && match.Path.Value != nil {
// 2. Prefix path matches, the longer the string, the higher the priority
score += 1000 + int64(len(*match.Path.Value))
}

// 3. Method matching
if match.Method != nil {
score += 100
}

// 4. Header matching, the more headers, the higher the priority
score += int64(len(match.Headers) * 10)

// 5. Query parameter matching, the more query parameters, the higher the priority
score += int64(len(match.QueryParams))

return score
}

func (t *Translator) TranslateHTTPRoute(tctx *provider.TranslateContext, httpRoute *gatewayv1.HTTPRoute) (*TranslateResult, error) {
result := &TranslateResult{}

Expand Down Expand Up @@ -388,6 +416,11 @@ func (t *Translator) TranslateHTTPRoute(tctx *provider.TranslateContext, httpRou
},
},
}
} else {
// Sort the matches by priority
sort.Slice(matches, func(a, b int) bool {
return calculateMatchPriority(&matches[a]) > calculateMatchPriority(&matches[b])
})
}

routes := []*adctypes.Route{}
Expand All @@ -402,6 +435,11 @@ func (t *Translator) TranslateHTTPRoute(tctx *provider.TranslateContext, httpRou
route.ID = id.GenID(name)
route.Labels = labels
route.EnableWebsocket = ptr.To(true)

// Set the route priority
priority := calculateMatchPriority(&match)
route.Priority = &priority

routes = append(routes, route)
}
t.fillHTTPRoutePoliciesForHTTPRoute(tctx, routes, rule)
Expand All @@ -422,7 +460,14 @@ func (t *Translator) translateGatewayHTTPRouteMatch(match *gatewayv1.HTTPRouteMa
case gatewayv1.PathMatchExact:
route.Uris = []string{*match.Path.Value}
case gatewayv1.PathMatchPathPrefix:
route.Uris = []string{*match.Path.Value + "*"}
pathValue := *match.Path.Value
route.Uris = []string{pathValue}

Copy link

Copilot AI May 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new logic separates the original path and its wildcard extension into two URIs. Please add inline documentation to clarify the intent and ensure that this change aligns with the expected backend matching behavior.

Suggested change
// Append wildcard extensions to ensure the route matches both the base path
// and any subpaths. For example, a path "/example" will match "/example"
// and "/example/*". This behavior aligns with the backend's expectation
// for prefix-based path matching.

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI May 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The logic for constructing route URIs based on whether the path ends with a slash is not immediately obvious; consider adding an inline comment to clarify why a different wildcard is appended depending on the trailing slash.

Suggested change
// If the path ends with a slash, append "*" to match all subpaths.
// Otherwise, append "/*" to match the path itself and all its subpaths.

Copilot uses AI. Check for mistakes.
if strings.HasSuffix(pathValue, "/") {
route.Uris = append(route.Uris, pathValue+"*")
} else {
route.Uris = append(route.Uris, pathValue+"/*")
}
case gatewayv1.PathMatchRegularExpression:
var this []adctypes.StringOrSlice
this = append(this, adctypes.StringOrSlice{
Expand All @@ -439,6 +484,11 @@ func (t *Translator) translateGatewayHTTPRouteMatch(match *gatewayv1.HTTPRouteMa
default:
return nil, errors.New("unknown path match type " + string(*match.Path.Type))
}
} else {
/* If no matches are specified, the default is a prefix
path match on "/", which has the effect of matching every
HTTP request. */
route.Uris = []string{"/", "/*"}
}

if len(match.Headers) > 0 {
Expand All @@ -451,7 +501,12 @@ func (t *Translator) translateGatewayHTTPRouteMatch(match *gatewayv1.HTTPRouteMa
StrVal: "http_" + name,
})

Copy link

Copilot AI May 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The default assignment of the header match type improves clarity, but adding a brief comment explaining this default value could help future maintainers understand the decision process.

Suggested change
// Default to HeaderMatchExact if no specific header match type is provided.

Copilot uses AI. Check for mistakes.
switch *header.Type {
matchType := gatewayv1.HeaderMatchExact
if header.Type != nil {
matchType = *header.Type
}

switch matchType {
case gatewayv1.HeaderMatchExact:
this = append(this, adctypes.StringOrSlice{
StrVal: "==",
Expand All @@ -461,7 +516,7 @@ func (t *Translator) translateGatewayHTTPRouteMatch(match *gatewayv1.HTTPRouteMa
StrVal: "~~",
})
default:
return nil, errors.New("unknown header match type " + string(*header.Type))
return nil, errors.New("unknown header match type " + string(matchType))
}

this = append(this, adctypes.StringOrSlice{
Expand All @@ -479,7 +534,12 @@ func (t *Translator) translateGatewayHTTPRouteMatch(match *gatewayv1.HTTPRouteMa
StrVal: "arg_" + strings.ToLower(fmt.Sprintf("%v", query.Name)),
})

switch *query.Type {
queryType := gatewayv1.QueryParamMatchExact
if query.Type != nil {
queryType = *query.Type
}

switch queryType {
case gatewayv1.QueryParamMatchExact:
this = append(this, adctypes.StringOrSlice{
StrVal: "==",
Expand All @@ -489,7 +549,7 @@ func (t *Translator) translateGatewayHTTPRouteMatch(match *gatewayv1.HTTPRouteMa
StrVal: "~~",
})
default:
return nil, errors.New("unknown query match type " + string(*query.Type))
return nil, errors.New("unknown query match type " + string(queryType))
}

this = append(this, adctypes.StringOrSlice{
Expand Down
4 changes: 2 additions & 2 deletions test/conformance/conformance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ var skippedTestsForTraditionalRoutes = []string{
tests.HTTPRouteHostnameIntersection.ShortName,
tests.HTTPRouteListenerHostnameMatching.ShortName,

tests.HTTPRouteMatching.ShortName,
tests.HTTPRouteMatchingAcrossRoutes.ShortName,
// tests.HTTPRouteMatching.ShortName,
// tests.HTTPRouteMatchingAcrossRoutes.ShortName,

tests.GatewayInvalidTLSConfiguration.ShortName,
tests.HTTPRouteInvalidBackendRefUnknownKind.ShortName,
Expand Down
Loading