dubbo.apache.org/dubbo-go/v3@v3.1.1/common/url.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  package common
    19  
    20  import (
    21  	"bytes"
    22  	"encoding/base64"
    23  	"fmt"
    24  	"math"
    25  	"net"
    26  	"net/url"
    27  	"strconv"
    28  	"strings"
    29  	"sync"
    30  	"time"
    31  )
    32  
    33  import (
    34  	cm "github.com/Workiva/go-datastructures/common"
    35  
    36  	gxset "github.com/dubbogo/gost/container/set"
    37  
    38  	"github.com/google/uuid"
    39  
    40  	"github.com/jinzhu/copier"
    41  
    42  	perrors "github.com/pkg/errors"
    43  )
    44  
    45  import (
    46  	"dubbo.apache.org/dubbo-go/v3/common/constant"
    47  )
    48  
    49  // dubbo role type constant
    50  const (
    51  	CONSUMER = iota
    52  	CONFIGURATOR
    53  	ROUTER
    54  	PROVIDER
    55  	PROTOCOL = "protocol"
    56  )
    57  
    58  var (
    59  	DubboNodes          = [...]string{"consumers", "configurators", "routers", "providers"} // Dubbo service node
    60  	DubboRole           = [...]string{"consumer", "", "routers", "provider"}                // Dubbo service role
    61  	compareURLEqualFunc CompareURLEqualFunc                                                 // function to compare two URL is equal
    62  )
    63  
    64  func init() {
    65  	compareURLEqualFunc = defaultCompareURLEqual
    66  }
    67  
    68  // nolint
    69  type RoleType int
    70  
    71  func (t RoleType) String() string {
    72  	return DubboNodes[t]
    73  }
    74  
    75  // Role returns role by @RoleType
    76  func (t RoleType) Role() string {
    77  	return DubboRole[t]
    78  }
    79  
    80  type baseURL struct {
    81  	Protocol string
    82  	Location string // ip+port
    83  	Ip       string
    84  	Port     string
    85  
    86  	PrimitiveURL string
    87  }
    88  
    89  // noCopy may be embedded into structs which must not be copied
    90  // after the first use.
    91  //
    92  // See https://golang.org/issues/8005#issuecomment-190753527
    93  // for details.
    94  type noCopy struct{}
    95  
    96  // Lock is a no-op used by -copylocks checker from `go vet`.
    97  func (*noCopy) Lock()   {}
    98  func (*noCopy) Unlock() {}
    99  
   100  // URL thread-safe. but this URL should not be copied.
   101  // we fail to define this struct to be immutable object.
   102  // but, those method which will update the URL, including SetParam, SetParams
   103  // are only allowed to be invoked in creating URL instance
   104  // Please keep in mind that this struct is immutable after it has been created and initialized.
   105  type URL struct {
   106  	noCopy noCopy
   107  
   108  	baseURL
   109  	// url.Values is not safe map, add to avoid concurrent map read and map write error
   110  	paramsLock sync.RWMutex
   111  	params     url.Values
   112  
   113  	Path     string // like  /com.ikurento.dubbo.UserProvider
   114  	Username string
   115  	Password string
   116  	Methods  []string
   117  	// special for registry
   118  	SubURL     *URL
   119  	attributes sync.Map
   120  }
   121  
   122  func (c *URL) AddAttribute(key string, value interface{}) {
   123  	if value != nil {
   124  		c.attributes.Store(key, value)
   125  	}
   126  }
   127  
   128  func (c *URL) GetAttribute(key string) interface{} {
   129  	v, _ := c.attributes.Load(key)
   130  	return v
   131  }
   132  
   133  // JavaClassName POJO for URL
   134  func (c *URL) JavaClassName() string {
   135  	return "org.apache.dubbo.common.URL"
   136  }
   137  
   138  // Option accepts URL
   139  // Option will define a function of handling URL
   140  type Option func(*URL)
   141  
   142  // WithUsername sets username for URL
   143  func WithUsername(username string) Option {
   144  	return func(url *URL) {
   145  		url.Username = username
   146  	}
   147  }
   148  
   149  // WithPassword sets password for URL
   150  func WithPassword(pwd string) Option {
   151  	return func(url *URL) {
   152  		url.Password = pwd
   153  	}
   154  }
   155  
   156  // WithMethods sets methods for URL
   157  func WithMethods(methods []string) Option {
   158  	return func(url *URL) {
   159  		url.Methods = methods
   160  	}
   161  }
   162  
   163  // WithParams deep copy the params in the argument into params of the target URL
   164  func WithParams(params url.Values) Option {
   165  	return func(url *URL) {
   166  		url.SetParams(params)
   167  	}
   168  }
   169  
   170  // WithParamsValue sets params field for URL
   171  func WithParamsValue(key, val string) Option {
   172  	return func(url *URL) {
   173  		url.SetParam(key, val)
   174  	}
   175  }
   176  
   177  // WithProtocol sets protocol for URL
   178  func WithProtocol(proto string) Option {
   179  	return func(url *URL) {
   180  		url.Protocol = proto
   181  	}
   182  }
   183  
   184  // WithIp sets ip for URL
   185  func WithIp(ip string) Option {
   186  	return func(url *URL) {
   187  		url.Ip = ip
   188  	}
   189  }
   190  
   191  // WithPort sets port for URL
   192  func WithPort(port string) Option {
   193  	return func(url *URL) {
   194  		url.Port = port
   195  	}
   196  }
   197  
   198  // WithPath sets path for URL
   199  func WithPath(path string) Option {
   200  	return func(url *URL) {
   201  		url.Path = "/" + strings.TrimPrefix(path, "/")
   202  	}
   203  }
   204  
   205  // WithInterface sets interface param for URL
   206  func WithInterface(v string) Option {
   207  	return func(url *URL) {
   208  		url.SetParam(constant.InterfaceKey, v)
   209  	}
   210  }
   211  
   212  // WithLocation sets location for URL
   213  func WithLocation(location string) Option {
   214  	return func(url *URL) {
   215  		url.Location = location
   216  	}
   217  }
   218  
   219  // WithToken sets token for URL
   220  func WithToken(token string) Option {
   221  	return func(url *URL) {
   222  		if len(token) > 0 {
   223  			value := token
   224  			if strings.ToLower(token) == "true" || strings.ToLower(token) == "default" {
   225  				u, _ := uuid.NewUUID()
   226  				value = u.String()
   227  			}
   228  			url.SetParam(constant.TokenKey, value)
   229  		}
   230  	}
   231  }
   232  
   233  // NewURLWithOptions will create a new URL with options
   234  func NewURLWithOptions(opts ...Option) *URL {
   235  	newURL := &URL{}
   236  	for _, opt := range opts {
   237  		opt(newURL)
   238  	}
   239  	newURL.Location = newURL.Ip + ":" + newURL.Port
   240  	return newURL
   241  }
   242  
   243  // NewURL will create a new URL
   244  // the urlString should not be empty
   245  func NewURL(urlString string, opts ...Option) (*URL, error) {
   246  	s := URL{baseURL: baseURL{}}
   247  	if urlString == "" {
   248  		return &s, nil
   249  	}
   250  
   251  	rawURLString, err := url.QueryUnescape(urlString)
   252  	if err != nil {
   253  		return &s, perrors.Errorf("URL.QueryUnescape(%s),  error{%v}", urlString, err)
   254  	}
   255  
   256  	// rawURLString = "//" + rawURLString
   257  	if !strings.Contains(rawURLString, "//") {
   258  		t := URL{baseURL: baseURL{}}
   259  		for _, opt := range opts {
   260  			opt(&t)
   261  		}
   262  		rawURLString = t.Protocol + "://" + rawURLString
   263  	}
   264  
   265  	serviceURL, urlParseErr := url.Parse(rawURLString)
   266  	if urlParseErr != nil {
   267  		return &s, perrors.Errorf("URL.Parse(URL string{%s}),  error{%v}", rawURLString, err)
   268  	}
   269  
   270  	s.params, err = url.ParseQuery(serviceURL.RawQuery)
   271  	if err != nil {
   272  		return &s, perrors.Errorf("URL.ParseQuery(raw URL string{%s}),  error{%v}", serviceURL.RawQuery, err)
   273  	}
   274  
   275  	s.PrimitiveURL = urlString
   276  	s.Protocol = serviceURL.Scheme
   277  	s.Username = serviceURL.User.Username()
   278  	s.Password, _ = serviceURL.User.Password()
   279  	s.Location = serviceURL.Host
   280  	s.Path = serviceURL.Path
   281  	for _, location := range strings.Split(s.Location, ",") {
   282  		location = strings.Trim(location, " ")
   283  		if strings.Contains(location, ":") {
   284  			s.Ip, s.Port, err = net.SplitHostPort(location)
   285  			if err != nil {
   286  				return &s, perrors.Errorf("net.SplitHostPort(url.Host{%s}), error{%v}", s.Location, err)
   287  			}
   288  			break
   289  		}
   290  	}
   291  	for _, opt := range opts {
   292  		opt(&s)
   293  	}
   294  	if s.params.Get(constant.RegistryGroupKey) != "" {
   295  		s.PrimitiveURL = strings.Join([]string{s.PrimitiveURL, s.params.Get(constant.RegistryGroupKey)}, constant.PathSeparator)
   296  	}
   297  	return &s, nil
   298  }
   299  
   300  func MatchKey(serviceKey string, protocol string) string {
   301  	return serviceKey + ":" + protocol
   302  }
   303  
   304  // Group get group
   305  func (c *URL) Group() string {
   306  	return c.GetParam(constant.GroupKey, "")
   307  }
   308  
   309  // Interface get interface
   310  func (c *URL) Interface() string {
   311  	return c.GetParam(constant.InterfaceKey, "")
   312  }
   313  
   314  // Version get group
   315  func (c *URL) Version() string {
   316  	return c.GetParam(constant.VersionKey, "")
   317  }
   318  
   319  // Address with format "ip:port"
   320  func (c *URL) Address() string {
   321  	if c.Port == "" {
   322  		return c.Ip
   323  	}
   324  	return c.Ip + ":" + c.Port
   325  }
   326  
   327  // URLEqual judge @URL and @c is equal or not.
   328  func (c *URL) URLEqual(url *URL) bool {
   329  	tmpC := c.Clone()
   330  	tmpC.Ip = ""
   331  	tmpC.Port = ""
   332  
   333  	tmpURL := url.Clone()
   334  	tmpURL.Ip = ""
   335  	tmpURL.Port = ""
   336  
   337  	cGroup := tmpC.GetParam(constant.GroupKey, "")
   338  	urlGroup := tmpURL.GetParam(constant.GroupKey, "")
   339  	cKey := tmpC.Key()
   340  	urlKey := tmpURL.Key()
   341  
   342  	if cGroup == constant.AnyValue {
   343  		cKey = strings.Replace(cKey, "group=*", "group="+urlGroup, 1)
   344  	} else if urlGroup == constant.AnyValue {
   345  		urlKey = strings.Replace(urlKey, "group=*", "group="+cGroup, 1)
   346  	}
   347  
   348  	// 1. protocol, username, password, ip, port, service name, group, version should be equal
   349  	if cKey != urlKey {
   350  		return false
   351  	}
   352  
   353  	// 2. if URL contains enabled key, should be true, or *
   354  	if tmpURL.GetParam(constant.EnabledKey, "true") != "true" && tmpURL.GetParam(constant.EnabledKey, "") != constant.AnyValue {
   355  		return false
   356  	}
   357  
   358  	// TODO :may need add interface key any value condition
   359  	return isMatchCategory(tmpURL.GetParam(constant.CategoryKey, constant.DefaultCategory), tmpC.GetParam(constant.CategoryKey, constant.DefaultCategory))
   360  }
   361  
   362  func isMatchCategory(category1 string, category2 string) bool {
   363  	if len(category2) == 0 {
   364  		return category1 == constant.DefaultCategory
   365  	} else if strings.Contains(category2, constant.AnyValue) {
   366  		return true
   367  	} else if strings.Contains(category2, constant.RemoveValuePrefix) {
   368  		return !strings.Contains(category2, constant.RemoveValuePrefix+category1)
   369  	} else {
   370  		return strings.Contains(category2, category1)
   371  	}
   372  }
   373  
   374  func (c *URL) String() string {
   375  	c.paramsLock.Lock()
   376  	defer c.paramsLock.Unlock()
   377  	var buf strings.Builder
   378  	if len(c.Username) == 0 && len(c.Password) == 0 {
   379  		buf.WriteString(fmt.Sprintf("%s://%s:%s%s?", c.Protocol, c.Ip, c.Port, c.Path))
   380  	} else {
   381  		buf.WriteString(fmt.Sprintf("%s://%s:%s@%s:%s%s?", c.Protocol, c.Username, c.Password, c.Ip, c.Port, c.Path))
   382  	}
   383  	buf.WriteString(c.params.Encode())
   384  	return buf.String()
   385  }
   386  
   387  // Key gets key
   388  func (c *URL) Key() string {
   389  	buildString := fmt.Sprintf("%s://%s:%s@%s:%s/?interface=%s&group=%s&version=%s",
   390  		c.Protocol, c.Username, c.Password, c.Ip, c.Port, c.Service(), c.GetParam(constant.GroupKey, ""), c.GetParam(constant.VersionKey, ""))
   391  	return buildString
   392  }
   393  
   394  // GetCacheInvokerMapKey get directory cacheInvokerMap key
   395  func (c *URL) GetCacheInvokerMapKey() string {
   396  	urlNew, _ := NewURL(c.PrimitiveURL)
   397  
   398  	buildString := fmt.Sprintf("%s://%s:%s@%s:%s/?interface=%s&group=%s&version=%s&timestamp=%s&"+constant.MeshClusterIDKey+"=%s",
   399  		c.Protocol, c.Username, c.Password, c.Ip, c.Port, c.Service(), c.GetParam(constant.GroupKey, ""),
   400  		c.GetParam(constant.VersionKey, ""), urlNew.GetParam(constant.TimestampKey, ""),
   401  		c.GetParam(constant.MeshClusterIDKey, ""))
   402  	return buildString
   403  }
   404  
   405  // ServiceKey gets a unique key of a service.
   406  func (c *URL) ServiceKey() string {
   407  	return ServiceKey(c.GetParam(constant.InterfaceKey, strings.TrimPrefix(c.Path, constant.PathSeparator)),
   408  		c.GetParam(constant.GroupKey, ""), c.GetParam(constant.VersionKey, ""))
   409  }
   410  
   411  func ServiceKey(intf string, group string, version string) string {
   412  	if intf == "" {
   413  		return ""
   414  	}
   415  	buf := &bytes.Buffer{}
   416  	if group != "" {
   417  		buf.WriteString(group)
   418  		buf.WriteString("/")
   419  	}
   420  
   421  	buf.WriteString(intf)
   422  
   423  	if version != "" && version != "0.0.0" {
   424  		buf.WriteString(":")
   425  		buf.WriteString(version)
   426  	}
   427  
   428  	return buf.String()
   429  }
   430  
   431  // ParseServiceKey gets interface, group and version from service key
   432  func ParseServiceKey(serviceKey string) (string, string, string) {
   433  	var (
   434  		group   string
   435  		version string
   436  	)
   437  	if serviceKey == "" {
   438  		return "", "", ""
   439  	}
   440  	// get group if it exists
   441  	sepIndex := strings.Index(serviceKey, constant.PathSeparator)
   442  	if sepIndex != -1 {
   443  		group = serviceKey[:sepIndex]
   444  		serviceKey = serviceKey[sepIndex+1:]
   445  	}
   446  	// get version if it exists
   447  	sepIndex = strings.LastIndex(serviceKey, constant.KeySeparator)
   448  	if sepIndex != -1 {
   449  		version = serviceKey[sepIndex+1:]
   450  		serviceKey = serviceKey[:sepIndex]
   451  	}
   452  
   453  	return serviceKey, group, version
   454  }
   455  
   456  // IsAnyCondition judges if is any condition
   457  func IsAnyCondition(intf, group, version string, serviceURL *URL) bool {
   458  	return intf == constant.AnyValue && (group == constant.AnyValue ||
   459  		group == serviceURL.Group()) && (version == constant.AnyValue || version == serviceURL.Version())
   460  }
   461  
   462  // ColonSeparatedKey
   463  // The format is "{interface}:[version]:[group]"
   464  func (c *URL) ColonSeparatedKey() string {
   465  	intf := c.GetParam(constant.InterfaceKey, strings.TrimPrefix(c.Path, "/"))
   466  	if intf == "" {
   467  		return ""
   468  	}
   469  	var buf strings.Builder
   470  	buf.WriteString(intf)
   471  	buf.WriteString(":")
   472  	version := c.GetParam(constant.VersionKey, "")
   473  	if version != "" && version != "0.0.0" {
   474  		buf.WriteString(version)
   475  	}
   476  	group := c.GetParam(constant.GroupKey, "")
   477  	buf.WriteString(":")
   478  	if group != "" {
   479  		buf.WriteString(group)
   480  	}
   481  	return buf.String()
   482  }
   483  
   484  // EncodedServiceKey encode the service key
   485  func (c *URL) EncodedServiceKey() string {
   486  	serviceKey := c.ServiceKey()
   487  	return strings.Replace(serviceKey, "/", "*", 1)
   488  }
   489  
   490  // Service gets service
   491  func (c *URL) Service() string {
   492  	service := c.GetParam(constant.InterfaceKey, strings.TrimPrefix(c.Path, "/"))
   493  	if service != "" {
   494  		return service
   495  	} else if c.SubURL != nil {
   496  		service = c.SubURL.GetParam(constant.InterfaceKey, strings.TrimPrefix(c.Path, "/"))
   497  		if service != "" { // if URL.path is "" then return suburl's path, special for registry URL
   498  			return service
   499  		}
   500  	}
   501  	return ""
   502  }
   503  
   504  // AddParam will add the key-value pair
   505  func (c *URL) AddParam(key string, value string) {
   506  	c.paramsLock.Lock()
   507  	defer c.paramsLock.Unlock()
   508  	if c.params == nil {
   509  		c.params = url.Values{}
   510  	}
   511  	c.params.Add(key, value)
   512  }
   513  
   514  // AddParamAvoidNil will add key-value pair
   515  func (c *URL) AddParamAvoidNil(key string, value string) {
   516  	c.paramsLock.Lock()
   517  	defer c.paramsLock.Unlock()
   518  	if c.params == nil {
   519  		c.params = url.Values{}
   520  	}
   521  	c.params.Add(key, value)
   522  }
   523  
   524  // SetParam will put the key-value pair into URL
   525  // usually it should only be invoked when you want to initialized an URL
   526  func (c *URL) SetParam(key string, value string) {
   527  	c.paramsLock.Lock()
   528  	defer c.paramsLock.Unlock()
   529  	if c.params == nil {
   530  		c.params = url.Values{}
   531  	}
   532  	c.params.Set(key, value)
   533  }
   534  
   535  // DelParam will delete the given key from the URL
   536  func (c *URL) DelParam(key string) {
   537  	c.paramsLock.Lock()
   538  	defer c.paramsLock.Unlock()
   539  	if c.params != nil {
   540  		c.params.Del(key)
   541  	}
   542  }
   543  
   544  // ReplaceParams will replace the URL.params
   545  // usually it should only be invoked when you want to modify an URL, such as MergeURL
   546  func (c *URL) ReplaceParams(param url.Values) {
   547  	c.paramsLock.Lock()
   548  	defer c.paramsLock.Unlock()
   549  	c.params = param
   550  }
   551  
   552  // RangeParams will iterate the params
   553  func (c *URL) RangeParams(f func(key, value string) bool) {
   554  	c.paramsLock.RLock()
   555  	defer c.paramsLock.RUnlock()
   556  	for k, v := range c.params {
   557  		if !f(k, v[0]) {
   558  			break
   559  		}
   560  	}
   561  }
   562  
   563  // GetParam gets value by key
   564  func (c *URL) GetParam(s string, d string) string {
   565  	c.paramsLock.RLock()
   566  	defer c.paramsLock.RUnlock()
   567  
   568  	var r string
   569  	if len(c.params) > 0 {
   570  		r = c.params.Get(s)
   571  	}
   572  	if len(r) == 0 {
   573  		r = d
   574  	}
   575  
   576  	return r
   577  }
   578  
   579  // GetNonDefaultParam gets value by key, return nil,false if no value found mapping to the key
   580  func (c *URL) GetNonDefaultParam(s string) (string, bool) {
   581  	c.paramsLock.RLock()
   582  	defer c.paramsLock.RUnlock()
   583  
   584  	var r string
   585  	if len(c.params) > 0 {
   586  		r = c.params.Get(s)
   587  	}
   588  
   589  	return r, r != ""
   590  }
   591  
   592  // GetParams gets values
   593  func (c *URL) GetParams() url.Values {
   594  	return c.params
   595  }
   596  
   597  // GetParamAndDecoded gets values and decode
   598  func (c *URL) GetParamAndDecoded(key string) (string, error) {
   599  	ruleDec, err := base64.URLEncoding.DecodeString(c.GetParam(key, ""))
   600  	value := string(ruleDec)
   601  	return value, err
   602  }
   603  
   604  // GetRawParam gets raw param
   605  func (c *URL) GetRawParam(key string) string {
   606  	switch key {
   607  	case PROTOCOL:
   608  		return c.Protocol
   609  	case "username":
   610  		return c.Username
   611  	case "host":
   612  		return strings.Split(c.Location, ":")[0]
   613  	case "password":
   614  		return c.Password
   615  	case "port":
   616  		return c.Port
   617  	case "path":
   618  		return c.Path
   619  	default:
   620  		return c.GetParam(key, "")
   621  	}
   622  }
   623  
   624  // GetParamBool judge whether @key exists or not
   625  func (c *URL) GetParamBool(key string, d bool) bool {
   626  	r, err := strconv.ParseBool(c.GetParam(key, ""))
   627  	if err != nil {
   628  		return d
   629  	}
   630  	return r
   631  }
   632  
   633  // GetParamInt gets int64 value by @key
   634  func (c *URL) GetParamInt(key string, d int64) int64 {
   635  	r, err := strconv.ParseInt(c.GetParam(key, ""), 10, 64)
   636  	if err != nil {
   637  		return d
   638  	}
   639  	return r
   640  }
   641  
   642  // GetParamInt32 gets int32 value by @key
   643  func (c *URL) GetParamInt32(key string, d int32) int32 {
   644  	r, err := strconv.ParseInt(c.GetParam(key, ""), 10, 32)
   645  	if err != nil {
   646  		return d
   647  	}
   648  	return int32(r)
   649  }
   650  
   651  // GetParamByIntValue gets int value by @key
   652  func (c *URL) GetParamByIntValue(key string, d int) int {
   653  	r, err := strconv.ParseInt(c.GetParam(key, ""), 10, 0)
   654  	if err != nil {
   655  		return d
   656  	}
   657  	return int(r)
   658  }
   659  
   660  // GetMethodParamInt gets int method param
   661  func (c *URL) GetMethodParamInt(method string, key string, d int64) int64 {
   662  	r, err := strconv.ParseInt(c.GetParam("methods."+method+"."+key, ""), 10, 64)
   663  	if err != nil {
   664  		return d
   665  	}
   666  	return r
   667  }
   668  
   669  // GetMethodParamIntValue gets int method param
   670  func (c *URL) GetMethodParamIntValue(method string, key string, d int) int {
   671  	r, err := strconv.ParseInt(c.GetParam("methods."+method+"."+key, ""), 10, 0)
   672  	if err != nil {
   673  		return d
   674  	}
   675  	return int(r)
   676  }
   677  
   678  // GetMethodParamInt64 gets int64 method param
   679  func (c *URL) GetMethodParamInt64(method string, key string, d int64) int64 {
   680  	r := c.GetMethodParamInt(method, key, math.MinInt64)
   681  	if r == math.MinInt64 {
   682  		return c.GetParamInt(key, d)
   683  	}
   684  	return r
   685  }
   686  
   687  // GetMethodParam gets method param
   688  func (c *URL) GetMethodParam(method string, key string, d string) string {
   689  	r := c.GetParam("methods."+method+"."+key, "")
   690  	if r == "" {
   691  		r = d
   692  	}
   693  	return r
   694  }
   695  
   696  // GetMethodParamBool judge whether @method param exists or not
   697  func (c *URL) GetMethodParamBool(method string, key string, d bool) bool {
   698  	r := c.GetParamBool("methods."+method+"."+key, d)
   699  	return r
   700  }
   701  
   702  // SetParams will put all key-value pair into URL.
   703  // 1. if there already has same key, the value will be override
   704  // 2. it's not thread safe
   705  // 3. think twice when you want to invoke this method
   706  func (c *URL) SetParams(m url.Values) {
   707  	for k := range m {
   708  		c.SetParam(k, m.Get(k))
   709  	}
   710  }
   711  
   712  // ToMap transfer URL to Map
   713  func (c *URL) ToMap() map[string]string {
   714  	paramsMap := make(map[string]string)
   715  
   716  	c.RangeParams(func(key, value string) bool {
   717  		paramsMap[key] = value
   718  		return true
   719  	})
   720  
   721  	if c.Protocol != "" {
   722  		paramsMap[PROTOCOL] = c.Protocol
   723  	}
   724  	if c.Username != "" {
   725  		paramsMap["username"] = c.Username
   726  	}
   727  	if c.Password != "" {
   728  		paramsMap["password"] = c.Password
   729  	}
   730  	if c.Location != "" {
   731  		paramsMap["host"] = strings.Split(c.Location, ":")[0]
   732  		var port string
   733  		if strings.Contains(c.Location, ":") {
   734  			port = strings.Split(c.Location, ":")[1]
   735  		} else {
   736  			port = "0"
   737  		}
   738  		paramsMap["port"] = port
   739  	}
   740  	if c.Protocol != "" {
   741  		paramsMap[PROTOCOL] = c.Protocol
   742  	}
   743  	if c.Path != "" {
   744  		paramsMap["path"] = c.Path
   745  	}
   746  	if len(paramsMap) == 0 {
   747  		return nil
   748  	}
   749  	return paramsMap
   750  }
   751  
   752  // configuration  > reference config >service config
   753  //  in this function we should merge the reference local URL config into the service URL from registry.
   754  // TODO configuration merge, in the future , the configuration center's config should merge too.
   755  
   756  // MergeURL will merge those two URL
   757  // the result is based on serviceURL, and the key which si only contained in referenceURL
   758  // will be added into result.
   759  // for example, if serviceURL contains params (a1->v1, b1->v2) and referenceURL contains params(a2->v3, b1 -> v4)
   760  // the params of result will be (a1->v1, b1->v2, a2->v3).
   761  // You should notice that the value of b1 is v2, not v4
   762  // except constant.LoadbalanceKey, constant.ClusterKey, constant.RetriesKey, constant.TimeoutKey.
   763  // due to URL is not thread-safe, so this method is not thread-safe
   764  func MergeURL(serviceURL *URL, referenceURL *URL) *URL {
   765  	// After Clone, it is a new URL that there is no thread safe issue.
   766  	mergedURL := serviceURL.Clone()
   767  	params := mergedURL.GetParams()
   768  	// iterator the referenceURL if serviceURL not have the key ,merge in
   769  	// referenceURL usually will not changed. so change RangeParams to GetParams to avoid the string value copy.// Group get group
   770  	for key, value := range referenceURL.GetParams() {
   771  		if _, ok := mergedURL.GetNonDefaultParam(key); !ok {
   772  			if len(value) > 0 {
   773  				params[key] = value
   774  			}
   775  			params[key] = make([]string, len(value))
   776  			copy(params[key], value)
   777  		}
   778  	}
   779  
   780  	// remote timestamp
   781  	if v, ok := serviceURL.GetNonDefaultParam(constant.TimestampKey); !ok {
   782  		params[constant.RemoteTimestampKey] = []string{v}
   783  		params[constant.TimestampKey] = []string{referenceURL.GetParam(constant.TimestampKey, "")}
   784  	}
   785  
   786  	// finally execute methodConfigMergeFcn
   787  	for _, method := range referenceURL.Methods {
   788  		for _, paramKey := range []string{constant.LoadbalanceKey, constant.ClusterKey, constant.RetriesKey, constant.TimeoutKey} {
   789  			if v := referenceURL.GetParam(paramKey, ""); len(v) > 0 {
   790  				params[paramKey] = []string{v}
   791  			}
   792  
   793  			methodsKey := "methods." + method + "." + paramKey
   794  			//if len(mergedURL.GetParam(methodsKey, "")) == 0 {
   795  			if v := referenceURL.GetParam(methodsKey, ""); len(v) > 0 {
   796  				params[methodsKey] = []string{v}
   797  			}
   798  			//}
   799  		}
   800  	}
   801  	// In this way, we will raise some performance.
   802  	mergedURL.ReplaceParams(params)
   803  	return mergedURL
   804  }
   805  
   806  // Clone will copy the URL
   807  func (c *URL) Clone() *URL {
   808  	newURL := &URL{}
   809  	if err := copier.Copy(newURL, c); err != nil {
   810  		// this is impossible
   811  		return newURL
   812  	}
   813  	newURL.params = url.Values{}
   814  	c.RangeParams(func(key, value string) bool {
   815  		newURL.SetParam(key, value)
   816  		return true
   817  	})
   818  	return newURL
   819  }
   820  
   821  func (c *URL) CloneExceptParams(excludeParams *gxset.HashSet) *URL {
   822  	newURL := &URL{}
   823  	if err := copier.Copy(newURL, c); err != nil {
   824  		// this is impossible
   825  		return newURL
   826  	}
   827  	newURL.params = url.Values{}
   828  	c.RangeParams(func(key, value string) bool {
   829  		if !excludeParams.Contains(key) {
   830  			newURL.SetParam(key, value)
   831  		}
   832  		return true
   833  	})
   834  	return newURL
   835  }
   836  
   837  func (c *URL) Compare(comp cm.Comparator) int {
   838  	a := c.String()
   839  	b := comp.(*URL).String()
   840  	switch {
   841  	case a > b:
   842  		return 1
   843  	case a < b:
   844  		return -1
   845  	default:
   846  		return 0
   847  	}
   848  }
   849  
   850  // CloneWithParams Copy URL based on the reserved parameter's keys.
   851  func (c *URL) CloneWithParams(reserveParams []string) *URL {
   852  	params := url.Values{}
   853  	for _, reserveParam := range reserveParams {
   854  		v := c.GetParam(reserveParam, "")
   855  		if len(v) != 0 {
   856  			params.Set(reserveParam, v)
   857  		}
   858  	}
   859  
   860  	return NewURLWithOptions(
   861  		WithProtocol(c.Protocol),
   862  		WithUsername(c.Username),
   863  		WithPassword(c.Password),
   864  		WithIp(c.Ip),
   865  		WithPort(c.Port),
   866  		WithPath(c.Path),
   867  		WithMethods(c.Methods),
   868  		WithParams(params),
   869  	)
   870  }
   871  
   872  // IsEquals compares if two URLs equals with each other. Excludes are all parameter keys which should ignored.
   873  func IsEquals(left *URL, right *URL, excludes ...string) bool {
   874  	if (left == nil && right != nil) || (right == nil && left != nil) {
   875  		return false
   876  	}
   877  	if left.Ip != right.Ip || left.Port != right.Port {
   878  		return false
   879  	}
   880  
   881  	leftMap := left.ToMap()
   882  	rightMap := right.ToMap()
   883  	for _, exclude := range excludes {
   884  		delete(leftMap, exclude)
   885  		delete(rightMap, exclude)
   886  	}
   887  
   888  	if len(leftMap) != len(rightMap) {
   889  		return false
   890  	}
   891  
   892  	for lk, lv := range leftMap {
   893  		if rv, ok := rightMap[lk]; !ok {
   894  			return false
   895  		} else if lv != rv {
   896  			return false
   897  		}
   898  	}
   899  
   900  	return true
   901  }
   902  
   903  // URLSlice will be used to sort URL instance
   904  // Instances will be order by URL.String()
   905  type URLSlice []*URL
   906  
   907  // nolint
   908  func (s URLSlice) Len() int {
   909  	return len(s)
   910  }
   911  
   912  // nolint
   913  func (s URLSlice) Less(i, j int) bool {
   914  	return s[i].String() < s[j].String()
   915  }
   916  
   917  // nolint
   918  func (s URLSlice) Swap(i, j int) {
   919  	s[i], s[j] = s[j], s[i]
   920  }
   921  
   922  type CompareURLEqualFunc func(l *URL, r *URL, excludeParam ...string) bool
   923  
   924  func defaultCompareURLEqual(l *URL, r *URL, excludeParam ...string) bool {
   925  	return IsEquals(l, r, excludeParam...)
   926  }
   927  
   928  func SetCompareURLEqualFunc(f CompareURLEqualFunc) {
   929  	compareURLEqualFunc = f
   930  }
   931  
   932  func GetCompareURLEqualFunc() CompareURLEqualFunc {
   933  	return compareURLEqualFunc
   934  }
   935  
   936  // GetParamDuration get duration if param is invalid or missing will return 3s
   937  func (c *URL) GetParamDuration(s string, d string) time.Duration {
   938  	if t, err := time.ParseDuration(c.GetParam(s, d)); err == nil {
   939  		return t
   940  	}
   941  	return 3 * time.Second
   942  }
   943  
   944  func GetSubscribeName(url *URL) string {
   945  	var buffer bytes.Buffer
   946  
   947  	buffer.Write([]byte(DubboNodes[PROVIDER]))
   948  	appendParam(&buffer, url, constant.InterfaceKey)
   949  	appendParam(&buffer, url, constant.VersionKey)
   950  	appendParam(&buffer, url, constant.GroupKey)
   951  	return buffer.String()
   952  }
   953  
   954  func appendParam(target *bytes.Buffer, url *URL, key string) {
   955  	value := url.GetParam(key, "")
   956  	target.Write([]byte(constant.NacosServiceNameSeparator))
   957  	if strings.TrimSpace(value) != "" {
   958  		target.Write([]byte(value))
   959  	}
   960  }