cluster/router/condition/route.go (261 lines of code) (raw):

/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package condition import ( "regexp" "sort" "strings" "sync" ) import ( "github.com/dubbogo/gost/log/logger" "github.com/pkg/errors" "gopkg.in/yaml.v2" ) import ( "dubbo.apache.org/dubbo-go/v3/cluster/router/condition/matcher" "dubbo.apache.org/dubbo-go/v3/common" "dubbo.apache.org/dubbo-go/v3/common/constant" "dubbo.apache.org/dubbo-go/v3/config" "dubbo.apache.org/dubbo-go/v3/protocol" ) var ( routePattern = regexp.MustCompile("([&!=,]*)\\s*([^&!=,\\s]+)") illegalMsg = "Illegal route rule \"%s\", The error char '%s' before '%s'" matcherFactories = make([]matcher.ConditionMatcherFactory, 0, 8) once sync.Once ) type StateRouter struct { enable bool force bool url *common.URL whenCondition map[string]matcher.Matcher thenCondition map[string]matcher.Matcher } func NewConditionStateRouter(url *common.URL) (*StateRouter, error) { once.Do(initMatcherFactories) if len(matcherFactories) == 0 { return nil, errors.Errorf("No ConditionMatcherFactory exists") } force := url.GetParamBool(constant.ForceKey, false) enable := url.GetParamBool(constant.EnabledKey, true) c := &StateRouter{ url: url, force: force, enable: enable, } if enable { when, then, err := generateMatcher(url) if err != nil { return nil, err } c.whenCondition = when c.thenCondition = then } return c, nil } // Route Determine the target invokers list. func (s *StateRouter) Route(invokers []protocol.Invoker, url *common.URL, invocation protocol.Invocation) []protocol.Invoker { if !s.enable { return invokers } if len(invokers) == 0 { return invokers } if !s.matchWhen(url, invocation) { return invokers } if len(s.thenCondition) == 0 { logger.Warn("condition state router thenCondition is empty") return []protocol.Invoker{} } var result = make([]protocol.Invoker, 0, len(invokers)) for _, invoker := range invokers { if s.matchThen(invoker.GetURL(), url) { result = append(result, invoker) } } if len(result) != 0 { return result } else if s.force { logger.Warn("execute condition state router result list is empty. and force=true") return result } return invokers } func (s *StateRouter) URL() *common.URL { return s.url } func (s *StateRouter) Priority() int64 { return 0 } func (s *StateRouter) matchWhen(url *common.URL, invocation protocol.Invocation) bool { if len(s.whenCondition) == 0 { return true } return doMatch(url, nil, invocation, s.whenCondition, true) } func (s *StateRouter) matchThen(url *common.URL, param *common.URL) bool { if len(s.thenCondition) == 0 { return false } return doMatch(url, param, nil, s.thenCondition, false) } func generateMatcher(url *common.URL) (when, then map[string]matcher.Matcher, err error) { rule := url.GetParam(constant.RuleKey, "") if rule == "" || len(strings.Trim(rule, " ")) == 0 { return nil, nil, errors.Errorf("Illegal route rule!") } rule = strings.Replace(rule, "consumer.", "", -1) rule = strings.Replace(rule, "provider.", "", -1) i := strings.Index(rule, "=>") // for the case of `{when rule} => {then rule}` var whenRule string var thenRule string if i < 0 { whenRule = "" thenRule = strings.Trim(rule, " ") } else { whenRule = strings.Trim(rule[0:i], " ") thenRule = strings.Trim(rule[i+2:], " ") } when, err = parseWhen(whenRule) if err != nil { return nil, nil, err } then, err = parseThen(thenRule) if err != nil { return nil, nil, err } // NOTE: It should be determined on the business level whether the `When condition` can be empty or not. return when, then, nil } func parseWhen(whenRule string) (map[string]matcher.Matcher, error) { if whenRule == "" || whenRule == " " || whenRule == "true" { return make(map[string]matcher.Matcher), nil } else { when, err := parseRule(whenRule) if err != nil { return nil, err } return when, nil } } func parseThen(thenRule string) (map[string]matcher.Matcher, error) { if thenRule == "" || thenRule == " " || thenRule == "false" { return nil, nil } else { when, err := parseRule(thenRule) if err != nil { return nil, err } return when, nil } } func parseRule(rule string) (map[string]matcher.Matcher, error) { if isRuleEmpty(rule) { return make(map[string]matcher.Matcher), nil } condition, err := processMatchers(rule) if err != nil { return nil, err } return condition, nil } func isRuleEmpty(rule string) bool { return rule == "" || (len(rule) == 1 && rule[0] == ' ') } func processMatchers(rule string) (map[string]matcher.Matcher, error) { condition := make(map[string]matcher.Matcher) var currentMatcher matcher.Matcher var err error values := make(map[string]struct{}) for _, matchers := range routePattern.FindAllStringSubmatch(rule, -1) { separator := matchers[1] content := matchers[2] switch separator { case "": currentMatcher = getMatcher(content) condition[content] = currentMatcher case "&": currentMatcher, condition = processAndSeparator(content, condition) case "=", "!=": values, currentMatcher, err = processEqualNotEqualSeparator(separator, content, currentMatcher, rule) if err != nil { return nil, err } case ",": values, err = processCommaSeparator(content, values, rule) if err != nil { return nil, err } default: return nil, errors.Errorf(illegalMsg, rule, separator, content) } } return condition, nil } func processAndSeparator(content string, condition map[string]matcher.Matcher) (matcher.Matcher, map[string]matcher.Matcher) { currentMatcher := condition[content] if currentMatcher == nil { currentMatcher = getMatcher(content) condition[content] = currentMatcher } return currentMatcher, condition } func processEqualNotEqualSeparator(separator, content string, currentMatcher matcher.Matcher, rule string) (map[string]struct{}, matcher.Matcher, error) { if currentMatcher == nil { return nil, nil, errors.Errorf(illegalMsg, rule, separator, content) } values := currentMatcher.GetMatches() if separator == "!=" { values = currentMatcher.GetMismatches() } values[content] = struct{}{} return values, currentMatcher, nil } func processCommaSeparator(content string, values map[string]struct{}, rule string) (map[string]struct{}, error) { if len(values) == 0 { return nil, errors.Errorf(illegalMsg, rule, ",", content) } values[content] = struct{}{} return values, nil } func getMatcher(key string) matcher.Matcher { for _, factory := range matcherFactories { if factory.ShouldMatch(key) { return factory.NewMatcher(key) } } return matcher.GetMatcherFactory(constant.Param).NewMatcher(key) } func doMatch(url *common.URL, param *common.URL, invocation protocol.Invocation, conditions map[string]matcher.Matcher, isWhenCondition bool) bool { sample := url.ToMap() for _, matcherPair := range conditions { if !matcher.Match(matcherPair, sample, param, invocation, isWhenCondition) { return false } } return true } func initMatcherFactories() { factoriesMap := matcher.GetMatcherFactories() if len(factoriesMap) == 0 { return } for _, factory := range factoriesMap { matcherFactories = append(matcherFactories, factory()) } sortMatcherFactories(matcherFactories) } func sortMatcherFactories(matcherFactories []matcher.ConditionMatcherFactory) { sort.Stable(byPriority(matcherFactories)) } type byPriority []matcher.ConditionMatcherFactory func (a byPriority) Len() int { return len(a) } func (a byPriority) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a byPriority) Less(i, j int) bool { return a[i].Priority() < a[j].Priority() } func parseRoute(routeContent string) (*config.RouterConfig, error) { routeDecoder := yaml.NewDecoder(strings.NewReader(routeContent)) routerConfig := &config.RouterConfig{} err := routeDecoder.Decode(routerConfig) if err != nil { return nil, err } return routerConfig, nil }