Skip to content
Snippets Groups Projects
url.go 10.4 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 (
fangyincheng's avatar
fangyincheng committed
	perrors "github.com/pkg/errors"
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 = [...]string{"consumers", "configurators", "routers", "providers"}
	DubboRole  = [...]string{"consumer", "", "", "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
	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)

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

func WithPassword(pwd string) option {
	return func(url *URL) {
		url.Password = pwd
	}
}

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

func WithParams(params url.Values) option {
	return func(url *URL) {
		url.Params = params
	}
}
func WithParamsValue(key, val string) option {
	return func(url *URL) {
		url.Params.Set(key, val)
	}
}
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
	}
}

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, "/")
func WithLocation(location string) option {
	return func(url *URL) {
		url.Location = location
	}
}
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)
	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 {
fangyincheng's avatar
fangyincheng 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
	}
	return true
}
func (c URL) String() string {
	buildString := fmt.Sprintf(
		"%s://%s:%s@%s:%s%s?",
		c.Protocol, c.Username, c.Password, c.Ip, c.Port, c.Path)
fangyincheng's avatar
fangyincheng committed
	buildString += c.Params.Encode()
	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()
}

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
}

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, "/"))
		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) {
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()
aliiohs's avatar
aliiohs committed
func (c URL) GetParamAndDecoded(key string) (string, error) {
	ruleDec, err := base64.URLEncoding.DecodeString(c.GetParam(key, ""))
	value := string(ruleDec)
	return value, err
}
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:
		return c.Params.Get(key)
aliiohs's avatar
aliiohs committed
// GetParamBool
func (c URL) GetParamBool(s string, d bool) bool {

	var r bool
	var err error
	if r, err = strconv.ParseBool(c.Params.Get(s)); err != nil {
		return d
	}
	return r
}
func (c URL) GetParamInt(s string, d int64) int64 {
	var r int
	var err error
	if r, err = strconv.Atoi(c.Params.Get(s)); r == 0 || err != nil {
		return d
	}
	return int64(r)
}

func (c URL) GetMethodParamInt(method string, key string, d int64) int64 {
	var r int
	var err error
	if r, err = strconv.Atoi(c.Params.Get("methods." + method + "." + key)); r == 0 || err != nil {
		return d
	}
	return int64(r)
}

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
}

func (c URL) GetMethodParam(method string, key string, d string) string {
fangyincheng's avatar
fangyincheng committed
	if r = c.Params.Get("methods." + method + "." + key); r == "" {
aliiohs's avatar
aliiohs committed
// ToMap transfer URL to Map
func (c URL) ToMap() map[string]string {

	paramsMap := make(map[string]string)

	for k, v := range c.Params {
		paramsMap[k] = v[0]
	}
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.
func MergeUrl(serviceUrl URL, referenceUrl *URL) URL {
	mergedUrl := serviceUrl
	var methodConfigMergeFcn = []func(method string){}
	//iterator the referenceUrl if serviceUrl not have the key ,merge in

	for k, v := range referenceUrl.Params {
		if _, ok := mergedUrl.Params[k]; !ok {
			mergedUrl.Params.Set(k, v[0])
		}
	}
vito.he's avatar
vito.he committed
	//loadBalance,cluster,retries strategy config
	mergeNormalParam(mergedUrl, referenceUrl, methodConfigMergeFcn, []string{constant.LOADBALANCE_KEY, constant.CLUSTER_KEY, constant.RETRIES_KEY})
vito.he's avatar
vito.he committed
	if v := serviceUrl.Params.Get(constant.TIMESTAMP_KEY); len(v) > 0 {
		mergedUrl.Params.Set(constant.REMOTE_TIMESTAMP_KEY, v)
		mergedUrl.Params.Set(constant.TIMESTAMP_KEY, referenceUrl.Params.Get(constant.TIMESTAMP_KEY))
	}

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

	return mergedUrl
}
vito.he's avatar
vito.he committed

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

}