| /* |
| * 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 ( |
| "encoding/base64" |
| "net/url" |
| "regexp" |
| "strconv" |
| "strings" |
| "sync" |
| ) |
| |
| import ( |
| perrors "github.com/pkg/errors" |
| ) |
| |
| import ( |
| "github.com/apache/dubbo-go/common" |
| "github.com/apache/dubbo-go/common/constant" |
| ) |
| |
| // FileConditionRouter Use for parse config file of condition router |
| type FileConditionRouter struct { |
| listenableRouter |
| parseOnce sync.Once |
| url *common.URL |
| } |
| |
| // NewFileConditionRouter Create file condition router instance with content ( from config file) |
| func NewFileConditionRouter(content []byte) (*FileConditionRouter, error) { |
| fileRouter := &FileConditionRouter{} |
| rule, err := getRule(string(content)) |
| if err != nil { |
| return nil, perrors.Errorf("yaml.Unmarshal() failed , error:%v", perrors.WithStack(err)) |
| } |
| |
| if !rule.Valid { |
| return nil, perrors.Errorf("rule content is not verify for condition router , error:%v", perrors.WithStack(err)) |
| } |
| |
| fileRouter.generateConditions(rule) |
| |
| return fileRouter, nil |
| } |
| |
| // URL Return URL in file condition router n |
| func (f *FileConditionRouter) URL() *common.URL { |
| f.parseOnce.Do(func() { |
| routerRule := f.routerRule |
| rule := parseCondition(routerRule.Conditions) |
| f.url = common.NewURLWithOptions( |
| common.WithProtocol(constant.CONDITION_ROUTE_PROTOCOL), |
| common.WithIp(constant.ANYHOST_VALUE), |
| common.WithParams(url.Values{}), |
| common.WithParamsValue(constant.RouterForce, strconv.FormatBool(routerRule.Force)), |
| common.WithParamsValue(constant.RouterPriority, strconv.Itoa(routerRule.Priority)), |
| common.WithParamsValue(constant.RULE_KEY, base64.URLEncoding.EncodeToString([]byte(rule))), |
| common.WithParamsValue(constant.ROUTER_KEY, constant.CONDITION_ROUTE_PROTOCOL), |
| common.WithParamsValue(constant.CATEGORY_KEY, constant.ROUTERS_CATEGORY), |
| ) |
| if routerRule.Scope == constant.RouterApplicationScope { |
| f.url.AddParam(constant.APPLICATION_KEY, routerRule.Key) |
| return |
| } |
| grp, srv, ver, e := parseServiceRouterKey(routerRule.Key) |
| if e != nil { |
| return |
| } |
| if len(grp) > 0 { |
| f.url.AddParam(constant.GROUP_KEY, grp) |
| } |
| if len(ver) > 0 { |
| f.url.AddParam(constant.VERSION_KEY, ver) |
| } |
| if len(srv) > 0 { |
| f.url.AddParam(constant.INTERFACE_KEY, srv) |
| } |
| }) |
| return f.url |
| } |
| |
| // The input value must follow [{group}/]{service}[:{version}] pattern |
| // the returning strings are representing group, service, version respectively. |
| // input: mock-group/mock-service:1.0.0 ==> "mock-group", "mock-service", "1.0.0" |
| // input: mock-group/mock-service ==> "mock-group", "mock-service", "" |
| // input: mock-service:1.0.0 ==> "", "mock-service", "1.0.0" |
| // For more samples, please refer to unit test. |
| func parseServiceRouterKey(key string) (string, string, string, error) { |
| if len(strings.TrimSpace(key)) == 0 { |
| return "", "", "", nil |
| } |
| reg := regexp.MustCompile(`(.*/{1})?([^:/]+)(:{1}[^:]*)?`) |
| strs := reg.FindAllStringSubmatch(key, -1) |
| if strs == nil || len(strs) > 1 { |
| return "", "", "", perrors.Errorf("Invalid key, service key must follow [{group}/]{service}[:{version}] pattern") |
| } |
| if len(strs[0]) != 4 { |
| return "", "", "", perrors.Errorf("Parse service router key failed") |
| } |
| grp := strings.TrimSpace(strings.TrimRight(strs[0][1], "/")) |
| srv := strings.TrimSpace(strs[0][2]) |
| ver := strings.TrimSpace(strings.TrimLeft(strs[0][3], ":")) |
| return grp, srv, ver, nil |
| } |
| |
| func parseCondition(conditions []string) string { |
| var when, then string |
| for _, condition := range conditions { |
| condition = strings.Trim(condition, " ") |
| if strings.Contains(condition, "=>") { |
| array := strings.SplitN(condition, "=>", 2) |
| consumer := strings.Trim(array[0], " ") |
| provider := strings.Trim(array[1], " ") |
| if len(consumer) != 0 { |
| if len(when) != 0 { |
| when = strings.Join([]string{when, consumer}, " & ") |
| } else { |
| when = consumer |
| } |
| } |
| if len(provider) != 0 { |
| if len(then) != 0 { |
| then = strings.Join([]string{then, provider}, " & ") |
| } else { |
| then = provider |
| } |
| } |
| } |
| } |
| return strings.Join([]string{when, then}, " => ") |
| } |