Skip to content

Commit 73b036f

Browse files
authored
Merge pull request #4321 from shuqz/shuqz-grpcroute
[feat gw-api]support multiple header value in condition
2 parents 7b76760 + 6151bc6 commit 73b036f

File tree

2 files changed

+99
-5
lines changed

2 files changed

+99
-5
lines changed

pkg/gateway/routeutils/route_rule_condition.go

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ package routeutils
22

33
import (
44
"fmt"
5+
"strings"
6+
57
"github.com/pkg/errors"
68
elbv2model "sigs.k8s.io/aws-load-balancer-controller/pkg/model/elbv2"
79
gwv1 "sigs.k8s.io/gateway-api/apis/v1"
8-
"strings"
910
)
1011

1112
// BuildHttpRuleConditions each match will be mapped to a ruleCondition, conditions within same match will be ANDed
@@ -98,8 +99,7 @@ func buildHttpHeaderCondition(headers []gwv1.HTTPHeaderMatch) []elbv2model.RuleC
9899
Field: elbv2model.RuleConditionFieldHTTPHeader,
99100
HTTPHeaderConfig: &elbv2model.HTTPHeaderConditionConfig{
100101
HTTPHeaderName: string(header.Name),
101-
// for a given HTTPHeaderName, ALB rule can accept a list of values. However, gateway route headers only accept one value per name, and name cannot duplicate.
102-
Values: []string{header.Value},
102+
Values: generateValuesFromMatchHeaderValue(header.Value),
103103
},
104104
},
105105
}
@@ -200,8 +200,7 @@ func buildGrpcHeaderCondition(headers []gwv1.GRPCHeaderMatch) []elbv2model.RuleC
200200
Field: elbv2model.RuleConditionFieldHTTPHeader,
201201
HTTPHeaderConfig: &elbv2model.HTTPHeaderConditionConfig{
202202
HTTPHeaderName: string(header.Name),
203-
// for a given HTTPHeaderName, ALB rule can accept a list of values. However, gateway route headers only accept one value per name, and name cannot duplicate.
204-
Values: []string{header.Value},
203+
Values: generateValuesFromMatchHeaderValue(header.Value),
205204
},
206205
},
207206
}
@@ -234,3 +233,29 @@ func buildGrpcMethodCondition(method *gwv1.GRPCMethodMatch) ([]elbv2model.RuleCo
234233
},
235234
}, nil
236235
}
236+
237+
// generateValuesFromMatchHeaderValue takes in header value from route match
238+
// returns list of values
239+
// for a given HTTPHeaderName/GRPCHeaderName, ALB rule can accept a list of values. However, gateway route headers only accept one value per name, and name cannot duplicate.
240+
func generateValuesFromMatchHeaderValue(headerValue string) []string {
241+
var values []string
242+
var current strings.Builder
243+
244+
for i := 0; i < len(headerValue); i++ {
245+
if headerValue[i] == '\\' && i+1 < len(headerValue) {
246+
// Escape sequence - add the escaped character literally
247+
current.WriteByte(headerValue[i+1])
248+
i++ // skip the escaped character
249+
} else if headerValue[i] == ',' {
250+
// Unescaped comma - split here
251+
values = append(values, current.String())
252+
current.Reset()
253+
} else {
254+
// Regular character
255+
current.WriteByte(headerValue[i])
256+
}
257+
}
258+
259+
values = append(values, current.String())
260+
return values
261+
}

pkg/gateway/routeutils/route_rule_condition_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package routeutils
22

33
import (
4+
"reflect"
45
"testing"
56

67
"github.com/stretchr/testify/assert"
@@ -696,3 +697,71 @@ func Test_buildGrpcMethodCondition(t *testing.T) {
696697
})
697698
}
698699
}
700+
701+
func TestGenerateValuesFromMatchHeaderValue(t *testing.T) {
702+
tests := []struct {
703+
name string
704+
input string
705+
expected []string
706+
}{
707+
{
708+
name: "simple comma separation",
709+
input: "a,b,c",
710+
expected: []string{"a", "b", "c"},
711+
},
712+
{
713+
name: "escaped comma",
714+
input: "a\\,b,c",
715+
expected: []string{"a,b", "c"},
716+
},
717+
{
718+
name: "escaped backslash",
719+
input: "a\\\\,b",
720+
expected: []string{"a\\", "b"},
721+
},
722+
{
723+
name: "multiple escaped commas",
724+
input: "a\\,b\\,c",
725+
expected: []string{"a,b,c"},
726+
},
727+
{
728+
name: "mixed escapes",
729+
input: "a\\\\,b\\,c,d",
730+
expected: []string{"a\\", "b,c", "d"},
731+
},
732+
{
733+
name: "no commas",
734+
input: "single-value",
735+
expected: []string{"single-value"},
736+
},
737+
{
738+
name: "empty string",
739+
input: "",
740+
expected: []string{""},
741+
},
742+
{
743+
name: "only commas",
744+
input: ",,",
745+
expected: []string{"", "", ""},
746+
},
747+
{
748+
name: "escaped other characters",
749+
input: "a\\n,b\\t",
750+
expected: []string{"an", "bt"},
751+
},
752+
{
753+
name: "backslash at end",
754+
input: "a\\",
755+
expected: []string{"a\\"},
756+
},
757+
}
758+
759+
for _, tt := range tests {
760+
t.Run(tt.name, func(t *testing.T) {
761+
result := generateValuesFromMatchHeaderValue(tt.input)
762+
if !reflect.DeepEqual(result, tt.expected) {
763+
t.Errorf("got %v, want %v", result, tt.expected)
764+
}
765+
})
766+
}
767+
}

0 commit comments

Comments
 (0)