Skip to content
Snippets Groups Projects
url.go 10.2 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"
fangyincheng's avatar
fangyincheng committed
	"fmt"
zonghaishang's avatar
zonghaishang committed
	"math"
fangyincheng's avatar
fangyincheng committed
	"net"
	"net/url"
	"strconv"
	"strings"
)

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 {
vito.he's avatar
vito.he committed
	Location     string // ip+port
	Ip           string
	Port         string
	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
	}
}
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 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
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)
	//
	//timeoutStr := s.Params.Get("timeout")
	//if len(timeoutStr) == 0 {
	//	timeoutStr = s.Params.Get("default.timeout")
	//}
	//if len(timeoutStr) != 0 {
	//	timeout, err := strconv.Atoi(timeoutStr)
	//	if err == nil && timeout != 0 {
	//		s.Timeout = time.Duration(timeout * 1e6) // timeout unit is millisecond
	//	}
	//}
	//fmt.Println(s.String())
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) {
	c.Params.Add(key, value)
}

func (c URL) GetParam(s string, d string) string {
	var r string
	if r = c.Params.Get(s); r == "" {
		r = d
	}
	return r
}

func (c URL) GetRawParameter(key string) string {
	if "protocol" == key {
		return c.Protocol
	}
	if "username" == key {
		return c.Username
	}
	if "host" == key {
		return strings.Split(c.Location, ":")[0]
	}
	if "password" == key {
		return c.Password
	}
	if "port" == key {
		return c.Port
	}
	if "path" == key {
		return c.Path
	}
	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
	}
	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])
		}
	}
	//loadBalance strategy config
	if v := referenceUrl.Params.Get(constant.LOADBALANCE_KEY); v != "" {
		mergedUrl.Params.Set(constant.LOADBALANCE_KEY, v)
	}
	methodConfigMergeFcn = append(methodConfigMergeFcn, func(method string) {
		if v := referenceUrl.Params.Get(method + "." + constant.LOADBALANCE_KEY); v != "" {
			mergedUrl.Params.Set(method+"."+constant.LOADBALANCE_KEY, v)
		}
	})

	//cluster strategy config
	if v := referenceUrl.Params.Get(constant.CLUSTER_KEY); v != "" {
		mergedUrl.Params.Set(constant.CLUSTER_KEY, v)
	}
	methodConfigMergeFcn = append(methodConfigMergeFcn, func(method string) {
		if v := referenceUrl.Params.Get(method + "." + constant.CLUSTER_KEY); v != "" {
			mergedUrl.Params.Set(method+"."+constant.CLUSTER_KEY, v)
		}
	})

	//remote timestamp
	if v := serviceUrl.Params.Get(constant.TIMESTAMP_KEY); v != "" {
		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
}