Skip to content
Snippets Groups Projects
url.go 14.9 KiB
Newer Older
AlexStocks's avatar
AlexStocks committed
/*
 * 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.
 */
fangyincheng's avatar
fangyincheng committed

vito.he's avatar
vito.he committed
package common
fangyincheng's avatar
fangyincheng committed

import (
	"bytes"
	"encoding/base64"
fangyincheng's avatar
fangyincheng committed
	"fmt"
zonghaishang's avatar
zonghaishang committed
	"math"
fangyincheng's avatar
fangyincheng committed
	"net"
	"net/url"
	"strconv"
	"strings"
fangyincheng's avatar
fangyincheng committed
)

import (
邹毅贤's avatar
邹毅贤 committed
	gxset "github.com/dubbogo/gost/container/set"
	"github.com/jinzhu/copier"
fangyincheng's avatar
fangyincheng committed
	perrors "github.com/pkg/errors"
xujianhai666's avatar
xujianhai666 committed
	"github.com/satori/go.uuid"
fangyincheng's avatar
fangyincheng committed
)
vito.he's avatar
vito.he committed
import (
	"github.com/apache/dubbo-go/common/constant"
vito.he's avatar
vito.he committed
)
/////////////////////////////////
// dubbo role type
/////////////////////////////////

const (
	CONSUMER = iota
	CONFIGURATOR
	ROUTER
	PROVIDER
)

var (
	// DubboNodes ...
	DubboNodes = [...]string{"consumers", "configurators", "routers", "providers"}
aliiohs's avatar
aliiohs committed
	DubboRole  = [...]string{"consumer", "", "routers", "provider"}
type RoleType int

func (t RoleType) String() string {
	return DubboNodes[t]
}

func (t RoleType) Role() string {
	return DubboRole[t]
vito.he's avatar
vito.he committed
type baseUrl struct {
	Protocol string
	Location string // ip+port
	Ip       string
	Port     string
	//url.Values is not safe map, add to avoid concurrent map read and map write error
vito.he's avatar
vito.he committed
	paramsLock   sync.RWMutex
vito.he's avatar
vito.he committed
	params       url.Values
vito.he's avatar
vito.he committed
	PrimitiveURL string
	ctx          context.Context
type URL struct {
vito.he's avatar
vito.he committed
	baseUrl
	Path     string // like  /com.ikurento.dubbo.UserProvider3
	Username string
	Password string
	SubURL *URL
type option func(*URL)

// WithUsername ...
func WithUsername(username string) option {
	return func(url *URL) {
		url.Username = username
	}
}

// WithPassword ...
func WithPassword(pwd string) option {
	return func(url *URL) {
		url.Password = pwd
	}
}
// WithMethods ...
func WithMethods(methods []string) option {
	return func(url *URL) {
		url.Methods = methods
	}
}

// WithParams ...
func WithParams(params url.Values) option {
	return func(url *URL) {
vito.he's avatar
vito.he committed
		url.params = params
// WithParamsValue ...
func WithParamsValue(key, val string) option {
	return func(url *URL) {
vito.he's avatar
vito.he committed
		url.SetParam(key, val)
// WithProtocol ...
vito.he's avatar
vito.he committed
func WithProtocol(proto string) option {
	return func(url *URL) {
		url.Protocol = proto
	}
}
vito.he's avatar
vito.he committed
func WithIp(ip string) option {
	return func(url *URL) {
		url.Ip = ip
	}
}

vito.he's avatar
vito.he committed
func WithPort(port string) option {
	return func(url *URL) {
		url.Port = port
	}
}

func WithPath(path string) option {
	return func(url *URL) {
		url.Path = "/" + strings.TrimPrefix(path, "/")
// WithLocation ...
func WithLocation(location string) option {
	return func(url *URL) {
		url.Location = location
	}
}
// WithToken ...
xujianhai666's avatar
xujianhai666 committed
func WithToken(token string) option {
	return func(url *URL) {
		if len(token) > 0 {
			value := token
			if strings.ToLower(token) == "true" || strings.ToLower(token) == "default" {
				value = uuid.NewV4().String()
xujianhai666's avatar
xujianhai666 committed
			}
			url.SetParam(constant.TOKEN_KEY, value)
		}
	}
}

// NewURLWithOptions ...
func NewURLWithOptions(opts ...option) *URL {
	url := &URL{}
	for _, opt := range opts {
		opt(url)
	}
fangyincheng's avatar
fangyincheng committed
	url.Location = url.Ip + ":" + url.Port
func NewURL(ctx context.Context, urlString string, opts ...option) (URL, error) {
fangyincheng's avatar
fangyincheng committed
	var (
		err          error
		rawUrlString string
		serviceUrl   *url.URL
		s            = URL{baseUrl: baseUrl{ctx: ctx}}
	// new a null instance
	if urlString == "" {
		return s, nil
	}

fangyincheng's avatar
fangyincheng committed
	rawUrlString, err = url.QueryUnescape(urlString)
	if err != nil {
fangyincheng's avatar
fangyincheng committed
		return s, perrors.Errorf("url.QueryUnescape(%s),  error{%v}", urlString, err)
	//rawUrlString = "//" + rawUrlString
	if strings.Index(rawUrlString, "//") < 0 {
		t := URL{baseUrl: baseUrl{ctx: ctx}}
		for _, opt := range opts {
			opt(&t)
		}
		rawUrlString = t.Protocol + "://" + rawUrlString
	}
fangyincheng's avatar
fangyincheng committed
	serviceUrl, err = url.Parse(rawUrlString)
	if err != nil {
fangyincheng's avatar
fangyincheng committed
		return s, perrors.Errorf("url.Parse(url string{%s}),  error{%v}", rawUrlString, err)
vito.he's avatar
vito.he committed
	s.params, err = url.ParseQuery(serviceUrl.RawQuery)
fangyincheng's avatar
fangyincheng committed
	if err != nil {
fangyincheng's avatar
fangyincheng committed
		return s, perrors.Errorf("url.ParseQuery(raw url string{%s}),  error{%v}", serviceUrl.RawQuery, err)
vito.he's avatar
vito.he committed
	s.PrimitiveURL = urlString
	s.Protocol = serviceUrl.Scheme
	s.Username = serviceUrl.User.Username()
	s.Password, _ = serviceUrl.User.Password()
vito.he's avatar
vito.he committed
	s.Location = serviceUrl.Host
	s.Path = serviceUrl.Path
	if strings.Contains(s.Location, ":") {
		s.Ip, s.Port, err = net.SplitHostPort(s.Location)
fangyincheng's avatar
fangyincheng committed
		if err != nil {
aliiohs's avatar
aliiohs committed
			return s, perrors.Errorf("net.SplitHostPort(url.Host{%s}), error{%v}", s.Location, err)
fangyincheng's avatar
fangyincheng committed
	return s, nil
}

func (c URL) URLEqual(url URL) bool {
fangyincheng's avatar
fangyincheng committed
	c.Ip = ""
	c.Port = ""
vito.he's avatar
vito.he committed
	url.Ip = ""
	url.Port = ""
	cGroup := c.GetParam(constant.GROUP_KEY, "")
	urlGroup := url.GetParam(constant.GROUP_KEY, "")
	cKey := c.Key()
	urlKey := url.Key()

	if cGroup == constant.ANY_VALUE {
		cKey = strings.Replace(cKey, "group=*", "group="+urlGroup, 1)
	} else if urlGroup == constant.ANY_VALUE {
		urlKey = strings.Replace(urlKey, "group=*", "group="+cGroup, 1)
	}
	if cKey != urlKey {
vito.he's avatar
vito.he committed
		return false
	}
	if url.GetParam(constant.ENABLED_KEY, "true") != "true" && url.GetParam(constant.ENABLED_KEY, "") != constant.ANY_VALUE {
		return false
	}
	//TODO :may need add interface key any value condition
	if !isMatchCategory(url.GetParam(constant.CATEGORY_KEY, constant.DEFAULT_CATEGORY), c.GetParam(constant.CATEGORY_KEY, constant.DEFAULT_CATEGORY)) {
		return false
	}
vito.he's avatar
vito.he committed
	return true
}
func isMatchCategory(category1 string, category2 string) bool {
	if len(category2) == 0 {
		return category1 == constant.DEFAULT_CATEGORY
	} else if strings.Contains(category2, constant.ANY_VALUE) {
		return true
	} else if strings.Contains(category2, constant.REMOVE_VALUE_PREFIX) {
		return !strings.Contains(category2, constant.REMOVE_VALUE_PREFIX+category1)
	} else {
vito.he's avatar
vito.he committed
		return strings.Contains(category2, category1)
func (c URL) String() string {
	var buildString string
	if len(c.Username) == 0 && len(c.Password) == 0 {
		buildString = fmt.Sprintf(
			"%s://%s:%s%s?",
			c.Protocol, c.Ip, c.Port, c.Path)
	} else {
		buildString = fmt.Sprintf(
			"%s://%s:%s@%s:%s%s?",
			c.Protocol, c.Username, c.Password, c.Ip, c.Port, c.Path)
	}
vito.he's avatar
vito.he committed
	c.paramsLock.RLock()
	buildString += c.params.Encode()
	c.paramsLock.RUnlock()
	return buildString
vito.he's avatar
vito.he committed
func (c URL) Key() string {
	buildString := fmt.Sprintf(
		"%s://%s:%s@%s:%s/?interface=%s&group=%s&version=%s",
fangyincheng's avatar
fangyincheng committed
		c.Protocol, c.Username, c.Password, c.Ip, c.Port, c.Service(), c.GetParam(constant.GROUP_KEY, ""), c.GetParam(constant.VERSION_KEY, ""))
vito.he's avatar
vito.he committed
	return buildString
	//return c.ServiceKey()
}

邹毅贤's avatar
邹毅贤 committed
func (c *URL) ColonSeparatedKey() string {
	intf := c.GetParam(constant.INTERFACE_KEY, strings.TrimPrefix(c.Path, "/"))
	if intf == "" {
		return ""
	}
	buf := &bytes.Buffer{}
	buf.WriteString(intf)
	buf.WriteString(":")
	version := c.GetParam(constant.VERSION_KEY, "")
	if version != "" && version != "0.0.0" {
		buf.WriteString(version)
	}
	group := c.GetParam(constant.GROUP_KEY, "")
	buf.WriteString(":")
	if group != "" {
		buf.WriteString(group)
	}
	return buf.String()
}

aliiohs's avatar
aliiohs committed
func (c *URL) GetBackupUrls() []*URL {
邹毅贤's avatar
邹毅贤 committed
	var (
		urls []*URL
		host string
	)
renzhiyuan's avatar
renzhiyuan committed
	urls = append(urls, c)
	backups := strings.Split(c.GetParam(constant.BACKUP_KEY, ""), "")
	for _, address := range backups {
		index := strings.LastIndex(address, ":")
		port := c.Port
		if index > 0 {
			host = address[:index]
			port = address[index+1:]
		} else {
aliiohs's avatar
aliiohs committed
			host = address
aliiohs's avatar
aliiohs committed
		newURL := NewURLWithOptions(
			WithProtocol(c.Protocol),
			WithPath(c.Path),
			WithIp(host),
			WithUsername(c.Username),
			WithPassword(c.Password),
			WithPort(port),
邹毅贤's avatar
邹毅贤 committed
			WithParams(c.params))
aliiohs's avatar
aliiohs committed

renzhiyuan's avatar
renzhiyuan committed
		urls = append(urls, newURL)
	}
	return urls
}

// ServiceKey ...
func (c URL) ServiceKey() string {
	intf := c.GetParam(constant.INTERFACE_KEY, strings.TrimPrefix(c.Path, "/"))
	if intf == "" {
		return ""
	}
	buf := &bytes.Buffer{}
	group := c.GetParam(constant.GROUP_KEY, "")
	if group != "" {
		buf.WriteString(group)
		buf.WriteString("/")
	}

	buf.WriteString(intf)

fangyincheng's avatar
fangyincheng committed
	version := c.GetParam(constant.VERSION_KEY, "")
	if version != "" && version != "0.0.0" {
		buf.WriteString(":")
		buf.WriteString(version)
	}

	return buf.String()
vito.he's avatar
vito.he committed
}

// EncodedServiceKey ...
func (c *URL) EncodedServiceKey() string {
	serviceKey := c.ServiceKey()
	return strings.Replace(serviceKey, "/", "*", 1)
}

func (c URL) Context() context.Context {
func (c URL) Service() string {
	service := c.GetParam(constant.INTERFACE_KEY, strings.TrimPrefix(c.Path, "/"))
	if service != "" {
		return service
	} else if c.SubURL != nil {
		service = c.GetParam(constant.INTERFACE_KEY, strings.TrimPrefix(c.Path, "/"))
aliiohs's avatar
aliiohs committed
		if service != "" { //if url.path is "" then return suburl's path, special for registry url
			return service
		}
	}
	return ""
func (c *URL) AddParam(key string, value string) {
vito.he's avatar
vito.he committed
	c.params.Add(key, value)
	c.paramsLock.Unlock()
}

vito.he's avatar
vito.he committed
func (c *URL) SetParam(key string, value string) {
	c.paramsLock.Lock()
	c.params.Set(key, value)
// RangeParams ...
vito.he's avatar
vito.he committed
func (c *URL) RangeParams(f func(key, value string) bool) {
	c.paramsLock.RLock()
vito.he's avatar
vito.he committed
	defer c.paramsLock.RUnlock()
vito.he's avatar
vito.he committed
	for k, v := range c.params {
		if !f(k, v[0]) {
			break
		}
	}
}

func (c URL) GetParam(s string, d string) string {
vito.he's avatar
vito.he committed
	c.paramsLock.RLock()
vito.he's avatar
vito.he committed
	if r = c.params.Get(s); len(r) == 0 {
vito.he's avatar
vito.he committed
	c.paramsLock.RUnlock()
vito.he's avatar
vito.he committed

// GetParams ...
vito.he's avatar
vito.he committed
func (c URL) GetParams() url.Values {
	return c.params
}

// GetParamAndDecoded ...
aliiohs's avatar
aliiohs committed
func (c URL) GetParamAndDecoded(key string) (string, error) {
vito.he's avatar
vito.he committed
	c.paramsLock.RLock()
	defer c.paramsLock.RUnlock()
	ruleDec, err := base64.URLEncoding.DecodeString(c.GetParam(key, ""))
	value := string(ruleDec)
	return value, err
}
// GetRawParam ...
aliiohs's avatar
aliiohs committed
func (c URL) GetRawParam(key string) string {
aliiohs's avatar
aliiohs committed
	switch key {
	case "protocol":
aliiohs's avatar
aliiohs committed
	case "username":
aliiohs's avatar
aliiohs committed
	case "host":
		return strings.Split(c.Location, ":")[0]
aliiohs's avatar
aliiohs committed
	case "password":
aliiohs's avatar
aliiohs committed
	case "port":
aliiohs's avatar
aliiohs committed
	case "path":
aliiohs's avatar
aliiohs committed
	default:
vito.he's avatar
vito.he committed
		return c.GetParam(key, "")
邹毅贤's avatar
邹毅贤 committed
// GetParamBool
aliiohs's avatar
aliiohs committed
func (c URL) GetParamBool(s string, d bool) bool {

	var r bool
	var err error
vito.he's avatar
vito.he committed
	if r, err = strconv.ParseBool(c.GetParam(s, "")); err != nil {
aliiohs's avatar
aliiohs committed
		return d
	}
	return r
}
// GetParamInt ...
func (c URL) GetParamInt(s string, d int64) int64 {
vito.he's avatar
vito.he committed

	if r, err = strconv.Atoi(c.GetParam(s, "")); r == 0 || err != nil {
// GetMethodParamInt ...
func (c URL) GetMethodParamInt(method string, key string, d int64) int64 {
vito.he's avatar
vito.he committed
	c.paramsLock.RLock()
	defer c.paramsLock.RUnlock()
vito.he's avatar
vito.he committed
	if r, err = strconv.Atoi(c.GetParam("methods."+method+"."+key, "")); r == 0 || err != nil {
// GetMethodParamInt64 ...
zonghaishang's avatar
zonghaishang committed
func (c URL) GetMethodParamInt64(method string, key string, d int64) int64 {
	r := c.GetMethodParamInt(method, key, math.MinInt64)
	if r == math.MinInt64 {
		return c.GetParamInt(key, d)
	}

	return r
}

// GetMethodParam ...
func (c URL) GetMethodParam(method string, key string, d string) string {
vito.he's avatar
vito.he committed
	if r = c.GetParam("methods."+method+"."+key, ""); r == "" {
// GetMethodParamBool ...
Ooo0oO0o0oO's avatar
Ooo0oO0o0oO committed
func (c URL) GetMethodParamBool(method string, key string, d bool) bool {
	r := c.GetParamBool("methods."+method+"."+key, d)
	return r
}

// RemoveParams ...
fangyincheng's avatar
fangyincheng committed
func (c *URL) RemoveParams(set *gxset.HashSet) {
vito.he's avatar
vito.he committed
	c.paramsLock.Lock()
	defer c.paramsLock.Unlock()
	for k := range set.Items {
vito.he's avatar
vito.he committed
		s := k.(string)
vito.he's avatar
vito.he committed
		delete(c.params, s)
// SetParams ...
vito.he's avatar
vito.he committed
func (c *URL) SetParams(m url.Values) {
	for k := range m {
		c.SetParam(k, m.Get(k))
aliiohs's avatar
aliiohs committed
// ToMap transfer URL to Map
func (c URL) ToMap() map[string]string {

	paramsMap := make(map[string]string)
vito.he's avatar
vito.he committed
	c.RangeParams(func(key, value string) bool {
		paramsMap[key] = value
		return true
	})

aliiohs's avatar
aliiohs committed
	if c.Protocol != "" {
		paramsMap["protocol"] = c.Protocol
	}
	if c.Username != "" {
		paramsMap["username"] = c.Username
	}
	if c.Password != "" {
		paramsMap["password"] = c.Password
	}
	if c.Location != "" {
		paramsMap["host"] = strings.Split(c.Location, ":")[0]
		var port string
		if strings.Contains(c.Location, ":") {
			port = strings.Split(c.Location, ":")[1]
		} else {
			port = "0"
		}
		paramsMap["port"] = port
aliiohs's avatar
aliiohs committed
	}
	if c.Protocol != "" {
		paramsMap["protocol"] = c.Protocol
	}
	if c.Path != "" {
		paramsMap["path"] = c.Path
	}
aliiohs's avatar
aliiohs committed
	if len(paramsMap) == 0 {
		return nil
	}
aliiohs's avatar
aliiohs committed
	return paramsMap
}

// configuration  > reference config >service config
//  in this function we should merge the reference local url config into the service url from registry.
//TODO configuration merge, in the future , the configuration center's config should merge too.
vito.he's avatar
vito.he committed

vito.he's avatar
vito.he committed
func MergeUrl(serviceUrl *URL, referenceUrl *URL) *URL {
	mergedUrl := serviceUrl.Clone()

	//iterator the referenceUrl if serviceUrl not have the key ,merge in
vito.he's avatar
vito.he committed
	referenceUrl.RangeParams(func(key, value string) bool {
		if v := mergedUrl.GetParam(key, ""); len(v) == 0 {
			mergedUrl.SetParam(key, value)
vito.he's avatar
vito.he committed
		return true
vito.he's avatar
vito.he committed
	//loadBalance,cluster,retries strategy config
	methodConfigMergeFcn := mergeNormalParam(mergedUrl, referenceUrl, []string{constant.LOADBALANCE_KEY, constant.CLUSTER_KEY, constant.RETRIES_KEY, constant.TIMEOUT_KEY})
vito.he's avatar
vito.he committed
	if v := serviceUrl.GetParam(constant.TIMESTAMP_KEY, ""); len(v) > 0 {
		mergedUrl.SetParam(constant.REMOTE_TIMESTAMP_KEY, v)
		mergedUrl.SetParam(constant.TIMESTAMP_KEY, referenceUrl.GetParam(constant.TIMESTAMP_KEY, ""))
	}

	//finally execute methodConfigMergeFcn
	for _, method := range referenceUrl.Methods {
		for _, fcn := range methodConfigMergeFcn {
			fcn("methods." + method)
		}
	}

	return mergedUrl
}
func (c *URL) Clone() *URL {
	newUrl := &URL{}
	copier.Copy(newUrl, c)
vito.he's avatar
vito.he committed
	newUrl.params = url.Values{}
	c.RangeParams(func(key, value string) bool {
		newUrl.SetParam(key, value)
		return true
	})
vito.he's avatar
vito.he committed

vito.he's avatar
vito.he committed
func mergeNormalParam(mergedUrl *URL, referenceUrl *URL, paramKeys []string) []func(method string) {
vito.he's avatar
vito.he committed
	var methodConfigMergeFcn = []func(method string){}
vito.he's avatar
vito.he committed
	for _, paramKey := range paramKeys {
vito.he's avatar
vito.he committed
		if v := referenceUrl.GetParam(paramKey, ""); len(v) > 0 {
			mergedUrl.SetParam(paramKey, v)
vito.he's avatar
vito.he committed
		}
		methodConfigMergeFcn = append(methodConfigMergeFcn, func(method string) {
vito.he's avatar
vito.he committed
			if v := referenceUrl.GetParam(method+"."+paramKey, ""); len(v) > 0 {
				mergedUrl.SetParam(method+"."+paramKey, v)
vito.he's avatar
vito.he committed
			}
		})
	}
vito.he's avatar
vito.he committed
	return methodConfigMergeFcn
vito.he's avatar
vito.he committed
}