dubbo.apache.org/dubbo-go/v3@v3.1.1/xds/client/resource/matcher.go (about) 1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 /* 19 * 20 * Copyright 2021 gRPC authors. 21 * 22 */ 23 24 package resource 25 26 import ( 27 "fmt" 28 "strings" 29 ) 30 31 import ( 32 "google.golang.org/grpc/metadata" 33 ) 34 35 import ( 36 "dubbo.apache.org/dubbo-go/v3/xds/utils/grpcrand" 37 "dubbo.apache.org/dubbo-go/v3/xds/utils/matcher" 38 metedatautils "dubbo.apache.org/dubbo-go/v3/xds/utils/metadata" 39 iresolver "dubbo.apache.org/dubbo-go/v3/xds/utils/resolver" 40 ) 41 42 // RouteToMatcher converts a route to a Matcher to match incoming RPC's against. 43 func RouteToMatcher(r *Route) (*CompositeMatcher, error) { 44 var pm pathMatcher 45 switch { 46 case r.Regex != nil: 47 pm = newPathRegexMatcher(r.Regex) 48 case r.Path != nil: 49 pm = newPathExactMatcher(*r.Path, r.CaseInsensitive) 50 case r.Prefix != nil: 51 pm = newPathPrefixMatcher(*r.Prefix, r.CaseInsensitive) 52 default: 53 return nil, fmt.Errorf("illegal route: missing path_matcher") 54 } 55 56 headerMatchers := make([]matcher.HeaderMatcher, 0, len(r.Headers)) 57 for _, h := range r.Headers { 58 var matcherT matcher.HeaderMatcher 59 invert := h.InvertMatch != nil && *h.InvertMatch 60 switch { 61 case h.ExactMatch != nil && *h.ExactMatch != "": 62 matcherT = matcher.NewHeaderExactMatcher(h.Name, *h.ExactMatch, invert) 63 case h.RegexMatch != nil: 64 matcherT = matcher.NewHeaderRegexMatcher(h.Name, h.RegexMatch, invert) 65 case h.PrefixMatch != nil && *h.PrefixMatch != "": 66 matcherT = matcher.NewHeaderPrefixMatcher(h.Name, *h.PrefixMatch, invert) 67 case h.SuffixMatch != nil && *h.SuffixMatch != "": 68 matcherT = matcher.NewHeaderSuffixMatcher(h.Name, *h.SuffixMatch, invert) 69 case h.RangeMatch != nil: 70 matcherT = matcher.NewHeaderRangeMatcher(h.Name, h.RangeMatch.Start, h.RangeMatch.End, invert) 71 case h.PresentMatch != nil: 72 matcherT = matcher.NewHeaderPresentMatcher(h.Name, *h.PresentMatch, invert) 73 default: 74 return nil, fmt.Errorf("illegal route: missing header_match_specifier") 75 } 76 headerMatchers = append(headerMatchers, matcherT) 77 } 78 79 var fractionMatcher *fractionMatcher 80 if r.Fraction != nil { 81 fractionMatcher = newFractionMatcher(*r.Fraction) 82 } 83 return newCompositeMatcher(pm, headerMatchers, fractionMatcher), nil 84 } 85 86 // CompositeMatcher is a matcher that holds onto many matchers and aggregates 87 // the matching results. 88 type CompositeMatcher struct { 89 pm pathMatcher 90 hms []matcher.HeaderMatcher 91 fm *fractionMatcher 92 } 93 94 func newCompositeMatcher(pm pathMatcher, hms []matcher.HeaderMatcher, fm *fractionMatcher) *CompositeMatcher { 95 return &CompositeMatcher{pm: pm, hms: hms, fm: fm} 96 } 97 98 // Match returns true if all matchers return true. 99 func (a *CompositeMatcher) Match(info iresolver.RPCInfo) bool { 100 if a.pm != nil && !a.pm.match(info.Method) { 101 return false 102 } 103 104 // Call headerMatchers even if md is nil, because routes may match 105 // non-presence of some headers. 106 var md metadata.MD 107 if info.Context != nil { 108 md, _ = metadata.FromOutgoingContext(info.Context) 109 if extraMD, ok := metedatautils.ExtraMetadata(info.Context); ok { 110 md = metadata.Join(md, extraMD) 111 // Remove all binary headers. They are hard to match with. May need 112 // to add back if asked by users. 113 for k := range md { 114 if strings.HasSuffix(k, "-bin") { 115 delete(md, k) 116 } 117 } 118 } 119 } 120 for _, m := range a.hms { 121 if !m.Match(md) { 122 return false 123 } 124 } 125 126 if a.fm != nil && !a.fm.match() { 127 return false 128 } 129 return true 130 } 131 132 func (a *CompositeMatcher) String() string { 133 var ret string 134 if a.pm != nil { 135 ret += a.pm.String() 136 } 137 for _, m := range a.hms { 138 ret += m.String() 139 } 140 if a.fm != nil { 141 ret += a.fm.String() 142 } 143 return ret 144 } 145 146 type fractionMatcher struct { 147 fraction int64 // real fraction is fraction/1,000,000. 148 } 149 150 func newFractionMatcher(fraction uint32) *fractionMatcher { 151 return &fractionMatcher{fraction: int64(fraction)} 152 } 153 154 // RandInt63n overwrites grpcrand for control in tests. 155 var RandInt63n = grpcrand.Int63n 156 157 func (fm *fractionMatcher) match() bool { 158 t := RandInt63n(1000000) 159 return t <= fm.fraction 160 } 161 162 func (fm *fractionMatcher) String() string { 163 return fmt.Sprintf("fraction:%v", fm.fraction) 164 } 165 166 type domainMatchType int 167 168 const ( 169 domainMatchTypeInvalid domainMatchType = iota 170 domainMatchTypeUniversal 171 domainMatchTypePrefix 172 domainMatchTypeSuffix 173 domainMatchTypeExact 174 ) 175 176 // Exact > Suffix > Prefix > Universal > Invalid. 177 func (t domainMatchType) betterThan(b domainMatchType) bool { 178 return t > b 179 } 180 181 func matchTypeForDomain(d string) domainMatchType { 182 if d == "" { 183 return domainMatchTypeInvalid 184 } 185 if d == "*" { 186 return domainMatchTypeUniversal 187 } 188 if strings.HasPrefix(d, "*") { 189 return domainMatchTypeSuffix 190 } 191 if strings.HasSuffix(d, "*") { 192 return domainMatchTypePrefix 193 } 194 if strings.Contains(d, "*") { 195 return domainMatchTypeInvalid 196 } 197 return domainMatchTypeExact 198 } 199 200 func match(domain, host string) (domainMatchType, bool) { 201 switch typ := matchTypeForDomain(domain); typ { 202 case domainMatchTypeInvalid: 203 return typ, false 204 case domainMatchTypeUniversal: 205 return typ, true 206 case domainMatchTypePrefix: 207 // abc.* 208 return typ, strings.HasPrefix(host, strings.TrimSuffix(domain, "*")) 209 case domainMatchTypeSuffix: 210 // *.123 211 return typ, strings.HasSuffix(host, strings.TrimPrefix(domain, "*")) 212 case domainMatchTypeExact: 213 return typ, domain == host 214 default: 215 return domainMatchTypeInvalid, false 216 } 217 } 218 219 // FindBestMatchingVirtualHost returns the virtual host whose domains field best 220 // matches host 221 // 222 // The domains field support 4 different matching pattern types: 223 // - Exact match 224 // - Suffix match (e.g. “*ABC”) 225 // - Prefix match (e.g. “ABC*) 226 // - Universal match (e.g. “*”) 227 // 228 // The best match is defined as: 229 // - A match is better if it’s matching pattern type is better 230 // - Exact match > suffix match > prefix match > universal match 231 // - If two matches are of the same pattern type, the longer match is better 232 // - This is to compare the length of the matching pattern, e.g. “*ABCDE” > 233 // “*ABC” 234 func FindBestMatchingVirtualHost(host string, vHosts []*VirtualHost) *VirtualHost { // Maybe move this crap to client 235 var ( 236 matchVh *VirtualHost 237 matchType = domainMatchTypeInvalid 238 matchLen int 239 ) 240 for _, vh := range vHosts { 241 for _, domain := range vh.Domains { 242 typ, matched := match(domain, host) 243 if typ == domainMatchTypeInvalid { 244 // The rds response is invalid. 245 return nil 246 } 247 if matchType.betterThan(typ) || matchType == typ && matchLen >= len(domain) || !matched { 248 // The previous match has better type, or the previous match has 249 // better length, or this domain isn't a match. 250 continue 251 } 252 matchVh = vh 253 matchType = typ 254 matchLen = len(domain) 255 } 256 } 257 return matchVh 258 } 259 260 // FindBestMatchingVirtualHostServer returns the virtual host whose domains field best 261 // matches authority. 262 func FindBestMatchingVirtualHostServer(authority string, vHosts []VirtualHostWithInterceptors) *VirtualHostWithInterceptors { 263 var ( 264 matchVh *VirtualHostWithInterceptors 265 matchType = domainMatchTypeInvalid 266 matchLen int 267 ) 268 for _, vh := range vHosts { 269 for _, domain := range vh.Domains { 270 typ, matched := match(domain, authority) 271 if typ == domainMatchTypeInvalid { 272 // The rds response is invalid. 273 return nil 274 } 275 if matchType.betterThan(typ) || matchType == typ && matchLen >= len(domain) || !matched { 276 // The previous match has better type, or the previous match has 277 // better length, or this domain isn't a match. 278 continue 279 } 280 matchVh = &vh 281 matchType = typ 282 matchLen = len(domain) 283 } 284 } 285 return matchVh 286 }