Skip to content
Permalink
Browse files

Mod: merge conflict resolve

  • Loading branch information...
hxmhlt committed Sep 11, 2019
2 parents 111e58c + e5d1247 commit 69bc3df31e04a4fefc3a2a169c0f0bc37c79fda3
Showing with 4,336 additions and 474 deletions.
  1. +8 −2 .gitignore
  2. +26 −0 CHANGE.md
  3. +0 −1 LICENSE
  4. +8 −4 cluster/cluster_impl/failback_cluster_invoker.go
  5. +14 −4 cluster/cluster_impl/failover_cluster_invoker.go
  6. +4 −4 cluster/cluster_impl/failover_cluster_test.go
  7. +0 −1 cluster/loadbalance/least_active.go
  8. +17 −0 cluster/loadbalance/round_robin_test.go
  9. +9 −7 common/constant/default.go
  10. +17 −0 common/extension/router_factory.go
  11. +92 −2 common/proxy/proxy_factory/default.go
  12. +4 −0 common/url.go
  13. +10 −10 config/base_config_test.go
  14. +1 −0 config/consumer_config.go
  15. +1 −1 config/method_config.go
  16. +1 −0 config/provider_config.go
  17. +3 −3 config/reference_config.go
  18. +4 −5 config/reference_config_test.go
  19. +3 −3 config/service_config.go
  20. +3 −3 config/service_config_test.go
  21. +1 −1 config/testdata/consumer_config.yml
  22. +1 −1 config/testdata/consumer_config_with_configcenter.yml
  23. +18 −0 config_center/configuration_parser.go
  24. +18 −0 config_center/configuration_parser_test.go
  25. +1 −1 examples/configcenter/zookeeper/dubbo/with-configcenter-go-client/app/client.go
  26. +1 −1 examples/configcenter/zookeeper/dubbo/with-configcenter-go-client/profiles/dev/client.yml
  27. +1 −1 examples/configcenter/zookeeper/dubbo/with-configcenter-go-client/profiles/release/client.yml
  28. +1 −1 examples/configcenter/zookeeper/dubbo/with-configcenter-go-client/profiles/test/client.yml
  29. +1 −1 examples/configcenter/zookeeper/dubbo/with-configcenter-go-server/app/server.go
  30. +1 −1 examples/configcenter/zookeeper/dubbo/with-configcenter-go-server/profiles/dev/server.yml
  31. +1 −1 examples/configcenter/zookeeper/dubbo/with-configcenter-go-server/profiles/release/server.yml
  32. +1 −1 examples/configcenter/zookeeper/dubbo/with-configcenter-go-server/profiles/test/server.yml
  33. +1 −1 examples/configcenter/zookeeper/jsonrpc/with-configcenter-go-client/app/client.go
  34. +1 −1 examples/configcenter/zookeeper/jsonrpc/with-configcenter-go-server/app/server.go
  35. +44 −0 examples/consul/README.md
  36. +43 −0 examples/consul/go-client/client.go
  37. +52 −0 examples/consul/go-client/config/client.yml
  38. +28 −0 examples/consul/go-client/config/log.yml
  39. +36 −0 examples/consul/go-client/service.go
  40. +28 −0 examples/consul/go-server/config/log.yml
  41. +52 −0 examples/consul/go-server/config/server.yml
  42. +70 −0 examples/consul/go-server/server.go
  43. +36 −0 examples/consul/go-server/service.go
  44. +7 −0 examples/consul/java-client/.gitignore
  45. +88 −0 examples/consul/java-client/pom.xml
  46. +22 −0 examples/consul/java-client/src/main/java/dubbo/DubboService.java
  47. +43 −0 examples/consul/java-client/src/main/java/dubbo/client/Main.java
  48. +4 −0 examples/consul/java-client/src/main/resources/log4j.properties
  49. +7 −0 examples/consul/java-server/.gitignore
  50. +89 −0 examples/consul/java-server/pom.xml
  51. +5 −0 examples/consul/java-server/src/main/java/dubbo/DubboService.java
  52. +11 −0 examples/consul/java-server/src/main/java/dubbo/server/Impl/DubboServiceImpl.java
  53. +53 −0 examples/consul/java-server/src/main/java/dubbo/server/Main.java
  54. +4 −0 examples/consul/java-server/src/main/resources/log4j.properties
  55. +15 −15 examples/general/dubbo/go-client/app/client.go
  56. +3 −3 examples/general/dubbo/go-client/profiles/dev/client.yml
  57. +3 −3 examples/general/dubbo/go-client/profiles/release/client.yml
  58. +3 −3 examples/general/dubbo/go-client/profiles/test/client.yml
  59. +1 −1 examples/general/dubbo/go-server/app/server.go
  60. +3 −3 examples/general/dubbo/go-server/profiles/dev/server.yml
  61. +3 −3 examples/general/dubbo/go-server/profiles/release/server.yml
  62. +3 −3 examples/general/dubbo/go-server/profiles/test/server.yml
  63. +1 −0 examples/general/dubbo/java-client/src/main/java/com/ikurento/user/UserProvider.java
  64. +2 −0 examples/general/dubbo/java-client/src/main/resources/META-INF/spring/dubbo.consumer.xml
  65. +1 −0 examples/general/dubbo/java-client/src/main/resources/META-INF/spring/service.xml
  66. +0 −1 examples/general/dubbo/java-server/pom.xml
  67. +0 −1 examples/general/dubbo/java-server/src/main/java/com/ikurento/user/UserProviderImpl.java
  68. +1 −1 examples/general/jsonrpc/go-client/app/client.go
  69. +3 −3 examples/general/jsonrpc/go-client/profiles/dev/client.yml
  70. +1 −1 examples/general/jsonrpc/go-server/app/server.go
  71. +3 −3 examples/general/jsonrpc/go-server/profiles/dev/server.yml
  72. +0 −4 examples/generic/go-client/app/client.go
  73. +0 −17 examples/generic/go-client/app/user.go
  74. +0 −1 examples/generic/go-client/assembly/bin/load.sh
  75. +1 −0 examples/generic/go-client/assembly/common/app.properties
  76. +1 −0 examples/generic/go-client/assembly/common/build.sh
  77. +0 −1 examples/generic/go-client/assembly/linux/dev.sh
  78. +0 −1 examples/generic/go-client/assembly/mac/dev.sh
  79. +1 −0 examples/generic/go-client/assembly/mac/test.sh
  80. +0 −1 examples/generic/java-server/script/debug.sh
  81. +1 −0 examples/generic/java-server/src/main/resources/META-INF/spring/dubbo.provider.xml
  82. +0 −1 examples/helloworld/dubbo/go-client/assembly/bin/load.sh
  83. +1 −0 examples/helloworld/dubbo/go-client/assembly/common/app.properties
  84. +1 −0 examples/helloworld/dubbo/go-client/assembly/common/build.sh
  85. +0 −1 examples/helloworld/dubbo/go-client/assembly/linux/release.sh
  86. +0 −1 examples/helloworld/dubbo/go-client/assembly/linux/test.sh
  87. +1 −1 examples/helloworld/dubbo/go-client/profiles/dev/client.yml
  88. +1 −1 examples/helloworld/dubbo/go-server/app/server.go
  89. +0 −1 examples/helloworld/dubbo/go-server/assembly/bin/load.sh
  90. +1 −1 examples/helloworld/dubbo/go-server/profiles/dev/server.yml
  91. +1 −0 examples/helloworld/dubbo/java-server/src/main/assembly/assembly.xml
  92. +1 −0 examples/helloworld/dubbo/java-server/src/main/resources/META-INF/spring/dubbo.provider.xml
  93. +98 −0 examples/hystrixfilter/dubbo/with-hystrix-go-client/app/client.go
  94. +80 −0 examples/hystrixfilter/dubbo/with-hystrix-go-client/app/example_fallback_filter.go
  95. +149 −0 examples/hystrixfilter/dubbo/with-hystrix-go-client/app/user.go
  96. +22 −0 examples/hystrixfilter/dubbo/with-hystrix-go-client/app/version.go
  97. +203 −0 examples/hystrixfilter/dubbo/with-hystrix-go-client/assembly/bin/load.sh
  98. +23 −0 examples/hystrixfilter/dubbo/with-hystrix-go-client/assembly/common/app.properties
  99. +83 −0 examples/hystrixfilter/dubbo/with-hystrix-go-client/assembly/common/build.sh
  100. +36 −0 examples/hystrixfilter/dubbo/with-hystrix-go-client/assembly/linux/dev.sh
  101. +35 −0 examples/hystrixfilter/dubbo/with-hystrix-go-client/assembly/linux/release.sh
  102. +35 −0 examples/hystrixfilter/dubbo/with-hystrix-go-client/assembly/linux/test.sh
  103. +36 −0 examples/hystrixfilter/dubbo/with-hystrix-go-client/assembly/mac/dev.sh
  104. +34 −0 examples/hystrixfilter/dubbo/with-hystrix-go-client/assembly/mac/release.sh
  105. +34 −0 examples/hystrixfilter/dubbo/with-hystrix-go-client/assembly/mac/test.sh
  106. +34 −0 examples/hystrixfilter/dubbo/with-hystrix-go-client/assembly/windows/dev.sh
  107. +34 −0 examples/hystrixfilter/dubbo/with-hystrix-go-client/assembly/windows/release.sh
  108. +34 −0 examples/hystrixfilter/dubbo/with-hystrix-go-client/assembly/windows/test.sh
  109. +98 −0 examples/hystrixfilter/dubbo/with-hystrix-go-client/profiles/dev/client.yml
  110. +28 −0 examples/hystrixfilter/dubbo/with-hystrix-go-client/profiles/dev/log.yml
  111. +98 −0 examples/hystrixfilter/dubbo/with-hystrix-go-client/profiles/release/client.yml
  112. +28 −0 examples/hystrixfilter/dubbo/with-hystrix-go-client/profiles/release/log.yml
  113. +99 −0 examples/hystrixfilter/dubbo/with-hystrix-go-client/profiles/test/client.yml
  114. +28 −0 examples/hystrixfilter/dubbo/with-hystrix-go-client/profiles/test/log.yml
  115. +0 −1 filter/impl/active_filter.go
  116. +2 −1 filter/impl/echo_filter.go
  117. +13 −5 filter/impl/generic_filter.go
  118. +32 −0 filter/impl/generic_filter_test.go
  119. +269 −0 filter/impl/hystrix_filter.go
  120. +217 −0 filter/impl/hystrix_filter_test.go
  121. +5 −4 go.mod
  122. +344 −2 go.sum
  123. +47 −18 protocol/dubbo/client.go
  124. +24 −16 protocol/dubbo/client_test.go
  125. +6 −6 protocol/dubbo/codec.go
  126. +2 −2 protocol/dubbo/codec_test.go
  127. +6 −4 protocol/dubbo/dubbo_invoker.go
  128. +4 −3 protocol/dubbo/dubbo_invoker_test.go
  129. +4 −1 protocol/dubbo/dubbo_protocol.go
  130. +1 −1 protocol/dubbo/dubbo_protocol_test.go
  131. +30 −102 protocol/dubbo/listener.go
  132. +48 −22 protocol/dubbo/pool.go
  133. +9 −9 protocol/dubbo/readwriter.go
  134. +4 −1 protocol/jsonrpc/http_test.go
  135. +4 −1 protocol/jsonrpc/jsonrpc_invoker_test.go
  136. +7 −98 protocol/jsonrpc/server.go
  137. +31 −3 protocol/protocolwrapper/protocol_filter_wrapper_test.go
  138. +0 −1 protocol/{RpcStatus.go → rpc_status.go}
  139. +206 −0 registry/consul/listener.go
  140. +33 −0 registry/consul/listener_test.go
  141. +143 −0 registry/consul/registry.go
  142. +57 −0 registry/consul/registry_test.go
  143. +114 −0 registry/consul/utils.go
  144. +226 −0 registry/consul/utils_test.go
  145. +1 −5 registry/directory/directory.go
  146. +1 −1 registry/nacos/listener.go
  147. +6 −8 registry/protocol/protocol.go
  148. +21 −1 registry/zookeeper/listener_test.go
  149. +1 −1 remoting/etcdv3/listener.go
  150. +1 −1 remoting/listener.go
  151. +16 −18 remoting/zookeeper/listener.go
@@ -4,6 +4,7 @@
*.dll
*.so
*.dylib
*.jar

# Test binary, build with `go test -c`
*.test
@@ -17,10 +18,15 @@ coverage.txt
target/
classes


# Gopkg.lock
# go mod, go test
vendor/
coverage.txt

logs/
.vscode/
coverage.txt

# unit test
remoting/zookeeper/zookeeper-4unittest/
config_center/zookeeper/zookeeper-4unittest/
registry/zookeeper/zookeeper-4unittest/
@@ -0,0 +1,26 @@
# Release Notes

## 1.1.0

### New Features

- Support Java bigdecimal<https://github.com/apache/dubbo-go/pull/126>;
- Support all JDK exceptions<https://github.com/apache/dubbo-go/pull/120>;
- Support multi-version of service<https://github.com/apache/dubbo-go/pull/119>;
- Allow user set custom params for registry<https://github.com/apache/dubbo-go/pull/117>;
- Support zookeeper config center<https://github.com/apache/dubbo-go/pull/99>;
- Failsafe/Failback Cluster Strategy<https://github.com/apache/dubbo-go/pull/136>;

### Enhancement

- Use time wheel instead of time.After to defeat timer object memory leakage<https://github.com/apache/dubbo-go/pull/130> ;

### Bugfixes

- Preventing dead loop when got zookeeper unregister event<https://github.com/apache/dubbo-go/pull/129>;
- Delete ineffassign<https://github.com/apache/dubbo-go/pull/127>;
- Add wg.Done() for mockDataListener<https://github.com/apache/dubbo-go/pull/118>;
- Delete wrong spelling words<https://github.com/apache/dubbo-go/pull/107>;
- Use sync.Map to defeat from gettyClientPool deadlock<https://github.com/apache/dubbo-go/pull/106>;
- Handle panic when function args list is empty<https://github.com/apache/dubbo-go/pull/98>;
- url.Values is not safe map<https://github.com/apache/dubbo-go/pull/172>;
@@ -176,7 +176,6 @@

END OF TERMS AND CONDITIONS


Licensed 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
@@ -18,6 +18,7 @@
package cluster_impl

import (
"strconv"
"sync"
"time"
)
@@ -54,15 +55,18 @@ func newFailbackClusterInvoker(directory cluster.Directory) protocol.Invoker {
invoker := &failbackClusterInvoker{
baseClusterInvoker: newBaseClusterInvoker(directory),
}
retriesConfig := invoker.GetUrl().GetParamInt(constant.RETRIES_KEY, constant.DEFAULT_FAILBACK_TIMES)
if retriesConfig <= 0 {
retriesConfig = constant.DEFAULT_FAILBACK_TIMES
retriesConfig := invoker.GetUrl().GetParam(constant.RETRIES_KEY, constant.DEFAULT_FAILBACK_TIMES)
retries, err := strconv.Atoi(retriesConfig)
if err != nil || retries < 0 {
logger.Error("Your retries config is invalid,pls do a check. And will use the default fail back times configuration instead.")
retries = constant.DEFAULT_FAILBACK_TIMES_INT
}

failbackTasksConfig := invoker.GetUrl().GetParamInt(constant.FAIL_BACK_TASKS_KEY, constant.DEFAULT_FAILBACK_TASKS)
if failbackTasksConfig <= 0 {
failbackTasksConfig = constant.DEFAULT_FAILBACK_TASKS
}
invoker.maxRetries = retriesConfig
invoker.maxRetries = int64(retries)
invoker.failbackTasks = failbackTasksConfig
return invoker
}
@@ -17,13 +17,18 @@

package cluster_impl

import (
"strconv"
)

import (
perrors "github.com/pkg/errors"
)

import (
"github.com/apache/dubbo-go/cluster"
"github.com/apache/dubbo-go/common/constant"
"github.com/apache/dubbo-go/common/logger"
"github.com/apache/dubbo-go/common/utils"
"github.com/apache/dubbo-go/protocol"
)
@@ -53,16 +58,21 @@ func (invoker *failoverClusterInvoker) Invoke(invocation protocol.Invocation) pr
url := invokers[0].GetUrl()

//get reties
retries := url.GetParamInt(constant.RETRIES_KEY, constant.DEFAULT_RETRIES)
retriesConfig := url.GetParam(constant.RETRIES_KEY, constant.DEFAULT_RETRIES)

//Get the service method loadbalance config if have
if v := url.GetMethodParamInt(methodName, constant.RETRIES_KEY, 0); v != 0 {
retries = v
if v := url.GetMethodParam(methodName, constant.RETRIES_KEY, ""); len(v) != 0 {
retriesConfig = v
}
retries, err := strconv.Atoi(retriesConfig)
if err != nil || retries < 0 {
logger.Error("Your retries config is invalid,pls do a check. And will use the default retries configuration instead.")
retries = constant.DEFAULT_RETRIES_INT
}
invoked := []protocol.Invoker{}
providers := []string{}
var result protocol.Result
for i := int64(0); i < retries; i++ {
for i := 0; i <= retries; i++ {
//Reselect before retry to avoid a change of candidate `invokers`.
//NOTE: if `invokers` changed, then `invoked` also lose accuracy.
if i > 0 {
@@ -118,22 +118,22 @@ func normalInvoke(t *testing.T, successCount int, urlParam url.Values, invocatio
}
func Test_FailoverInvokeSuccess(t *testing.T) {
urlParams := url.Values{}
result := normalInvoke(t, 2, urlParams)
result := normalInvoke(t, 3, urlParams)
assert.NoError(t, result.Error())
count = 0
}

func Test_FailoverInvokeFail(t *testing.T) {
urlParams := url.Values{}
result := normalInvoke(t, 3, urlParams)
result := normalInvoke(t, 4, urlParams)
assert.Errorf(t, result.Error(), "error")
count = 0
}

func Test_FailoverInvoke1(t *testing.T) {
urlParams := url.Values{}
urlParams.Set(constant.RETRIES_KEY, "3")
result := normalInvoke(t, 3, urlParams)
result := normalInvoke(t, 4, urlParams)
assert.NoError(t, result.Error())
count = 0
}
@@ -144,7 +144,7 @@ func Test_FailoverInvoke2(t *testing.T) {
urlParams.Set("methods.test."+constant.RETRIES_KEY, "3")

ivc := invocation.NewRPCInvocationWithOptions(invocation.WithMethodName("test"))
result := normalInvoke(t, 3, urlParams, ivc)
result := normalInvoke(t, 4, urlParams, ivc)
assert.NoError(t, result.Error())
count = 0
}
@@ -15,7 +15,6 @@
* limitations under the License.
*/

// @author yiji@apache.org
package loadbalance

import (
@@ -1,3 +1,20 @@
/*
* 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.
*/

package loadbalance

import (
@@ -26,13 +26,15 @@ const (
)

const (
DEFAULT_LOADBALANCE = "random"
DEFAULT_RETRIES = 2
DEFAULT_PROTOCOL = "dubbo"
DEFAULT_REG_TIMEOUT = "10s"
DEFAULT_CLUSTER = "failover"
DEFAULT_FAILBACK_TIMES = 3
DEFAULT_FAILBACK_TASKS = 100
DEFAULT_LOADBALANCE = "random"
DEFAULT_RETRIES = "2"
DEFAULT_RETRIES_INT = 2
DEFAULT_PROTOCOL = "dubbo"
DEFAULT_REG_TIMEOUT = "10s"
DEFAULT_CLUSTER = "failover"
DEFAULT_FAILBACK_TIMES = "3"
DEFAULT_FAILBACK_TIMES_INT = 3
DEFAULT_FAILBACK_TASKS = 100
)

const (
@@ -1,3 +1,20 @@
/*
* 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.
*/

package extension

import (
@@ -17,10 +17,20 @@

package proxy_factory

import (
"reflect"
"strings"
)

import (
perrors "github.com/pkg/errors"
)

import (
"github.com/apache/dubbo-go/common"
"github.com/apache/dubbo-go/common/constant"
"github.com/apache/dubbo-go/common/extension"
"github.com/apache/dubbo-go/common/logger"
"github.com/apache/dubbo-go/common/proxy"
"github.com/apache/dubbo-go/protocol"
)
@@ -51,6 +61,86 @@ func (factory *DefaultProxyFactory) GetProxy(invoker protocol.Invoker, url *comm
return proxy.NewProxy(invoker, nil, attachments)
}
func (factory *DefaultProxyFactory) GetInvoker(url common.URL) protocol.Invoker {
// todo: call service
return protocol.NewBaseInvoker(url)
return &ProxyInvoker{
BaseInvoker: *protocol.NewBaseInvoker(url),
}
}

type ProxyInvoker struct {
protocol.BaseInvoker
}

func (pi *ProxyInvoker) Invoke(invocation protocol.Invocation) protocol.Result {
result := &protocol.RPCResult{}
result.SetAttachments(invocation.Attachments())

url := pi.GetUrl()

methodName := invocation.MethodName()
proto := url.Protocol
path := strings.TrimPrefix(url.Path, "/")
args := invocation.Arguments()

// get service
svc := common.ServiceMap.GetService(proto, path)
if svc == nil {
logger.Errorf("cannot find service [%s] in %s", path, proto)
result.SetError(perrors.Errorf("cannot find service [%s] in %s", path, proto))
return result
}

// get method
method := svc.Method()[methodName]
if method == nil {
logger.Errorf("cannot find method [%s] of service [%s] in %s", methodName, path, proto)
result.SetError(perrors.Errorf("cannot find method [%s] of service [%s] in %s", methodName, path, proto))
return result
}

in := []reflect.Value{svc.Rcvr()}
if method.CtxType() != nil {
in = append(in, method.SuiteContext(nil)) // todo: ctx will be used later.
}

// prepare argv
if (len(method.ArgsType()) == 1 || len(method.ArgsType()) == 2 && method.ReplyType() == nil) && method.ArgsType()[0].String() == "[]interface {}" {
in = append(in, reflect.ValueOf(args))
} else {
for i := 0; i < len(args); i++ {
t := reflect.ValueOf(args[i])
if !t.IsValid() {
at := method.ArgsType()[i]
if at.Kind() == reflect.Ptr {
at = at.Elem()
}
t = reflect.New(at)
}
in = append(in, t)
}
}

// prepare replyv
var replyv reflect.Value
if method.ReplyType() == nil && len(method.ArgsType()) > 0 {
replyv = reflect.New(method.ArgsType()[len(method.ArgsType())-1].Elem())
in = append(in, replyv)
}

returnValues := method.Method().Func.Call(in)

var retErr interface{}
if len(returnValues) == 1 {
retErr = returnValues[0].Interface()
} else {
replyv = returnValues[0]
retErr = returnValues[1].Interface()
}
if retErr != nil {
result.SetError(retErr.(error))
} else {
if replyv.IsValid() && (replyv.Kind() != reflect.Ptr || replyv.Kind() == reflect.Ptr && replyv.Elem().IsValid()) {
result.SetResult(replyv.Interface())
}
}
return result
}
@@ -111,16 +111,19 @@ func WithParams(params url.Values) option {
url.Params = params
}
}

func WithParamsValue(key, val string) option {
return func(url *URL) {
url.Params.Set(key, val)
}
}

func WithProtocol(proto string) option {
return func(url *URL) {
url.Protocol = proto
}
}

func WithIp(ip string) option {
return func(url *URL) {
url.Ip = ip
@@ -144,6 +147,7 @@ func WithLocation(location string) option {
url.Location = location
}
}

func NewURLWithOptions(opts ...option) *URL {
url := &URL{}
for _, opt := range opts {

0 comments on commit 69bc3df

Please sign in to comment.
You can’t perform that action at this time.