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  }