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×tamp=%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 }