Skip to content

Commit

Permalink
Gateway API: only keep first match for a given header name (#4593)
Browse files Browse the repository at this point in the history
Adds logic to only keep the first HTTP header match
with a given name (case-insensitive) for each HTTP
route rule, per the Gateway API spec.

Closes #4591.

Signed-off-by: Steve Kriss <krisss@vmware.com>
  • Loading branch information
skriss authored Jun 23, 2022
1 parent 817f857 commit 3550f75
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 14 deletions.
1 change: 1 addition & 0 deletions changelogs/unreleased/4593-skriss-small.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Gateway API: adds logic to only keep the first HTTP header match with a given name (case-insensitive) for each HTTP route match, per the Gateway API spec.
48 changes: 48 additions & 0 deletions internal/dag/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2640,6 +2640,54 @@ func TestDAGInsertGatewayAPI(t *testing.T) {
},
),
},
"insert route with multiple header matches including multiple for the same key": {
gatewayclass: validClass,
gateway: gatewayHTTPAllNamespaces,
objs: []interface{}{
kuardService,
&gatewayapi_v1alpha2.HTTPRoute{
ObjectMeta: metav1.ObjectMeta{
Name: "basic",
Namespace: "projectcontour",
},
Spec: gatewayapi_v1alpha2.HTTPRouteSpec{
CommonRouteSpec: gatewayapi_v1alpha2.CommonRouteSpec{
ParentRefs: []gatewayapi_v1alpha2.ParentReference{gatewayapi.GatewayParentRef("projectcontour", "contour")},
},
Hostnames: []gatewayapi_v1alpha2.Hostname{
"test.projectcontour.io",
},
Rules: []gatewayapi_v1alpha2.HTTPRouteRule{{
Matches: []gatewayapi_v1alpha2.HTTPRouteMatch{{
Headers: []gatewayapi_v1alpha2.HTTPHeaderMatch{
{Name: "header-1", Value: "value-1"},
{Name: "header-2", Value: "value-2"},
{Name: "header-1", Value: "value-3"},
{Name: "HEADER-1", Value: "value-4"},
},
}},
BackendRefs: gatewayapi.HTTPBackendRef("kuard", 8080, 1),
}},
},
},
},
want: listeners(
&Listener{
Name: HTTP_LISTENER_NAME,
Port: 80,
VirtualHosts: virtualhosts(virtualhost("test.projectcontour.io",
&Route{
PathMatchCondition: prefixString("/"),
HeaderMatchConditions: []HeaderMatchCondition{
{Name: "header-1", Value: "value-1", MatchType: "exact"},
{Name: "header-2", Value: "value-2", MatchType: "exact"},
},
Clusters: clustersWeight(service(kuardService)),
},
)),
},
),
},
"route with HTTP method match": {
gatewayclass: validClass,
gateway: gatewayHTTPAllNamespaces,
Expand Down
35 changes: 21 additions & 14 deletions internal/dag/gatewayapi_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -1237,20 +1237,28 @@ func gatewayPathMatchCondition(match *gatewayapi_v1alpha2.HTTPPathMatch, routeAc

func gatewayHeaderMatchConditions(matches []gatewayapi_v1alpha2.HTTPHeaderMatch) ([]HeaderMatchCondition, error) {
var headerMatchConditions []HeaderMatchCondition
seenNames := sets.String{}

for _, match := range matches {
// HeaderMatchTypeExact is the default if not defined in the object.
headerMatchType := HeaderMatchTypeExact
if match.Type != nil {
switch *match.Type {
case gatewayapi_v1alpha2.HeaderMatchExact:
headerMatchType = HeaderMatchTypeExact
default:
return nil, fmt.Errorf("HTTPRoute.Spec.Rules.Matches.Headers: Only Exact match type is supported")
}
// "Exact" is the default if not defined in the object, and
// the only supported match type.
if match.Type != nil && *match.Type != gatewayapi_v1alpha2.HeaderMatchExact {
return nil, fmt.Errorf("HTTPRoute.Spec.Rules.Matches.Headers: Only Exact match type is supported")
}

// If multiple match conditions are found for the same header name (case-insensitive),
// use the first one and ignore subsequent ones.
upperName := strings.ToUpper(string(match.Name))
if seenNames.Has(upperName) {
continue
}
seenNames.Insert(upperName)

headerMatchConditions = append(headerMatchConditions, HeaderMatchCondition{MatchType: headerMatchType, Name: string(match.Name), Value: match.Value})
headerMatchConditions = append(headerMatchConditions, HeaderMatchCondition{
MatchType: HeaderMatchTypeExact,
Name: string(match.Name),
Value: match.Value,
})
}

return headerMatchConditions, nil
Expand All @@ -1261,9 +1269,8 @@ func gatewayQueryParamMatchConditions(matches []gatewayapi_v1alpha2.HTTPQueryPar
seenNames := sets.String{}

for _, match := range matches {
// QueryParamMatchTypeExact is the default if not defined in the object.
queryParamMatchType := QueryParamMatchTypeExact

// "Exact" is the default if not defined in the object, and
// the only supported match type.
if match.Type != nil && *match.Type != gatewayapi_v1alpha2.QueryParamMatchExact {
return nil, fmt.Errorf("HTTPRoute.Spec.Rules.Matches.QueryParams: Only Exact match type is supported")
}
Expand All @@ -1276,7 +1283,7 @@ func gatewayQueryParamMatchConditions(matches []gatewayapi_v1alpha2.HTTPQueryPar
seenNames.Insert(match.Name)

dagMatchConditions = append(dagMatchConditions, QueryParamMatchCondition{
MatchType: queryParamMatchType,
MatchType: QueryParamMatchTypeExact,
Name: match.Name,
Value: match.Value,
})
Expand Down

0 comments on commit 3550f75

Please sign in to comment.