Skip to content
Snippets Groups Projects
router.go 9.1 KiB
Newer Older
/*
 * 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.
 */

邹毅贤's avatar
邹毅贤 committed
package condition
aliiohs's avatar
aliiohs committed

import (
	"regexp"
	"strings"
aliiohs's avatar
aliiohs committed
)
aliiohs's avatar
aliiohs committed
import (
Ian Luo's avatar
Ian Luo committed
	"github.com/dubbogo/gost/container/set"
aliiohs's avatar
aliiohs committed
	perrors "github.com/pkg/errors"
)

aliiohs's avatar
aliiohs committed
import (
	"github.com/apache/dubbo-go/cluster/router"
Ian Luo's avatar
Ian Luo committed
	"github.com/apache/dubbo-go/cluster/router/utils"
aliiohs's avatar
aliiohs committed
	"github.com/apache/dubbo-go/common"
	"github.com/apache/dubbo-go/common/constant"
aliiohs's avatar
aliiohs committed
	"github.com/apache/dubbo-go/common/logger"
aliiohs's avatar
aliiohs committed
	"github.com/apache/dubbo-go/protocol"
aliiohs's avatar
aliiohs committed
)
aliiohs's avatar
aliiohs committed

aliiohs's avatar
aliiohs committed
const (
Ian Luo's avatar
Ian Luo committed
	// pattern route pattern regex
邹毅贤's avatar
邹毅贤 committed
	pattern = `([&!=,]*)\\s*([^&!=,\\s]+)`
邹毅贤's avatar
邹毅贤 committed
var (
邹毅贤's avatar
邹毅贤 committed
	routerPatternReg = regexp.MustCompile(`([&!=,]*)\s*([^&!=,\s]+)`)
邹毅贤's avatar
邹毅贤 committed
)

邹毅贤's avatar
邹毅贤 committed
// ConditionRouter Condition router struct
aliiohs's avatar
aliiohs committed
type ConditionRouter struct {
	Pattern       string
aliiohs's avatar
aliiohs committed
	url           *common.URL
	priority      int64
aliiohs's avatar
aliiohs committed
	Force         bool
邹毅贤's avatar
邹毅贤 committed
	enabled       bool
aliiohs's avatar
aliiohs committed
	WhenCondition map[string]MatchPair
	ThenCondition map[string]MatchPair
}

邹毅贤's avatar
邹毅贤 committed
// NewConditionRouterWithRule Init condition router by raw rule
邹毅贤's avatar
邹毅贤 committed
func NewConditionRouterWithRule(rule string) (*ConditionRouter, error) {
aliiohs's avatar
aliiohs committed
	var (
		whenRule string
		thenRule string
aliiohs's avatar
aliiohs committed
		when     map[string]MatchPair
		then     map[string]MatchPair
aliiohs's avatar
aliiohs committed
	)
	rule = strings.Replace(rule, "consumer.", "", -1)
	rule = strings.Replace(rule, "provider.", "", -1)
	i := strings.Index(rule, "=>")
aliiohs's avatar
aliiohs committed
	if i > 0 {
		whenRule = rule[0:i]
	}
	if i < 0 {
		thenRule = rule
	} else {
		thenRule = rule[i+2:]
	}
	whenRule = strings.Trim(whenRule, " ")
	thenRule = strings.Trim(thenRule, " ")
aliiohs's avatar
aliiohs committed
	w, err := parseRule(whenRule)
	if err != nil {
		return nil, perrors.Errorf("%s", "")
	}
	t, err := parseRule(thenRule)
	if err != nil {
		return nil, perrors.Errorf("%s", "")
	}
aliiohs's avatar
aliiohs committed
	if len(whenRule) == 0 || "true" == whenRule {
		when = make(map[string]MatchPair, 16)
aliiohs's avatar
aliiohs committed
	} else {
		when = w
	}
aliiohs's avatar
aliiohs committed
	if len(thenRule) == 0 || "false" == thenRule {
		when = make(map[string]MatchPair, 16)
aliiohs's avatar
aliiohs committed
	} else {
		then = t
	}
aliiohs's avatar
aliiohs committed
	return &ConditionRouter{
邹毅贤's avatar
邹毅贤 committed
		Pattern:       pattern,
邹毅贤's avatar
邹毅贤 committed
		WhenCondition: when,
		ThenCondition: then,
邹毅贤's avatar
邹毅贤 committed
// NewConditionRouter Init condition router by URL
邹毅贤's avatar
邹毅贤 committed
func NewConditionRouter(url *common.URL) (*ConditionRouter, error) {
邹毅贤's avatar
邹毅贤 committed
	if url == nil {
		return nil, perrors.Errorf("Illegal route URL!")
	}
邹毅贤's avatar
邹毅贤 committed
	rule, err := url.GetParamAndDecoded(constant.RULE_KEY)
	if err != nil || len(rule) == 0 {
		return nil, perrors.Errorf("Illegal route rule!")
	}

	router, err := NewConditionRouterWithRule(rule)
	if err != nil {
		return nil, err
	}

	router.url = url
	var defaultPriority int64 = 0
	if url.GetParam(constant.APPLICATION_KEY, "") != "" {
		defaultPriority = 150
	} else if url.GetParam(constant.INTERFACE_KEY, "") != "" {
		defaultPriority = 140
	}
	router.priority = url.GetParamInt(constant.RouterPriority, defaultPriority)
邹毅贤's avatar
邹毅贤 committed
	router.Force = url.GetParamBool(constant.RouterForce, false)
	router.enabled = url.GetParamBool(constant.RouterEnabled, true)
邹毅贤's avatar
邹毅贤 committed

	return router, nil
}

邹毅贤's avatar
邹毅贤 committed
// Priority Return Priority in condition router
邹毅贤's avatar
邹毅贤 committed
func (c *ConditionRouter) Priority() int64 {
aliiohs's avatar
aliiohs committed
	return c.priority
}

邹毅贤's avatar
邹毅贤 committed
// URL Return URL in condition router
haohongfan's avatar
haohongfan committed
func (c *ConditionRouter) URL() *common.URL {
	return c.url
邹毅贤's avatar
邹毅贤 committed
// Enabled Return is condition router is enabled
// true: enabled
// false: disabled
邹毅贤's avatar
邹毅贤 committed
func (c *ConditionRouter) Enabled() bool {
	return c.enabled
}

邹毅贤's avatar
邹毅贤 committed
// Route Determine the target invokers list.
Ian Luo's avatar
Ian Luo committed
func (c *ConditionRouter) Route(invokers *roaring.Bitmap, cache router.Cache, url *common.URL, invocation protocol.Invocation) *roaring.Bitmap {
邹毅贤's avatar
邹毅贤 committed
	if !c.Enabled() {
		return invokers
	}

	if invokers.IsEmpty() {
		return invokers
	}
邹毅贤's avatar
邹毅贤 committed
	isMatchWhen := c.MatchWhen(url, invocation)
	if !isMatchWhen {
		return invokers
	}
	if len(c.ThenCondition) == 0 {
Ian Luo's avatar
Ian Luo committed
		return utils.EmptyAddr
	result := roaring.NewBitmap()
	for iter := invokers.Iterator(); iter.HasNext(); {
		index := iter.Next()
Ian Luo's avatar
Ian Luo committed
		invoker := cache.GetInvokers()[index]
		invokerUrl := invoker.GetUrl()
haohongfan's avatar
haohongfan committed
		isMatchThen := c.MatchThen(invokerUrl, url)

	if !result.IsEmpty() {
		return result
	} else if c.Force {
aliiohs's avatar
aliiohs committed
		rule, _ := url.GetParamAndDecoded(constant.RULE_KEY)
		localIP := common.GetLocalIp()
aliiohs's avatar
aliiohs committed
		logger.Warnf("The route result is empty and force execute. consumer: %s, service: %s, router: %s", localIP, url.Service(), rule)
		return result
	}
aliiohs's avatar
aliiohs committed
func parseRule(rule string) (map[string]MatchPair, error) {
aliiohs's avatar
aliiohs committed
	condition := make(map[string]MatchPair, 16)
	if len(rule) == 0 {
aliiohs's avatar
aliiohs committed
		return condition, nil
	}
	var pair MatchPair
邹毅贤's avatar
邹毅贤 committed
	matches := routerPatternReg.FindAllSubmatch([]byte(rule), -1)
aliiohs's avatar
aliiohs committed
	for _, groups := range matches {
		separator := string(groups[1])
		content := string(groups[2])
		switch separator {
		case "":
				Matches:    gxset.NewSet(),
				Mismatches: gxset.NewSet(),
aliiohs's avatar
aliiohs committed
			condition[content] = pair
		case "&":
			if r, ok := condition[content]; ok {
				pair = r
			} else {
					Matches:    gxset.NewSet(),
					Mismatches: gxset.NewSet(),
aliiohs's avatar
aliiohs committed
				condition[content] = pair
			}
		case "=":
			if &pair == nil {
邹毅贤's avatar
邹毅贤 committed
				var startIndex = getStartIndex(rule)
				return nil, perrors.Errorf("Illegal route rule \"%s\", The error char '%s' at index %d before \"%d\".", rule, separator, startIndex, startIndex)
aliiohs's avatar
aliiohs committed
			}
			values = pair.Matches
aliiohs's avatar
aliiohs committed
		case "!=":
			if &pair == nil {
邹毅贤's avatar
邹毅贤 committed
				var startIndex = getStartIndex(rule)
				return nil, perrors.Errorf("Illegal route rule \"%s\", The error char '%s' at index %d before \"%d\".", rule, separator, startIndex, startIndex)
			values = pair.Mismatches
			values.Add(content)
aliiohs's avatar
aliiohs committed
		case ",":
邹毅贤's avatar
邹毅贤 committed
				var startIndex = getStartIndex(rule)
				return nil, perrors.Errorf("Illegal route rule \"%s\", The error char '%s' at index %d before \"%d\".", rule, separator, startIndex, startIndex)
aliiohs's avatar
aliiohs committed
		default:
邹毅贤's avatar
邹毅贤 committed
			var startIndex = getStartIndex(rule)
			return nil, perrors.Errorf("Illegal route rule \"%s\", The error char '%s' at index %d before \"%d\".", rule, separator, startIndex, startIndex)
aliiohs's avatar
aliiohs committed

		}
	}
	return condition, nil
}

邹毅贤's avatar
邹毅贤 committed
func getStartIndex(rule string) int {
邹毅贤's avatar
邹毅贤 committed
	if indexTuple := routerPatternReg.FindIndex([]byte(rule)); len(indexTuple) > 0 {
邹毅贤's avatar
邹毅贤 committed
		return indexTuple[0]
	}
	return -1
}

邹毅贤's avatar
邹毅贤 committed
// MatchWhen MatchWhen
邹毅贤's avatar
邹毅贤 committed
func (c *ConditionRouter) MatchWhen(url *common.URL, invocation protocol.Invocation) bool {
	condition := matchCondition(c.WhenCondition, url, nil, invocation)
	return len(c.WhenCondition) == 0 || condition
邹毅贤's avatar
邹毅贤 committed
// MatchThen MatchThen
邹毅贤's avatar
邹毅贤 committed
func (c *ConditionRouter) MatchThen(url *common.URL, param *common.URL) bool {
	condition := matchCondition(c.ThenCondition, url, param, nil)
	return len(c.ThenCondition) > 0 && condition
邹毅贤's avatar
邹毅贤 committed
// MatchCondition MatchCondition
邹毅贤's avatar
邹毅贤 committed
func matchCondition(pairs map[string]MatchPair, url *common.URL, param *common.URL, invocation protocol.Invocation) bool {
aliiohs's avatar
aliiohs committed
	sample := url.ToMap()
aliiohs's avatar
aliiohs committed
	if sample == nil {
邹毅贤's avatar
邹毅贤 committed
		// because url.ToMap() may return nil, but it should continue to process make condition
		sample = make(map[string]string)
AlexStocks's avatar
AlexStocks committed
	var result bool
aliiohs's avatar
aliiohs committed
	for key, matchPair := range pairs {
		var sampleValue string

		if invocation != nil && ((constant.METHOD_KEY == key) || (constant.METHOD_KEYS == key)) {
			sampleValue = invocation.MethodName()
		} else {
			sampleValue = sample[key]
aliiohs's avatar
aliiohs committed
			if len(sampleValue) == 0 {
				sampleValue = sample[constant.PREFIX_DEFAULT_KEY+key]
aliiohs's avatar
aliiohs committed
		if len(sampleValue) > 0 {
aliiohs's avatar
aliiohs committed
			if !matchPair.isMatch(sampleValue, param) {
邹毅贤's avatar
邹毅贤 committed
				return false
AlexStocks's avatar
AlexStocks committed

			result = true
aliiohs's avatar
aliiohs committed
		} else {
			if !(matchPair.Matches.Empty()) {
邹毅贤's avatar
邹毅贤 committed
				return false
AlexStocks's avatar
AlexStocks committed

			result = true
邹毅贤's avatar
邹毅贤 committed
	return result
// MatchPair Match key pair, condition process
aliiohs's avatar
aliiohs committed
type MatchPair struct {
	Matches    *gxset.HashSet
	Mismatches *gxset.HashSet
func (pair MatchPair) isMatch(value string, param *common.URL) bool {
	if !pair.Matches.Empty() && pair.Mismatches.Empty() {

		for match := range pair.Matches.Items {
pantianying's avatar
pantianying committed
			if isMatchGlobalPattern(match.(string), value, param) {
				return true
			}
		}
		return false
	}
	if !pair.Mismatches.Empty() && pair.Matches.Empty() {

		for mismatch := range pair.Mismatches.Items {
pantianying's avatar
pantianying committed
			if isMatchGlobalPattern(mismatch.(string), value, param) {
				return false
			}
		}
		return true
	}
	if !pair.Mismatches.Empty() && !pair.Matches.Empty() {
Ian Luo's avatar
Ian Luo committed
		// when both mismatches and matches contain the same value, then using mismatches first
		for mismatch := range pair.Mismatches.Items {
pantianying's avatar
pantianying committed
			if isMatchGlobalPattern(mismatch.(string), value, param) {
				return false
			}
		}
		for match := range pair.Matches.Items {
pantianying's avatar
pantianying committed
			if isMatchGlobalPattern(match.(string), value, param) {
aliiohs's avatar
aliiohs committed
	return false
}