mirror of https://github.com/grpc/grpc-go.git
xds/internal/xdsclient: Add support for String Matcher Header Matcher in RDS (#6313)
This commit is contained in:
parent
157db1907e
commit
4d3f221d1d
|
@ -241,3 +241,34 @@ func (hcm *HeaderContainsMatcher) Match(md metadata.MD) bool {
|
|||
func (hcm *HeaderContainsMatcher) String() string {
|
||||
return fmt.Sprintf("headerContains:%v%v", hcm.key, hcm.contains)
|
||||
}
|
||||
|
||||
// HeaderStringMatcher matches on whether the header value matches against the
|
||||
// StringMatcher specified.
|
||||
type HeaderStringMatcher struct {
|
||||
key string
|
||||
stringMatcher StringMatcher
|
||||
invert bool
|
||||
}
|
||||
|
||||
// NewHeaderStringMatcher returns a new HeaderStringMatcher.
|
||||
func NewHeaderStringMatcher(key string, sm StringMatcher, invert bool) *HeaderStringMatcher {
|
||||
return &HeaderStringMatcher{
|
||||
key: key,
|
||||
stringMatcher: sm,
|
||||
invert: invert,
|
||||
}
|
||||
}
|
||||
|
||||
// Match returns whether the passed in HTTP Headers match according to the
|
||||
// specified StringMatcher.
|
||||
func (hsm *HeaderStringMatcher) Match(md metadata.MD) bool {
|
||||
v, ok := mdValuesFromOutgoingCtx(md, hsm.key)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return hsm.stringMatcher.Match(v) != hsm.invert
|
||||
}
|
||||
|
||||
func (hsm *HeaderStringMatcher) String() string {
|
||||
return fmt.Sprintf("headerString:%v:%v", hsm.key, hsm.stringMatcher)
|
||||
}
|
||||
|
|
|
@ -467,3 +467,83 @@ func TestHeaderSuffixMatcherMatch(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHeaderStringMatch(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
key string
|
||||
sm StringMatcher
|
||||
invert bool
|
||||
md metadata.MD
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "should-match",
|
||||
key: "th",
|
||||
sm: StringMatcher{
|
||||
exactMatch: newStringP("tv"),
|
||||
},
|
||||
invert: false,
|
||||
md: metadata.Pairs("th", "tv"),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "not match",
|
||||
key: "th",
|
||||
sm: StringMatcher{
|
||||
containsMatch: newStringP("tv"),
|
||||
},
|
||||
invert: false,
|
||||
md: metadata.Pairs("th", "not-match"),
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "invert string match",
|
||||
key: "th",
|
||||
sm: StringMatcher{
|
||||
containsMatch: newStringP("tv"),
|
||||
},
|
||||
invert: true,
|
||||
md: metadata.Pairs("th", "not-match"),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "header missing",
|
||||
key: "th",
|
||||
sm: StringMatcher{
|
||||
containsMatch: newStringP("tv"),
|
||||
},
|
||||
invert: false,
|
||||
md: metadata.Pairs("not-specified-key", "not-match"),
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "header missing invert true",
|
||||
key: "th",
|
||||
sm: StringMatcher{
|
||||
containsMatch: newStringP("tv"),
|
||||
},
|
||||
invert: true,
|
||||
md: metadata.Pairs("not-specified-key", "not-match"),
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "header empty string invert",
|
||||
key: "th",
|
||||
sm: StringMatcher{
|
||||
containsMatch: newStringP("tv"),
|
||||
},
|
||||
invert: true,
|
||||
md: metadata.Pairs("th", ""),
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
hsm := NewHeaderStringMatcher(test.key, test.sm, test.invert)
|
||||
if got := hsm.Match(test.md); got != test.want {
|
||||
t.Errorf("match() = %v, want %v", got, test.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,6 +59,8 @@ func RouteToMatcher(r *Route) (*CompositeMatcher, error) {
|
|||
matcherT = matcher.NewHeaderRangeMatcher(h.Name, h.RangeMatch.Start, h.RangeMatch.End, invert)
|
||||
case h.PresentMatch != nil:
|
||||
matcherT = matcher.NewHeaderPresentMatcher(h.Name, *h.PresentMatch, invert)
|
||||
case h.StringMatch != nil:
|
||||
matcherT = matcher.NewHeaderStringMatcher(h.Name, *h.StringMatch, invert)
|
||||
default:
|
||||
return nil, fmt.Errorf("illegal route: missing header_match_specifier")
|
||||
}
|
||||
|
|
|
@ -171,6 +171,7 @@ type HeaderMatcher struct {
|
|||
SuffixMatch *string
|
||||
RangeMatch *Int64Range
|
||||
PresentMatch *bool
|
||||
StringMatch *matcher.StringMatcher
|
||||
}
|
||||
|
||||
// Int64Range is a range for header range match.
|
||||
|
|
|
@ -24,13 +24,15 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
|
||||
v3typepb "github.com/envoyproxy/go-control-plane/envoy/type/v3"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/internal/envconfig"
|
||||
"google.golang.org/grpc/internal/xds/matcher"
|
||||
"google.golang.org/grpc/xds/internal/clusterspecifier"
|
||||
"google.golang.org/protobuf/types/known/anypb"
|
||||
|
||||
v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
|
||||
v3typepb "github.com/envoyproxy/go-control-plane/envoy/type/v3"
|
||||
)
|
||||
|
||||
func unmarshalRouteConfigResource(r *anypb.Any) (string, RouteConfigUpdate, error) {
|
||||
|
@ -273,6 +275,12 @@ func routesProtoToSlice(routes []*v3routepb.Route, csps map[string]clusterspecif
|
|||
header.PrefixMatch = &ht.PrefixMatch
|
||||
case *v3routepb.HeaderMatcher_SuffixMatch:
|
||||
header.SuffixMatch = &ht.SuffixMatch
|
||||
case *v3routepb.HeaderMatcher_StringMatch:
|
||||
sm, err := matcher.StringMatcherFromProto(ht.StringMatch)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("route %+v has an invalid string matcher: %v", err, ht.StringMatch)
|
||||
}
|
||||
header.StringMatch = &sm
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("route %+v has an unrecognized header matcher: %+v", r, ht)
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ import (
|
|||
"google.golang.org/grpc/internal/envconfig"
|
||||
"google.golang.org/grpc/internal/pretty"
|
||||
"google.golang.org/grpc/internal/testutils"
|
||||
"google.golang.org/grpc/internal/xds/matcher"
|
||||
"google.golang.org/grpc/xds/internal/clusterspecifier"
|
||||
"google.golang.org/grpc/xds/internal/httpfilter"
|
||||
"google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version"
|
||||
|
@ -923,6 +924,7 @@ func (s) TestUnmarshalRouteConfig(t *testing.T) {
|
|||
}
|
||||
|
||||
func (s) TestRoutesProtoToSlice(t *testing.T) {
|
||||
sm, _ := matcher.StringMatcherFromProto(&v3matcherpb.StringMatcher{MatchPattern: &v3matcherpb.StringMatcher_Exact{Exact: "tv"}})
|
||||
var (
|
||||
goodRouteWithFilterConfigs = func(cfgs map[string]*anypb.Any) []*v3routepb.Route {
|
||||
// Sets per-filter config in cluster "B" and in the route.
|
||||
|
@ -1085,6 +1087,51 @@ func (s) TestRoutesProtoToSlice(t *testing.T) {
|
|||
}},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "good with string matcher",
|
||||
routes: []*v3routepb.Route{
|
||||
{
|
||||
Match: &v3routepb.RouteMatch{
|
||||
PathSpecifier: &v3routepb.RouteMatch_SafeRegex{SafeRegex: &v3matcherpb.RegexMatcher{Regex: "/a/"}},
|
||||
Headers: []*v3routepb.HeaderMatcher{
|
||||
{
|
||||
Name: "th",
|
||||
HeaderMatchSpecifier: &v3routepb.HeaderMatcher_StringMatch{StringMatch: &v3matcherpb.StringMatcher{MatchPattern: &v3matcherpb.StringMatcher_Exact{Exact: "tv"}}},
|
||||
},
|
||||
},
|
||||
RuntimeFraction: &v3corepb.RuntimeFractionalPercent{
|
||||
DefaultValue: &v3typepb.FractionalPercent{
|
||||
Numerator: 1,
|
||||
Denominator: v3typepb.FractionalPercent_HUNDRED,
|
||||
},
|
||||
},
|
||||
},
|
||||
Action: &v3routepb.Route_Route{
|
||||
Route: &v3routepb.RouteAction{
|
||||
ClusterSpecifier: &v3routepb.RouteAction_WeightedClusters{
|
||||
WeightedClusters: &v3routepb.WeightedCluster{
|
||||
Clusters: []*v3routepb.WeightedCluster_ClusterWeight{
|
||||
{Name: "B", Weight: &wrapperspb.UInt32Value{Value: 60}},
|
||||
{Name: "A", Weight: &wrapperspb.UInt32Value{Value: 40}},
|
||||
},
|
||||
}}}},
|
||||
},
|
||||
},
|
||||
wantRoutes: []*Route{{
|
||||
Regex: func() *regexp.Regexp { return regexp.MustCompile("/a/") }(),
|
||||
Headers: []*HeaderMatcher{
|
||||
{
|
||||
Name: "th",
|
||||
InvertMatch: newBoolP(false),
|
||||
StringMatch: &sm,
|
||||
},
|
||||
},
|
||||
Fraction: newUInt32P(10000),
|
||||
WeightedClusters: map[string]WeightedCluster{"A": {Weight: 40}, "B": {Weight: 60}},
|
||||
ActionType: RouteActionRoute,
|
||||
}},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "query is ignored",
|
||||
routes: []*v3routepb.Route{
|
||||
|
|
Loading…
Reference in New Issue