diff --git a/CHANGE.md b/CHANGE.md index 9864601a3746031c7c3f31afabc62fb56a48eedb..8b16870ef884a8a1f1c57d28a0a370a05e53a5e1 100644 --- a/CHANGE.md +++ b/CHANGE.md @@ -1,6 +1,34 @@ # Release Notes --- +## 1.4.0 +### New Features + +- [Condition router](https://github.com/apache/dubbo-go/pull/294) +- [Context support](https://github.com/apache/dubbo-go/pull/330) +- [Opentracing & transfer context end to end for jsonrpc protocol](https://github.com/apache/dubbo-go/pull/335) +- [Opentracing & transfer context end to end for dubbo protocol](https://github.com/apache/dubbo-go/pull/344) +- [Nacos config center](https://github.com/apache/dubbo-go/pull/357) +- [Prometheus support](https://github.com/apache/dubbo-go/pull/342) +- [Support sign and auth for request](https://github.com/apache/dubbo-go/pull/323) +- [Healthy instance first router](https://github.com/apache/dubbo-go/pull/389) +- [User can add attachments for dubbo protocol](https://github.com/apache/dubbo-go/pull/398) +- [K8s as registry](https://github.com/apache/dubbo-go/pull/400) +- [Rest protocol](https://github.com/apache/dubbo-go/pull/352) + +### Enhancement + +- [Reduce the scope of lock in zk listener](https://github.com/apache/dubbo-go/pull/346) +- [Trace error of getGettyRpcClient](https://github.com/apache/dubbo-go/pull/384) +- [Refactor to add base_registry](https://github.com/apache/dubbo-go/pull/348) +- [Do not listen to directory event if zkPath ends with providers/ or consumers/](https://github.com/apache/dubbo-go/pull/359) + +### Bugfixes + +- [Handle the panic when invoker was destroyed](https://github.com/apache/dubbo-go/pull/358) +- [HessianCodec failed to check package header length](https://github.com/apache/dubbo-go/pull/381) + + ## 1.3.0 ### New Features diff --git a/README.md b/README.md index 1dde951d350e6ee51f3f2aeeac7bb516b1b999be..c74769827b238f10aca56de7f7794edd6c5cd12f 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ [](https://travis-ci.org/apache/dubbo-go) [](https://codecov.io/gh/apache/dubbo-go) [](https://pkg.go.dev/github.com/apache/dubbo-go?tab=doc) +[](https://goreportcard.com/report/github.com/apache/dubbo-go) + --- Apache Dubbo Go Implementation. @@ -50,16 +52,22 @@ Finished List: * Dubbo * Jsonrpc2.0 * [gRPC](https://github.com/apache/dubbo-go/pull/311) + * [RESTful](https://github.com/apache/dubbo-go/pull/352) + +- Router + * [Conditional router](https://github.com/apache/dubbo-go/pull/294) - Registry * ZooKeeper * [etcd v3](https://github.com/apache/dubbo-go/pull/148) * [nacos](https://github.com/apache/dubbo-go/pull/151) * [consul](https://github.com/apache/dubbo-go/pull/121) + * [k8s](https://github.com/apache/dubbo-go/pull/400) - Dynamic Configure Center & Service Management Configurator * Zookeeper * [apollo](https://github.com/apache/dubbo-go/pull/250) + * [nacos](https://github.com/apache/dubbo-go/pull/357) - Cluster Strategy * Failover @@ -86,6 +94,10 @@ Finished List: - Invoke * [generic invoke](https://github.com/apache/dubbo-go/pull/122) + +- Monitor + * Opentracing API + * Prometheus - Others: * start check @@ -97,9 +109,8 @@ Finished List: Working List: -- Registry: k8s - Metadata Center (dubbo v2.7.x) -- Metrics: Opentracing/Promethus(dubbo v2.7.x) +- Service Discovery (dubbo v2.7.x) You can know more about dubbo-go by its [roadmap](https://github.com/apache/dubbo-go/wiki/Roadmap). diff --git a/README_CN.md b/README_CN.md index ade924e7a9a6206b6e935e084d68679957dd7fcb..d01e5efbdf18c96a501c8561dc99468f54c0c113 100644 --- a/README_CN.md +++ b/README_CN.md @@ -3,6 +3,8 @@ [](https://travis-ci.org/apache/dubbo-go) [](https://codecov.io/gh/apache/dubbo-go) [](https://pkg.go.dev/github.com/apache/dubbo-go?tab=doc) +[](https://goreportcard.com/report/github.com/apache/dubbo-go) + --- Apache Dubbo Go 语言实现 @@ -49,16 +51,22 @@ Apache License, Version 2.0 * Dubbo * Jsonrpc2.0 * [gRPC](https://github.com/apache/dubbo-go/pull/311) - + * [RESTful](https://github.com/apache/dubbo-go/pull/352) + +- 路由器 + * [Conditional router](https://github.com/apache/dubbo-go/pull/294) + - 注册中心 * ZooKeeper * [etcd v3](https://github.com/apache/dubbo-go/pull/148) * [nacos](https://github.com/apache/dubbo-go/pull/151) * [consul](https://github.com/apache/dubbo-go/pull/121) + * [k8s](https://github.com/apache/dubbo-go/pull/400) - 动态配置中心与服务治理配置器 * Zookeeper * [apollo](https://github.com/apache/dubbo-go/pull/250) + * [nacos](https://github.com/apache/dubbo-go/pull/357) - 集群策略 * Failover @@ -84,6 +92,10 @@ Apache License, Version 2.0 - 调用 * [泛化调用](https://github.com/apache/dubbo-go/pull/122) + +- 监控 + * Opentracing API + * Prometheus - 其他功能支持: * 启动时检查 @@ -95,9 +107,8 @@ Apache License, Version 2.0 开发中列表: -- 注册中心: k8s - 元数据中心 (dubbo v2.7.x) -- Metrics: Opentracing/Promethus(dubbo v2.7.x) +- 服务发现 (dubbo v2.7.x) 你可以通过访问 [roadmap](https://github.com/apache/dubbo-go/wiki/Roadmap) 知道更多关于 dubbo-go 的信息。 diff --git a/before_ut.bat b/before_ut.bat index abe7fc250ef44bf01396ae20c4dacc9db3f60ce2..5e1c877af229b2b30bffc8b802cc35b6aab6c80a 100644 --- a/before_ut.bat +++ b/before_ut.bat @@ -14,10 +14,10 @@ :: See the License for the specific language governing permissions and :: limitations under the License. -set zkJarName="zookeeper-3.4.9-fatjar.jar" +set zkJarName=zookeeper-3.4.9-fatjar.jar set remoteJarUrl="https://github.com/dubbogo/resources/raw/master/zookeeper-4unitest/contrib/fatjar/%zkJarName%" -set zkJarPath="remoting/zookeeper/zookeeper-4unittest/contrib/fatjar" -set zkJar="%zkJarPath%/%zkJarName%" +set zkJarPath=remoting/zookeeper/zookeeper-4unittest/contrib/fatjar +set zkJar=%zkJarPath%/%zkJarName% if not exist "%zkJar%" ( md %zkJarPath% diff --git a/common/constant/default.go b/common/constant/default.go index 8ed645e84a724531080eff6efe5fdb0df5479e80..3c889158e460031f06b9401008c80f55200a46e4 100644 --- a/common/constant/default.go +++ b/common/constant/default.go @@ -41,6 +41,8 @@ const ( DEFAULT_FAILBACK_TIMES = "3" DEFAULT_FAILBACK_TIMES_INT = 3 DEFAULT_FAILBACK_TASKS = 100 + DEFAULT_REST_CLIENT = "resty" + DEFAULT_REST_SERVER = "go-restful" ) const ( diff --git a/common/constant/key.go b/common/constant/key.go index c8a03b3be9f0179bb5317640d38abef8d9cc2b3a..07335bed599788b0240b28b096c7d7a395ee9c11 100644 --- a/common/constant/key.go +++ b/common/constant/key.go @@ -174,21 +174,36 @@ const ( ) const ( - CONSUMER_SIGN_FILTER = "sign" - PROVIDER_AUTH_FILTER = "auth" - SERVICE_AUTH_KEY = "auth" - AUTHENTICATOR_KEY = "authenticator" - DEFAULT_AUTHENTICATOR = "accesskeys" - DEFAULT_ACCESS_KEY_STORAGE = "urlstorage" - ACCESS_KEY_STORAGE_KEY = "accessKey.storage" - REQUEST_TIMESTAMP_KEY = "timestamp" - REQUEST_SIGNATURE_KEY = "signature" - AK_KEY = "ak" - SIGNATURE_STRING_FORMAT = "%s#%s#%s#%s" + // name of consumer sign filter + CONSUMER_SIGN_FILTER = "sign" + // name of consumer sign filter + PROVIDER_AUTH_FILTER = "auth" + // name of service filter + SERVICE_AUTH_KEY = "auth" + // key of authenticator + AUTHENTICATOR_KEY = "authenticator" + // name of default authenticator + DEFAULT_AUTHENTICATOR = "accesskeys" + // name of default url storage + DEFAULT_ACCESS_KEY_STORAGE = "urlstorage" + // key of storage + ACCESS_KEY_STORAGE_KEY = "accessKey.storage" + // key of request timestamp + REQUEST_TIMESTAMP_KEY = "timestamp" + // key of request signature + REQUEST_SIGNATURE_KEY = "signature" + // AK key + AK_KEY = "ak" + // signature format + SIGNATURE_STRING_FORMAT = "%s#%s#%s#%s" + // key whether enable signature PARAMTER_SIGNATURE_ENABLE_KEY = "param.sign" - CONSUMER = "consumer" - ACCESS_KEY_ID_KEY = "accessKeyId" - SECRET_ACCESS_KEY_KEY = "secretAccessKey" + // consumer + CONSUMER = "consumer" + // key of access key id + ACCESS_KEY_ID_KEY = "accessKeyId" + // key of secret access key + SECRET_ACCESS_KEY_KEY = "secretAccessKey" ) // HealthCheck Router diff --git a/common/constant/time.go b/common/constant/time.go index be1baaca67f474aa92e86e529d03400948ef4612..3bb339229ba6e7ab470cbe2964312bd8cefa022b 100644 --- a/common/constant/time.go +++ b/common/constant/time.go @@ -22,5 +22,7 @@ import ( ) var ( + // The value will be 10^6 + // 1ms = 10^6ns MsToNanoRate = int64(time.Millisecond / time.Nanosecond) ) diff --git a/common/extension/auth.go b/common/extension/auth.go index e57e22f660b6d4dec63f8b4a06c25b05bd5c8d72..a35fc509dae5b77a4e80fdd04171f90f337c668b 100644 --- a/common/extension/auth.go +++ b/common/extension/auth.go @@ -9,10 +9,13 @@ var ( accesskeyStorages = make(map[string]func() filter.AccessKeyStorage) ) +// SetAuthenticator put the fcn into map with name func SetAuthenticator(name string, fcn func() filter.Authenticator) { authenticators[name] = fcn } +// GetAuthenticator find the Authenticator with name +// if not found, it will panic func GetAuthenticator(name string) filter.Authenticator { if authenticators[name] == nil { panic("authenticator for " + name + " is not existing, make sure you have import the package.") @@ -20,10 +23,13 @@ func GetAuthenticator(name string) filter.Authenticator { return authenticators[name]() } +// SetAccesskeyStorages will set the fcn into map with this name func SetAccesskeyStorages(name string, fcn func() filter.AccessKeyStorage) { accesskeyStorages[name] = fcn } +// GetAccesskeyStorages find the storage with the name. +// If not found, it will panic. func GetAccesskeyStorages(name string) filter.AccessKeyStorage { if accesskeyStorages[name] == nil { panic("accesskeyStorages for " + name + " is not existing, make sure you have import the package.") diff --git a/common/extension/config_reader.go b/common/extension/config_reader.go new file mode 100644 index 0000000000000000000000000000000000000000..aced5b0281ff9313461425e5ec6d70d562c6c947 --- /dev/null +++ b/common/extension/config_reader.go @@ -0,0 +1,50 @@ +/* + * 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 ( + "github.com/apache/dubbo-go/config/interfaces" +) + +var ( + configReaders = make(map[string]func() interfaces.ConfigReader) + defaults = make(map[string]string) +) + +// SetConfigReaders set a creator of config reader. +func SetConfigReaders(name string, v func() interfaces.ConfigReader) { + configReaders[name] = v +} + +// GetConfigReaders get a config reader by name. +func GetConfigReaders(name string) interfaces.ConfigReader { + if configReaders[name] == nil { + panic("config reader for " + name + " is not existing, make sure you have imported the package.") + } + return configReaders[name]() +} + +// SetDefaultConfigReader set {name} to default config reader for {module} +func SetDefaultConfigReader(module, name string) { + defaults[module] = name +} + +// GetDefaultConfigReader +func GetDefaultConfigReader() map[string]string { + return defaults +} diff --git a/common/extension/rest_client.go b/common/extension/rest_client.go new file mode 100644 index 0000000000000000000000000000000000000000..514d1fdfd2efb5c291fdb47df4dd69da26fa90b1 --- /dev/null +++ b/common/extension/rest_client.go @@ -0,0 +1,37 @@ +/* + * 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 ( + "github.com/apache/dubbo-go/protocol/rest/client" +) + +var ( + restClients = make(map[string]func(restOptions *client.RestOptions) client.RestClient, 8) +) + +func SetRestClient(name string, fun func(restOptions *client.RestOptions) client.RestClient) { + restClients[name] = fun +} + +func GetNewRestClient(name string, restOptions *client.RestOptions) client.RestClient { + if restClients[name] == nil { + panic("restClient for " + name + " is not existing, make sure you have import the package.") + } + return restClients[name](restOptions) +} diff --git a/common/extension/rest_server.go b/common/extension/rest_server.go new file mode 100644 index 0000000000000000000000000000000000000000..fa8d435a5c976a4c95b036810fa2916a327a73b9 --- /dev/null +++ b/common/extension/rest_server.go @@ -0,0 +1,37 @@ +/* + * 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 ( + "github.com/apache/dubbo-go/protocol/rest/server" +) + +var ( + restServers = make(map[string]func() server.RestServer, 8) +) + +func SetRestServer(name string, fun func() server.RestServer) { + restServers[name] = fun +} + +func GetNewRestServer(name string) server.RestServer { + if restServers[name] == nil { + panic("restServer for " + name + " is not existing, make sure you have import the package.") + } + return restServers[name]() +} diff --git a/common/proxy/proxy.go b/common/proxy/proxy.go index 6765a810a5ed48d95f49b5b97fbf660dd8587715..68ba3ff7882837a9419c5e47228461af11fd79ba 100644 --- a/common/proxy/proxy.go +++ b/common/proxy/proxy.go @@ -140,6 +140,14 @@ func (p *Proxy) Implement(v common.RPCService) { inv.SetAttachments(k, value) } + // add user setAttachment + atm := invCtx.Value("attachment") + if m, ok := atm.(map[string]string); ok { + for k, value := range m { + inv.SetAttachments(k, value) + } + } + result := p.invoke.Invoke(invCtx, inv) err = result.Error() diff --git a/common/yaml/testdata/config.yml b/common/yaml/testdata/config.yml new file mode 100644 index 0000000000000000000000000000000000000000..b5c2ca8ad14310f3a173f0bad9ee25cd65e9a3b3 --- /dev/null +++ b/common/yaml/testdata/config.yml @@ -0,0 +1,7 @@ + +intTest: 11 +booleanTest: false +strTest: "strTest" + +child: + strTest: "childStrTest" \ No newline at end of file diff --git a/common/yaml/yaml.go b/common/yaml/yaml.go new file mode 100644 index 0000000000000000000000000000000000000000..7c31d71c35fff547d2ed0a765e8245717148a451 --- /dev/null +++ b/common/yaml/yaml.go @@ -0,0 +1,50 @@ +/* + * 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 yaml + +import ( + "io/ioutil" + "path" +) + +import ( + perrors "github.com/pkg/errors" + "gopkg.in/yaml.v2" +) + +// loadYMLConfig Load yml config byte from file +func LoadYMLConfig(confProFile string) ([]byte, error) { + if len(confProFile) == 0 { + return nil, perrors.Errorf("application configure(provider) file name is nil") + } + + if path.Ext(confProFile) != ".yml" { + return nil, perrors.Errorf("application configure file name{%v} suffix must be .yml", confProFile) + } + + return ioutil.ReadFile(confProFile) +} + +// unmarshalYMLConfig Load yml config byte from file , then unmarshal to object +func UnmarshalYMLConfig(confProFile string, out interface{}) ([]byte, error) { + confFileStream, err := LoadYMLConfig(confProFile) + if err != nil { + return confFileStream, perrors.Errorf("ioutil.ReadFile(file:%s) = error:%v", confProFile, perrors.WithStack(err)) + } + return confFileStream, yaml.Unmarshal(confFileStream, out) +} diff --git a/common/yaml/yaml_test.go b/common/yaml/yaml_test.go new file mode 100644 index 0000000000000000000000000000000000000000..45eee59048c1c074b9c386e26cc7a2252896e6ea --- /dev/null +++ b/common/yaml/yaml_test.go @@ -0,0 +1,58 @@ +/* + * 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 yaml + +import ( + "path/filepath" + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +func TestUnmarshalYMLConfig(t *testing.T) { + conPath, err := filepath.Abs("./testdata/config.yml") + assert.NoError(t, err) + c := &Config{} + _, err = UnmarshalYMLConfig(conPath, c) + assert.NoError(t, err) + assert.Equal(t, "strTest", c.StrTest) + assert.Equal(t, 11, c.IntTest) + assert.Equal(t, false, c.BooleanTest) + assert.Equal(t, "childStrTest", c.ChildConfig.StrTest) +} + +func TestUnmarshalYMLConfig_Error(t *testing.T) { + c := &Config{} + _, err := UnmarshalYMLConfig("./testdata/config", c) + assert.Error(t, err) + _, err = UnmarshalYMLConfig("", c) + assert.Error(t, err) +} + +type Config struct { + StrTest string `yaml:"strTest" default:"default" json:"strTest,omitempty" property:"strTest"` + IntTest int `default:"109" yaml:"intTest" json:"intTest,omitempty" property:"intTest"` + BooleanTest bool `yaml:"booleanTest" default:"true" json:"booleanTest,omitempty"` + ChildConfig ChildConfig `yaml:"child" json:"child,omitempty"` +} + +type ChildConfig struct { + StrTest string `default:"strTest" default:"default" yaml:"strTest" json:"strTest,omitempty"` +} diff --git a/config/base_config.go b/config/base_config.go index 6d5ec7e2498ba65b2a6833b6c9cefcb3394e60df..93c0ce6a6692193e7ea7b1b9f2f74e9eaed0c858 100644 --- a/config/base_config.go +++ b/config/base_config.go @@ -18,8 +18,7 @@ package config import ( - "io/ioutil" - "path" + "bytes" "reflect" "strconv" "strings" @@ -27,7 +26,6 @@ import ( import ( perrors "github.com/pkg/errors" - "gopkg.in/yaml.v2" ) import ( @@ -50,6 +48,8 @@ type BaseConfig struct { fatherConfig interface{} MetricConfig *MetricConfig `yaml:"metrics" json:"metrics,omitempty"` + + fileStream *bytes.Buffer } // startConfigCenter will start the config center. @@ -364,27 +364,4 @@ func initializeStruct(t reflect.Type, v reflect.Value) { } } - -} - -// loadYMLConfig Load yml config byte from file -func loadYMLConfig(confProFile string) ([]byte, error) { - if len(confProFile) == 0 { - return nil, perrors.Errorf("application configure(provider) file name is nil") - } - - if path.Ext(confProFile) != ".yml" { - return nil, perrors.Errorf("application configure file name{%v} suffix must be .yml", confProFile) - } - - return ioutil.ReadFile(confProFile) -} - -// unmarshalYMLConfig Load yml config byte from file , then unmarshal to object -func unmarshalYMLConfig(confProFile string, out interface{}) error { - confFileStream, err := loadYMLConfig(confProFile) - if err != nil { - return perrors.Errorf("ioutil.ReadFile(file:%s) = error:%v", confProFile, perrors.WithStack(err)) - } - return yaml.Unmarshal(confFileStream, out) } diff --git a/config/base_config_test.go b/config/base_config_test.go index 6973a4a18b5e3a78d9039bc818a5a2a046613783..d16b2420922ece60ef2135729cd47d5aa73a3760 100644 --- a/config/base_config_test.go +++ b/config/base_config_test.go @@ -18,7 +18,6 @@ package config import ( "fmt" - "path/filepath" "reflect" "testing" ) @@ -518,13 +517,3 @@ func Test_initializeStruct(t *testing.T) { return consumerConfig.References != nil }) } - -func TestUnmarshalYMLConfig(t *testing.T) { - conPath, err := filepath.Abs("./testdata/consumer_config_with_configcenter.yml") - assert.NoError(t, err) - c := &ConsumerConfig{} - assert.NoError(t, unmarshalYMLConfig(conPath, c)) - assert.Equal(t, "default", c.ProxyFactory) - assert.Equal(t, "dubbo.properties", c.ConfigCenterConfig.ConfigFile) - assert.Equal(t, "100ms", c.Connect_Timeout) -} diff --git a/config/condition_router_config.go b/config/condition_router_config.go index a95b2d2b1265a4c069abd8cbc682a9474c15f454..87e835108efd2ccac4f829386ec44a3916339f85 100644 --- a/config/condition_router_config.go +++ b/config/condition_router_config.go @@ -25,12 +25,13 @@ import ( "github.com/apache/dubbo-go/cluster/directory" "github.com/apache/dubbo-go/common/extension" "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/common/yaml" ) //RouterInit Load config file to init router config func RouterInit(confRouterFile string) error { fileRouterFactories := extension.GetFileRouterFactories() - bytes, err := loadYMLConfig(confRouterFile) + bytes, err := yaml.LoadYMLConfig(confRouterFile) if err != nil { return perrors.Errorf("ioutil.ReadFile(file:%s) = error:%v", confRouterFile, perrors.WithStack(err)) } diff --git a/config/config_loader.go b/config/config_loader.go index 437f4d7323e66afcf62808b3c8d6bf51cc5bce88..c0687d8fc162331afc5098e347d4bbba6a1750c6 100644 --- a/config/config_loader.go +++ b/config/config_loader.go @@ -24,9 +24,14 @@ import ( "time" ) +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" ) @@ -78,6 +83,7 @@ func checkApplicationName(config *ApplicationConfig) { // Load Dubbo Init func Load() { + // init router if confRouterFile != "" { if errPro := RouterInit(confRouterFile); errPro != nil { @@ -89,6 +95,18 @@ func Load() { if consumerConfig == nil { logger.Warnf("consumerConfig is nil!") } else { + // init other consumer config + conConfigType := consumerConfig.ConfigType + for key, value := range extension.GetDefaultConfigReader() { + if conConfigType == nil { + if v, ok := conConfigType[key]; ok { + value = v + } + } + if err := extension.GetConfigReaders(value).ReadConsumerConfig(consumerConfig.fileStream); err != nil { + logger.Errorf("ReadConsumerConfig error: %#v for %s", perrors.WithStack(err), value) + } + } metricConfig = consumerConfig.MetricConfig applicationConfig = consumerConfig.ApplicationConfig @@ -150,6 +168,18 @@ func Load() { if providerConfig == nil { logger.Warnf("providerConfig is nil!") } else { + // init other provider config + proConfigType := providerConfig.ConfigType + for key, value := range extension.GetDefaultConfigReader() { + if proConfigType != nil { + if v, ok := proConfigType[key]; ok { + value = v + } + } + if err := extension.GetConfigReaders(value).ReadProviderConfig(providerConfig.fileStream); err != nil { + logger.Errorf("ReadProviderConfig error: %#v for %s", perrors.WithStack(err), value) + } + } // so, you should know that the consumer's config will be override metricConfig = providerConfig.MetricConfig diff --git a/config/consumer_config.go b/config/consumer_config.go index 94da301ce45acedb720120d56dc07bf76c780d7f..1fa68415bfc3c7e622c0b455e9945c926fed4df2 100644 --- a/config/consumer_config.go +++ b/config/consumer_config.go @@ -18,6 +18,7 @@ package config import ( + "bytes" "time" ) @@ -30,6 +31,7 @@ import ( import ( "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/common/yaml" ) ///////////////////////// @@ -57,6 +59,7 @@ type ConsumerConfig struct { ProtocolConf interface{} `yaml:"protocol_conf" json:"protocol_conf,omitempty" property:"protocol_conf"` FilterConf interface{} `yaml:"filter_conf" json:"filter_conf,omitempty" property:"filter_conf" ` ShutdownConfig *ShutdownConfig `yaml:"shutdown_conf" json:"shutdown_conf,omitempty" property:"shutdown_conf" ` + ConfigType map[string]string `yaml:"config_type" json:"config_type,omitempty" property:"config_type"` } // UnmarshalYAML ... @@ -86,13 +89,12 @@ func ConsumerInit(confConFile string) error { if confConFile == "" { return perrors.Errorf("application configure(consumer) file name is nil") } - consumerConfig = &ConsumerConfig{} - err := unmarshalYMLConfig(confConFile, consumerConfig) + fileStream, err := yaml.UnmarshalYMLConfig(confConFile, consumerConfig) if err != nil { - return perrors.Errorf("yaml.Unmarshal() = error:%v", perrors.WithStack(err)) + return perrors.Errorf("unmarshalYmlConfig error %v", perrors.WithStack(err)) } - + consumerConfig.fileStream = bytes.NewBuffer(fileStream) //set method interfaceId & interfaceName for k, v := range consumerConfig.References { //set id for reference @@ -116,6 +118,7 @@ func ConsumerInit(confConFile string) error { } } logger.Debugf("consumer config{%#v}\n", consumerConfig) + return nil } @@ -139,5 +142,6 @@ func configCenterRefreshConsumer() error { return perrors.WithMessagef(err, "time.ParseDuration(Connect_Timeout{%#v})", consumerConfig.Connect_Timeout) } } - return err + + return nil } diff --git a/config/generic_service.go b/config/generic_service.go index 9895486e977a9848e576597f31b724d51d144d4e..b66e399f9e5f467e51c8eccf465f926ac44299d5 100644 --- a/config/generic_service.go +++ b/config/generic_service.go @@ -17,9 +17,11 @@ package config +import "context" + // GenericService ... type GenericService struct { - Invoke func(req []interface{}) (interface{}, error) `dubbo:"$invoke"` + Invoke func(ctx context.Context, req []interface{}) (interface{}, error) `dubbo:"$invoke"` referenceStr string } diff --git a/config/interfaces/config_reader.go b/config/interfaces/config_reader.go new file mode 100644 index 0000000000000000000000000000000000000000..23f2225e1bd670d43065f3b6eca08385a5c964a2 --- /dev/null +++ b/config/interfaces/config_reader.go @@ -0,0 +1,9 @@ +package interfaces + +import "bytes" + +// ConfigReader +type ConfigReader interface { + ReadConsumerConfig(reader *bytes.Buffer) error + ReadProviderConfig(reader *bytes.Buffer) error +} diff --git a/config/provider_config.go b/config/provider_config.go index a36fd4d0a07c3203e53582cbf2f3442d880a3981..14b77cafb3487754b9583d3b4e64ff605394b7db 100644 --- a/config/provider_config.go +++ b/config/provider_config.go @@ -17,6 +17,10 @@ package config +import ( + "bytes" +) + import ( "github.com/creasty/defaults" perrors "github.com/pkg/errors" @@ -25,6 +29,7 @@ import ( import ( "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/common/yaml" ) ///////////////////////// @@ -45,6 +50,7 @@ type ProviderConfig struct { ProtocolConf interface{} `yaml:"protocol_conf" json:"protocol_conf,omitempty" property:"protocol_conf" ` FilterConf interface{} `yaml:"filter_conf" json:"filter_conf,omitempty" property:"filter_conf" ` ShutdownConfig *ShutdownConfig `yaml:"shutdown_conf" json:"shutdown_conf,omitempty" property:"shutdown_conf" ` + ConfigType map[string]string `yaml:"config_type" json:"config_type,omitempty" property:"config_type"` } // UnmarshalYAML ... @@ -74,13 +80,13 @@ func ProviderInit(confProFile string) error { if len(confProFile) == 0 { return perrors.Errorf("application configure(provider) file name is nil") } - providerConfig = &ProviderConfig{} - err := unmarshalYMLConfig(confProFile, providerConfig) + fileStream, err := yaml.UnmarshalYMLConfig(confProFile, providerConfig) if err != nil { - return perrors.Errorf("yaml.Unmarshal() = error:%v", perrors.WithStack(err)) + return perrors.Errorf("unmarshalYmlConfig error %v", perrors.WithStack(err)) } + providerConfig.fileStream = bytes.NewBuffer(fileStream) //set method interfaceId & interfaceName for k, v := range providerConfig.Services { //set id for reference diff --git a/config_center/nacos/client.go b/config_center/nacos/client.go index 1bf61a942ba9f7530b495a57465c4ee6cb0c98c1..d3373e249bf99873dd3aa05b7488b0e7f38730ec 100644 --- a/config_center/nacos/client.go +++ b/config_center/nacos/client.go @@ -157,7 +157,7 @@ func newNacosClient(name string, nacosAddrs []string, timeout time.Duration) (*N }, } - svrConfList := []nacosconst.ServerConfig{} + svrConfList := make([]nacosconst.ServerConfig, 0, len(n.NacosAddrs)) for _, nacosAddr := range n.NacosAddrs { split := strings.Split(nacosAddr, ":") port, err := strconv.ParseUint(split[1], 10, 64) diff --git a/config_center/nacos/facade.go b/config_center/nacos/facade.go index fc83e14eac7fcc51025b54f6daff2553f309312c..77a79ed091461ea5184cb2531d985c233ccd92e9 100644 --- a/config_center/nacos/facade.go +++ b/config_center/nacos/facade.go @@ -46,15 +46,10 @@ type nacosClientFacade interface { common.Node } -func timeSecondDuration(sec int) time.Duration { - return time.Duration(sec) * time.Second -} - // HandleClientRestart Restart client handler func HandleClientRestart(r nacosClientFacade) { var ( - err error - + err error failTimes int ) @@ -79,7 +74,7 @@ LOOP: case <-r.GetDone(): logger.Warnf("(NacosProviderRegistry)reconnectZkRegistry goroutine exit now...") break LOOP - case <-getty.GetTimeWheel().After(timeSecondDuration(failTimes * connDelay)): // Prevent crazy reconnection nacos. + case <-getty.GetTimeWheel().After(time.Duration(failTimes*connDelay) * time.Second): // Prevent crazy reconnection nacos. } err = ValidateNacosClient(r, WithNacosName(nacosName)) logger.Infof("NacosProviderRegistry.validateNacosClient(nacosAddr{%s}) = error{%#v}", diff --git a/config_center/parser/configuration_parser.go b/config_center/parser/configuration_parser.go index f342dc62e765f8d38c9e64ba3be03f3362f0bf61..f33b4ba866da69e1d23b493f42152bbb0f437878 100644 --- a/config_center/parser/configuration_parser.go +++ b/config_center/parser/configuration_parser.go @@ -233,18 +233,10 @@ func getParamString(item ConfigItem) (string, error) { "you want to change in the rule.") } for k, v := range params { - retStr = retStr + "&" - retStr = retStr + k - retStr = retStr + "=" - retStr = retStr + v + retStr += "&" + k + "=" + v } - if len(item.ProviderAddresses) >= 0 { - retStr = retStr + "&" - retStr = retStr + constant.OVERRIDE_PROVIDERS_KEY - retStr = retStr + "=" - retStr = retStr + strings.Join(item.ProviderAddresses, ",") - } + retStr += "&" + constant.OVERRIDE_PROVIDERS_KEY + "=" + strings.Join(item.ProviderAddresses, ",") return retStr, nil } diff --git a/doc/apache/apache-release-procedure-20200306.md b/doc/apache/apache-release-procedure-20200306.md new file mode 100644 index 0000000000000000000000000000000000000000..3f677ff56b8a97f881ae3d1b9bf842754ddb05ab --- /dev/null +++ b/doc/apache/apache-release-procedure-20200306.md @@ -0,0 +1,448 @@ + +# Apache 软件发版流程 + +> author: wongoo@apache.org +> last updated: 2020-03-06 + +Apache开源软件是有社区驱动的,为了提高发布软件质量而指定了软件发布流程,本文主要介绍此流程,以给第一次发布打包的apacher参考。 + +如果你要准备打包一个apache软件了,想必你已经是一个项目的committer了,而且知道社区、PMC这些概念,而你现在还担任本次发布的 release manager 一职。 + +发版流程其实也很简单,无非如下: +1. 整理变更内容,打包并对打包文件签名; +2. 将签名文件上传apache svn仓库; +3. 发邮件请社区PMC大佬投票; +4. 投票通过后发一个投票结果通告邮件; +5. 发版 +6. 发版邮件通告社区新版本发布; + +下面详细整理发版的一些流程步骤,使用 dubbo 的子项目 dubbog-go-hessian2 发版为例! + + +## 1. 发版准备 + +发版文件需要签名,需要安装pgp工具. + +```bash +$ brew install gpg +$ gpg --version +$ gpg --full-gen-key + (1) RSA and RSA (default) <-- RSA 类型 + What keysize do you want? (2048) 4096 <-- key大小为4096 + 0 = key does not expire <-- 永不过期 + Real name: Liu Yang + Email address: wongoo@apache.org + Comment: CODE SIGNING KEY + + gpg: /Users/gelnyang/.gnupg/trustdb.gpg: trustdb created + gpg: key 7DB68550D366E4C0 marked as ultimately trusted + gpg: revocation certificate stored as '/Users/gelnyang/.gnupg/openpgp-revocs.d/1376A2FF67E4C477573909BD7DB68550D366E4C0.rev' + public and secret key created and signed. + + pub rsa4096 2019-10-17 [SC] + 1376A2FF67E4C477573909BD7DB68550D366E4C0 + uid Liu Yang (CODE SIGNING KEY) <wongoo@apache.org> + sub rsa4096 2019-10-17 [E] + +$ gpg --list-keys + gpg: checking the trustdb + gpg: marginals needed: 3 completes needed: 1 trust model: pgp + gpg: depth: 0 valid: 1 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 1u + /Users/gelnyang/.gnupg/pubring.kbx + ---------------------------------- + pub rsa4096 2019-10-17 [SC] + 1376A2FF67E4C477573909BD7DB68550D366E4C0 + uid [ultimate] Liu Yang (CODE SIGNING KEY) <wongoo@apache.org> + sub rsa4096 2019-10-17 [E] + + +# 公钥服务器是网络上专门储存用户公钥的服务器 +# 通过key id发送public key到keyserver +$ gpg --keyserver pgpkeys.mit.edu --send-key 1376A2FF67E4C477573909BD7DB68550D366E4C0 + gpg: sending key 7DB68550D366E4C0 to hkp://pgpkeys.mit.edu +# 其中,pgpkeys.mit.edu为随意挑选的keyserver,keyserver列表为:https://sks-keyservers.net/status/,为相互之间是自动同步的,选任意一个都可以。 + +# 如果有多个public key,设置默认key。修改 ~/.gnupg/gpg.conf +$ vi ~/.gnupg/gpg.conf +default-key 7DB68550D366E4C0 + +# 如果有多个public key, 也可以删除无用的key: +### 先删除私钥,再删除公钥 +$ gpg --yes --delete-secret-keys shenglicao2@gmail.com ###老的私钥,指明邮箱即可 +$ gpg --delete-keys 1808C6444C781C0AEA0AAD4C4D6A8007D20DB8A4 + +## 由于公钥服务器没有检查机制,任何人都可以用你的名义上传公钥,所以没有办法保证服务器上的公钥的可靠性。 +## 通常,你可以在网站上公布一个公钥指纹,让其他人核对下载到的公钥是否为真。 +# fingerprint参数生成公钥指纹: +$ gpg --fingerprint wongoo + + pub rsa4096 2019-10-17 [SC] + 1376 A2FF 67E4 C477 5739 09BD 7DB6 8550 D366 E4C0 + uid [ultimate] Liu Yang (CODE SIGNING KEY) <wongoo@apache.org> + sub rsa4096 2019-10-17 [E] + # 将上面的 fingerprint (即 1376 A2FF 67E4 C477 5739 09BD 7DB6 8550 D366 E4C0)粘贴到自己的用户信息中: + # https://id.apache.org OpenPGP Public Key Primary Fingerprint: +``` + +> 详细参考: +> - 发布签名: http://www.apache.org/dev/release-signing.html +> - 发布策略: http://www.apache.org/dev/release-distribution +> - 将密钥上传到公共密钥服务器: https://www.apache.org/dev/openpgp.html#generate-key + +## 2. 打包签名 + +准备打包前(尤其提第一次打包)需要注意以下内容: +- 每个文件的LICENSE头部是否正确, 包括 `*.java`, `*.go`, `*.xml`, `Makefile` 等 +- LICENSE 文件是否存在 +- NOTICE 文件是否存在 +- CHANGE.md 是否存在 (变更内容格式符合规范) + +以上可以参考其他已发布项目的配置。 + + +``` + +# NOTICE: 这里切分支,分支名称不要和版本号(tag用)类似,不然会有冲突 +$ git checkout -b 1.4 + +$ git tag -a v1.4.0-rc1 -m "v1.4.0 release candidate 1" + +$ git push --tags + +# 打包 +$ git archive --format=tar 1.4 --prefix=dubbo-go-hessian2-v1.4.0/ | gzip > dubbo-go-hessian2-v1.4.0-src.tar.gz + +# 签名 +$ gpg -u wongoo@apache.org --armor --output dubbo-go-hessian2-v1.4.0-src.tar.gz.asc --detach-sign dubbo-go-hessian2-v1.4.0-src.tar.gz + +# 验证签名 +$ gpg --verify dubbo-go-hessian2-v1.4.0-src.tar.gz.asc dubbo-go-hessian2-v1.4.0-src.tar.gz + +# hash +$ shasum -a 512 dubbo-go-hessian2-v1.4.0-src.tar.gz > dubbo-go-hessian2-v1.4.0-src.tar.gz.sha512 + +# 验证 hash +$ shasum --check dubbo-go-hessian2-v1.4.0-src.tar.gz.sha512 + +``` + +> 发布版本: http://www.apache.org/dev/release-publishing.html + +## 3. 上传打包文件到svn仓库 + +``` +$ svn checkout https://dist.apache.org/repos/dist/dev/dubbo + +$ cd dubbo + +# 更新 +$ svn update + +# 添加 签名 和 public key 到KEYS文件并提交到SVN仓库 +# 这里是将公钥KEYS放到根目录, 有的项目放到本次打包文件目录 +$ (gpg --list-sigs wongoo && gpg --armor --export wongoo) >> KEYS + +$ mkdir -p dubbo-go-hessian2/v1.4.0-rc1 + +# 拷贝相关文件到新建目录下 + +$ tree dubbo-go-hessian2 +dubbo-go-hessian2 +└── v1.4.0-rc1 + ├── dubbo-go-hessian2-v1.4.0-src.tar.gz + ├── dubbo-go-hessian2-v1.4.0-src.tar.gz.asc + └── dubbo-go-hessian2-v1.4.0-src.tar.gz.sha512 + +$ svn add dubbo-go-hessian2 +$ svn add dubbo-go-hessian2/* +$ svn status +$ svn commit --username wongoo -m "Release dubbo-go-hessian2 v1.4.0-rc1" +``` + +> 详细参考: svn版本管理 https://www.apache.org/dev/version-control.html + + +## 4. 发投票 [VOTE] 邮件 + +发任何邮件都是有一定格式的,你加入社区邮件列表后,就会收到很多这样的邮件,多看看就知道了,具体邮件范本参考文章后面的邮件范本。 + +发完【VOTE】邮件,私下沟通群里面请大佬PMC投票。 +PMC投票会对你上传打包文件进行相关检查, +详细可以了解孵化中的项目发布完整的检查项参考: https://cwiki.apache.org/confluence/display/INCUBATOR2/IncubatorReleaseChecklist + +收到3个binding邮件且超过72小时后,就可以发 投票结果 [RESULT] [VOTE] 邮件了。 + +> 原则上只有PMC的投票才算binding邮件, 当然也可以由社区决定。 + +这一步骤最常见有以下问题: +- 文件签名有问题 +- 引用项目LICENSE问题 +- 单元测试不通过 + +> 另外需要注意: 一个apache项目可能包含很多子项目,项目的PMC可能只对主项目比较了解, 他们并不清楚如何将子项目跑起来,也不知道如何跑单元测试,最好在邮件中附带一个如何进行单元测试的连接。例如 PMC 最了解 java,但子项目是golang,python,js等,你需要告诉他们如何测试你的项目。 + +可以参考投票规则: https://www.apache.org/foundation/voting.html + +## 5. 发布版本 + +当正式发布投票成功后,先发[Result]邮件,然后就准备 release package。 +将之前在dev下发布的对应rc文件夹下的源码包、签名文件和hash文件拷贝到另一个目录 v1.4.0, +注意文件名字中不要rcxx (可以rename,但不要重新计算签名,hash可以重新计算,结果不会变)。 + +将release包移动到正式版目录。如果你的软件是需要客户从apache下载的,则这一步是必须的。如果不是,比如golang引用github打包地址的则可以忽略。 +``` +svn up +cd dubbo-go-hessian2 +svn move v1.4.0-rc1 v1.4.0 +svn status +svn commit --username wongoo -m "Release dubbo-go-hessian2 v1.4.0" +``` + +移到发版目录后,还需要进行相应的正式版本发布, 这里将具体发布方式整理到单独的章节 `7. 不同语言版本发布`,因为发布流程马上就要结束了 ^v^ + + +## 6. 新版本通告 ANNOUNCE 邮件 + +恭喜你你已经到发版最后一步了,邮件格式参考以下邮件范本! + + +## 7. 不同语言版本发布 + +### 7.1 golang + +在 github 基于投票分支发布了 release 版本。 + +### 7.2 java + +java项目发版需发布到java maven仓库。 + +TODO + +### 7.3 js + +js项目发版需发布到npm仓库。 + +TODO + +### 7.4 python + +TODO + +## 8. 邮件范本 + +### 8.1. 提出发版投票 + +- TO: dev@dubbo.apache.org +- Title: [VOTE] Release Apache dubbo-go-hessian2 v1.4.0 RC1 + +``` +Hello Dubbo/Dubbogo Community, + + This is a call for vote to release Apache dubbo-go-hessian2 version v1.4.0 RC1. + + The release candidates: https://dist.apache.org/repos/dist/dev/dubbo/dubbo-go-hessian2/v1.4.0-rc1/ + Git tag for the release: https://github.com/apache/dubbo-go-hessian2/tree/1.4 + Hash for the release tag: 4c31e88c35afe84c0321d9f12f036e6d3c8962d0 + Release Notes: https://github.com/apache/dubbo-go-hessian2/blob/1.4/CHANGE.md + The artifacts have been signed with Key :7DB68550D366E4C0, which can be found in the keys file: + https://dist.apache.org/repos/dist/dev/dubbo/KEYS + + The vote will be open for at least 72 hours or until necessary number of votes are reached. + + Please vote accordingly: + [ ] +1 approve + [ ] +0 no opinion + [ ] -1 disapprove with the reason + + Thanks, + The Apache Dubbo-go Team + ``` + + +### 8.2. PMC 投票邮件回复 + + +范例1: +``` ++1 approve <-- 首先表明同不同意 + +I have checked: <-- 其次要说明自己检查了哪些项 + +1.source code can build <-- 能否构建 +2.tests can pass in my local <-- 单元测试能否通过 +3. NOTICE LICENSE file exist <-- 协议文件是否存在 +4.git tag is correct <-- git tag 是否正确 + +there is one minor thing that in change logs file, there is no space +between text And link. I suggest add one to make it looks better. <--- 一些其他改进建议 +``` + +范例2: +``` ++1 + +I checked the following items: + +[v] Are release files in correct location? <-- 发布文件目录是否正确 +[v] Do release files have the word incubating in their name? +[v] Are the digital signature and hashes correct? <-- 签名、hash是否正确 +[v] Do LICENSE and NOTICE files exists? +[v] Is the LICENSE and NOTICE text correct? <-- 协议文本是否正确 +[v] Is the NOTICE year correct? <-- 注意年份是否正确 +[v] Un-included software dependencies are not mentioned in LICENSE or NOTICE? <-- 没有包含协议或注意没有提到的软件依赖 +[v] License information is not mentioned in NOTICE? <-- 协议信息没有在注意中提及 +[x] Is there any 3rd party code contained inside the release? If so: <-- 是否包含第三方代码 + [ ] Does the software have a compatible license? + [ ] Are all software licenses mentioned in LICENSE? + [ ] Is the full text of the licenses (or pointers to it) in LICENSE? + Is any of this code Apache licensed? Do they have NOTICE files? If so: + [ ] Have relevant parts of those NOTICE files been added to this NOTICE file? +[v] Do all source files have ASF headers? <-- 是否所有源码都有ASF头部 +[v] Do the contents of the release match with what's tagged in version control? <-- 发布的文件是否和github中tag标记的版本一致 +[x] Are there any unexpected binary files in the release? <-- 是否包含不应该存在的二进制文件 +[v] Can you compile from source? Are the instruction clear? <-- 能否编译?指令是否明确? + +On my mac laptop, I could compile successfully but there's one failed unit +test against net.go. I believe this issue [1] can be fixed with [2] in the +next release. <-- 编译问题及建议 + +Is the issue minor? <-- 编译存在的问题是否都是较小的? +[v] Yes [ ] No [ ] Unsure + +Could it possibly be fixed in the next release? <-- 能否在下一版本修复? +[v] Yes [ ] No [ ] Unsure + +I vote with: <-- 我的投票 +[v] +1 release the software +[ ] +0 not sure if it should be released +[ ] -1 don’t release the software because... + +Regards, +-Ian. + +1. https://github.com/apache/dubbo-go/issues/207 +2. https://github.com/apache/dubbo-go/pull/209 +``` + +范例3: +``` ++1 + +I checked the following items: + +[√] Do LICENSE and NOTICE files exists? +[√] Is the LICENSE and NOTICE text correct? +[√] Is the NOTICE year correct? +[√] Do all source files have ASF headers? +[√] Do the contents of the release match with what's tagged in version control? +[√] Can you compile from source? +I could compile successfully but there's failed units test. I run the unit +test refer to :https://github.com/apache/dubbo-go#running-unit-tests . +But I think it is not matter, the test can be fixed in next release. + + +I vote with: +[√] +1 release the software +``` + +范例4: +``` +Great improvement over the previous release but there are still issues from the last vote that have not been resolved. e.g. [6][7][8] + +Can someone tell me if these files [1][2][3][4][5] are just missing ASF headers or have a different license? + +If they are just missing headers and [6][7][8] explained then it +1 form me, otherwise it’s probably a -1. + +Can people please carefully check the contents, and write down what you checked, rather than just saying +1. + +I checked: +- signatures and hashes good +- LICENSE is missing the appendix (not a major issue) +- LICENSE may be is missing some information[1][2][3][4][5] +- NOTICE is fine +- No binaries in source release +- Some files are missing ASF headers or other license headers [1][2][3][4][5] - please fix + +Thanks, +Justin + +1. dubbo-go-1.1.0/cluster/loadbalance/round_robin_test.go +2. dubbo-go-1.1.0/common/extension/router_factory.go +3. dubbo-go-1.1.0/config_center/configuration_parser.go +4. dubbo-go-1.1.0/config_center/configuration_parser_test.go +5. dubbo-go-1.1.0/registry/zookeeper/listener_test.go +6. dubbo-go-1.1.0/cluster/loadbalance/least_active.go +7. dubbo-go-1.1.0/protocol/RpcStatus.go +8. dubbo-go-1.1.0/filter/impl/active_filter.go +``` + + +### 8.3. 发 [RESULT] [VOTE] 投票结果通知邮件 + +- TO: dev@dubbo.apache.org +- Title: [RESULT] [VOTE]: Release Apache dubbo-go-hessian2 v1.4.0 RC1 + + +``` +Hello Dubbo/Dubbogo Community, + +The release dubbo-go-hessian2 v1.4.0 RC1 vote finished, We’ve received 3 +1 (binding) votes. + ++1 binding, Stocks Alex ++1 binding, Ian Luo ++1 binding, Jun Liu + +The vote and result thread: +https://lists.apache.org/thread.html/r8070f3b00984888069dd4ddad1bbc424cde51ea68b6ff0520e609e18%40%3Cdev.dubbo.apache.org%3E + + +The vote passed. Thanks all. +I will proceed with the formal release later. + + +Best regards, + +The Apache Dubbogo Team +``` + + +### 8.4. 发 Announce 发版邮件 + +- TO: dev@dubbo.apache.org +- [ANNOUNCE] Apache Dubbo version 2.7.4 Released + +``` +Hello Community, + +The Apache Dubbo team is pleased to announce that the 2.7.4 has been +released. + +Apache Dubbo™ is a high-performance, java based, open source +RPC framework. Dubbo offers three key functionalities, which include +interface based remote call, fault tolerance & load balancing, and +automatic service registration & discovery. + +Both the source release[1] and the maven binary release[2] are available +now, you can also find the detailed release notes in here[3]. + + +If you have any usage questions, or have problems when upgrading or find +any problems about enhancements included in this release, please don’t +hesitate to let us know by sending feedback to this mailing list or filing +an issue on GitHub[4]. + + + +[1] http://dubbo.apache.org/en-us/blog/download.html +[2] http://central.maven.org/maven2/org/apache/dubbo +[3] https://github.com/apache/dubbo/releases +[4] https://github.com/apache/dubbo/issues +``` + +## 9. 参考 + +- dubbo发布流程: http://dubbo.apache.org/zh-cn/docs/developers/committer-guide/release-guide_dev.html +- doris发布流程: https://github.com/apache/incubator-doris/blob/master/docs/documentation/cn/community/release-process.md +- spark发布流程: http://spark0apache0org.icopy.site/release-process.html + + diff --git a/doc/apache/release_note.md b/doc/apache/release_note.md new file mode 100644 index 0000000000000000000000000000000000000000..747a3348a1324cc059906fae16cb432b3bc6188d --- /dev/null +++ b/doc/apache/release_note.md @@ -0,0 +1,11 @@ +### How to release a new version? +--- + +* 1 Check the time range of NOTICE; +* 2 Add the features to the feature list of README.md/README_CN.md/CHANGE.md; +* 3 Check whether every code file has the Apache License 2.0 or not; +* 4 There should not be author info(name & email etc) exist in code file; +* 5 Run all unit tests; +* 6 Run all samples in apache/dubbo-samples/golang; +* 7 Write "What's New" by releaser who should be an apache/dubbo-go committer; +* 8 And then, u can release a new version refer to [Apache 软件发版流程](./apache-release-procedure-20200306.md); \ No newline at end of file diff --git a/go.mod b/go.mod index b977df68c3df370280dd47f59d6f4577d01b908c..83091cf8b985d2dec3d1d53967593fc9dee0d17e 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ require ( github.com/Workiva/go-datastructures v1.0.50 github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190802083043-4cd0c391755e // indirect - github.com/apache/dubbo-go-hessian2 v1.3.1-0.20200302092433-6ae5479d93a3 + github.com/apache/dubbo-go-hessian2 v1.4.0 github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23 // indirect github.com/coreos/bbolt v1.3.3 // indirect github.com/coreos/etcd v3.3.13+incompatible @@ -15,8 +15,10 @@ require ( github.com/dubbogo/getty v1.3.3 github.com/dubbogo/go-zookeeper v1.0.0 github.com/dubbogo/gost v1.5.2 + github.com/emicklei/go-restful/v3 v3.0.0 github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239 // indirect github.com/go-errors/errors v1.0.1 // indirect + github.com/go-resty/resty/v2 v2.1.0 github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 // indirect github.com/golang/mock v1.3.1 github.com/golang/protobuf v1.3.2 @@ -24,6 +26,7 @@ require ( github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.9.5 // indirect + github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 github.com/hashicorp/consul v1.5.3 github.com/hashicorp/consul/api v1.1.0 github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 // indirect diff --git a/go.sum b/go.sum index c2be2b1e432ee747fd7aa4426c7244bc997dc7cf..813496b6eea7838756e7f62f2b31aa58cd858356 100644 --- a/go.sum +++ b/go.sum @@ -38,8 +38,8 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190802083043-4cd0c391755e h1:MSuLXx/mveDbpDNhVrcWTMeV4lbYWKcyO4rH+jAxmX0= github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190802083043-4cd0c391755e/go.mod h1:myCDvQSzCW+wB1WAlocEru4wMGJxy+vlxHdhegi1CDQ= github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= -github.com/apache/dubbo-go-hessian2 v1.3.1-0.20200302092433-6ae5479d93a3 h1:1HM47ILUkLaMxLKUub+WHPncqrJGEQ0KRJzSJueMDpY= -github.com/apache/dubbo-go-hessian2 v1.3.1-0.20200302092433-6ae5479d93a3/go.mod h1:VwEnsOMidkM1usya2uPfGpSLO9XUF//WQcWn3y+jFz8= +github.com/apache/dubbo-go-hessian2 v1.4.0 h1:Cb9FQVTy3G93dnDr7P93U8DeKFYpDTJjQp44JG5TafA= +github.com/apache/dubbo-go-hessian2 v1.4.0/go.mod h1:VwEnsOMidkM1usya2uPfGpSLO9XUF//WQcWn3y+jFz8= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e h1:QEF07wC0T1rKkctt1RINW/+RMTVmiwxETico2l3gxJA= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -110,7 +110,6 @@ github.com/dubbogo/getty v1.3.3 h1:8m4zZBqFHO+NmhH7rMPlFuuYRVjcPD7cUhumevqMZZs= github.com/dubbogo/getty v1.3.3/go.mod h1:U92BDyJ6sW9Jpohr2Vlz8w2uUbIbNZ3d+6rJvFTSPp0= github.com/dubbogo/go-zookeeper v1.0.0 h1:RsYdlGwhDW+iKXM3eIIcvt34P2swLdmQfuIJxsHlGoM= github.com/dubbogo/go-zookeeper v1.0.0/go.mod h1:fn6n2CAEer3novYgk9ULLwAjuV8/g4DdC2ENwRb6E+c= -github.com/dubbogo/gost v1.5.1 h1:oG5dzaWf1KYynBaBoUIOkgT+YD0niHV6xxI0Odq7hDg= github.com/dubbogo/gost v1.5.1/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8= github.com/dubbogo/gost v1.5.2 h1:ri/03971hdpnn3QeCU+4UZgnRNGDXLDGDucR/iozZm8= github.com/dubbogo/gost v1.5.2/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8= @@ -118,7 +117,10 @@ github.com/duosecurity/duo_api_golang v0.0.0-20190308151101-6c680f768e74 h1:2MIh github.com/duosecurity/duo_api_golang v0.0.0-20190308151101-6c680f768e74/go.mod h1:UqXY1lYT/ERa4OEAywUqdok1T4RCRdArkhic1Opuavo= github.com/elazarl/go-bindata-assetfs v0.0.0-20160803192304-e1a2a7ec64b0 h1:ZoRgc53qJCfSLimXqJDrmBhnt5GChDsExMCK7t48o0Y= github.com/elazarl/go-bindata-assetfs v0.0.0-20160803192304-e1a2a7ec64b0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633 h1:H2pdYOb3KQ1/YsqVWoWNLQO+fusocsw354rqGTZtAgw= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful/v3 v3.0.0 h1:Duxxa4x0WIHW3bYEDmoAPNjmy8Rbqn+utcF74dlF/G8= +github.com/emicklei/go-restful/v3 v3.0.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.8.0 h1:uE6Fp4fOcAJdc1wTQXLJ+SYistkbG1dNoi6Zs1+Ybvk= github.com/envoyproxy/go-control-plane v0.8.0/go.mod h1:GSSbY9P1neVhdY7G4wu+IK1rk/dqhiCC/4ExuWJZVuk= github.com/envoyproxy/protoc-gen-validate v0.0.14 h1:YBW6/cKy9prEGRYLnaGa4IDhzxZhRCtKsax8srGKDnM= @@ -149,6 +151,8 @@ github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+ github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= +github.com/go-resty/resty/v2 v2.1.0 h1:Z6IefCpUMfnvItVJaJXWv/pMiiD11So35QgwEELsldE= +github.com/go-resty/resty/v2 v2.1.0/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8= github.com/go-sql-driver/mysql v0.0.0-20180618115901-749ddf1598b4 h1:1LlmVz15APoKz9dnm5j2ePptburJlwEH+/v/pUuoxck= github.com/go-sql-driver/mysql v0.0.0-20180618115901-749ddf1598b4/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -212,6 +216,8 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92Bcuy github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.5 h1:UImYN5qQ8tuGpGE16ZmjvcTtTw24zw1QAp/SlnNrZhI= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU= +github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= github.com/hashicorp/consul v1.5.3 h1:EmTWRf/cuqZk6Ug9tgFUVE9xNgJPpmBvJwJMvm+agSk= @@ -453,7 +459,6 @@ github.com/smartystreets/assertions v0.0.0-20180820201707-7c9eb446e3cf/go.mod h1 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= -github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a h1:pa8hGb/2YqsZKovtsgrwcDH1RZhVbTKCjLp47XpqCDs= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945 h1:N8Bg45zpk/UcpNGnfJt2y/3lRWASHNTUET8owPYCgYI= github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= @@ -470,9 +475,7 @@ github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= @@ -505,7 +508,6 @@ go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -520,8 +522,9 @@ golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 h1:dfGZHvZk057jK2MCeWus/TowKpJ8y4AmooUzdBSR9GU= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20170807180024-9a379c6b3e95/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -544,7 +547,6 @@ golang.org/x/sys v0.0.0-20190523142557-0e01d883c5c5/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdOCQUEXhbk/P4A9WmJq0= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -558,7 +560,6 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135 h1:5Beo0mZN8dRzgrMMkDp0jc8YXQKx9DiJ2k1dkvGsn5A= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= google.golang.org/api v0.0.0-20180829000535-087779f1d2c9 h1:z1TeLUmxf9ws9KLICfmX+KGXTs+rjm+aGWzfsv7MZ9w= google.golang.org/api v0.0.0-20180829000535-087779f1d2c9/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= @@ -571,7 +572,6 @@ google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZi google.golang.org/grpc v1.22.1 h1:/7cs52RnTJmD43s3uxzlq2U7nqVTd/37viQwMrMNlOM= google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= -gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM= gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= diff --git a/protocol/grpc/client.go b/protocol/grpc/client.go index d35a2c770cd8b9bda805715889791ccf53c562db..6026f0991b926fd38de8aef3774e46b001820edd 100644 --- a/protocol/grpc/client.go +++ b/protocol/grpc/client.go @@ -22,6 +22,8 @@ import ( ) import ( + "github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc" + "github.com/opentracing/opentracing-go" "google.golang.org/grpc" ) @@ -39,7 +41,11 @@ type Client struct { // NewClient ... func NewClient(url common.URL) *Client { - conn, err := grpc.Dial(url.Location, grpc.WithInsecure(), grpc.WithBlock()) + // if global trace instance was set , it means trace function enabled. If not , will return Nooptracer + tracer := opentracing.GlobalTracer() + conn, err := grpc.Dial(url.Location, grpc.WithInsecure(), grpc.WithBlock(), + grpc.WithUnaryInterceptor( + otgrpc.OpenTracingClientInterceptor(tracer, otgrpc.LogPayloads()))) if err != nil { panic(err) } diff --git a/protocol/grpc/server.go b/protocol/grpc/server.go index 19b9db4ac743ceefcf035d399c0bbcdd99f1fa80..cc184bf3cff83e6ed57bc41cba49c184860233dd 100644 --- a/protocol/grpc/server.go +++ b/protocol/grpc/server.go @@ -24,6 +24,8 @@ import ( ) import ( + "github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc" + "github.com/opentracing/opentracing-go" "google.golang.org/grpc" ) @@ -63,7 +65,11 @@ func (s *Server) Start(url common.URL) { if err != nil { panic(err) } - server := grpc.NewServer() + + // if global trace instance was set , then server tracer instance can be get. If not , will return Nooptracer + tracer := opentracing.GlobalTracer() + server := grpc.NewServer( + grpc.UnaryInterceptor(otgrpc.OpenTracingServerInterceptor(tracer))) key := url.GetParam(constant.BEAN_NAME_KEY, "") service := config.GetProviderService(key) diff --git a/protocol/rest/client/client_impl/resty_client.go b/protocol/rest/client/client_impl/resty_client.go new file mode 100644 index 0000000000000000000000000000000000000000..aa6c23137dc68492948b85a85555a5340572ac49 --- /dev/null +++ b/protocol/rest/client/client_impl/resty_client.go @@ -0,0 +1,85 @@ +/* + * 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 client_impl + +import ( + "context" + "net" + "net/http" + "path" + "time" +) + +import ( + "github.com/go-resty/resty/v2" + perrors "github.com/pkg/errors" +) + +import ( + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/protocol/rest/client" +) + +func init() { + extension.SetRestClient(constant.DEFAULT_REST_CLIENT, NewRestyClient) +} + +type RestyClient struct { + client *resty.Client +} + +func NewRestyClient(restOption *client.RestOptions) client.RestClient { + client := resty.New() + client.SetTransport( + &http.Transport{ + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + c, err := net.DialTimeout(network, addr, restOption.ConnectTimeout) + if err != nil { + return nil, err + } + err = c.SetDeadline(time.Now().Add(restOption.RequestTimeout)) + if err != nil { + return nil, err + } + return c, nil + }, + }) + return &RestyClient{ + client: client, + } +} + +func (rc *RestyClient) Do(restRequest *client.RestRequest, res interface{}) error { + r, err := rc.client.R(). + SetHeader("Content-Type", restRequest.Consumes). + SetHeader("Accept", restRequest.Produces). + SetPathParams(restRequest.PathParams). + SetQueryParams(restRequest.QueryParams). + SetHeaders(restRequest.Headers). + SetBody(restRequest.Body). + SetResult(res). + Execute(restRequest.Method, "http://"+path.Join(restRequest.Location, restRequest.Path)) + if err != nil { + return perrors.WithStack(err) + } + if r.IsError() { + return perrors.New(r.String()) + } + return nil +} diff --git a/protocol/rest/client/rest_client.go b/protocol/rest/client/rest_client.go new file mode 100644 index 0000000000000000000000000000000000000000..7d020abc81c2bd44473ed25ffec4b2b657e7bcc0 --- /dev/null +++ b/protocol/rest/client/rest_client.go @@ -0,0 +1,43 @@ +/* + * 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 client + +import ( + "time" +) + +type RestOptions struct { + RequestTimeout time.Duration + ConnectTimeout time.Duration +} + +type RestRequest struct { + Location string + Path string + Produces string + Consumes string + Method string + PathParams map[string]string + QueryParams map[string]string + Body interface{} + Headers map[string]string +} + +type RestClient interface { + Do(request *RestRequest, res interface{}) error +} diff --git a/protocol/rest/config/reader/rest_config_reader.go b/protocol/rest/config/reader/rest_config_reader.go new file mode 100644 index 0000000000000000000000000000000000000000..873af9924b5644158024b22c24aa9eebbf1bf187 --- /dev/null +++ b/protocol/rest/config/reader/rest_config_reader.go @@ -0,0 +1,158 @@ +/* + * 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 reader + +import ( + "bytes" + "strconv" + "strings" +) + +import ( + perrors "github.com/pkg/errors" + "gopkg.in/yaml.v2" +) + +import ( + "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/config/interfaces" + "github.com/apache/dubbo-go/protocol/rest/config" +) + +const REST = "rest" + +func init() { + extension.SetConfigReaders(REST, NewRestConfigReader) + extension.SetDefaultConfigReader(REST, REST) +} + +type RestConfigReader struct { +} + +func NewRestConfigReader() interfaces.ConfigReader { + return &RestConfigReader{} +} + +// ReadConsumerConfig read consumer config for rest protocol +func (cr *RestConfigReader) ReadConsumerConfig(reader *bytes.Buffer) error { + restConsumerConfig := &config.RestConsumerConfig{} + err := yaml.Unmarshal(reader.Bytes(), restConsumerConfig) + if err != nil { + return perrors.Errorf("[Rest Config] unmarshal Consumer error %#v", perrors.WithStack(err)) + } + + restConsumerServiceConfigMap := make(map[string]*config.RestServiceConfig, len(restConsumerConfig.RestServiceConfigsMap)) + for key, rc := range restConsumerConfig.RestServiceConfigsMap { + rc.Client = getNotEmptyStr(rc.Client, restConsumerConfig.Client, constant.DEFAULT_REST_CLIENT) + rc.RestMethodConfigsMap = initMethodConfigMap(rc, restConsumerConfig.Consumes, restConsumerConfig.Produces) + restConsumerServiceConfigMap[strings.TrimPrefix(key, "/")] = rc + } + config.SetRestConsumerServiceConfigMap(restConsumerServiceConfigMap) + return nil +} + +// ReadProviderConfig read provider config for rest protocol +func (cr *RestConfigReader) ReadProviderConfig(reader *bytes.Buffer) error { + restProviderConfig := &config.RestProviderConfig{} + err := yaml.Unmarshal(reader.Bytes(), restProviderConfig) + if err != nil { + return perrors.Errorf("[Rest Config] unmarshal Provider error %#v", perrors.WithStack(err)) + } + restProviderServiceConfigMap := make(map[string]*config.RestServiceConfig, len(restProviderConfig.RestServiceConfigsMap)) + for key, rc := range restProviderConfig.RestServiceConfigsMap { + rc.Server = getNotEmptyStr(rc.Server, restProviderConfig.Server, constant.DEFAULT_REST_SERVER) + rc.RestMethodConfigsMap = initMethodConfigMap(rc, restProviderConfig.Consumes, restProviderConfig.Produces) + restProviderServiceConfigMap[strings.TrimPrefix(key, "/")] = rc + } + config.SetRestProviderServiceConfigMap(restProviderServiceConfigMap) + return nil +} + +// initProviderRestConfig ... +func initMethodConfigMap(rc *config.RestServiceConfig, consumes string, produces string) map[string]*config.RestMethodConfig { + mcm := make(map[string]*config.RestMethodConfig, len(rc.RestMethodConfigs)) + for _, mc := range rc.RestMethodConfigs { + mc.InterfaceName = rc.InterfaceName + mc.Path = rc.Path + mc.Path + mc.Consumes = getNotEmptyStr(mc.Consumes, rc.Consumes, consumes) + mc.Produces = getNotEmptyStr(mc.Produces, rc.Produces, produces) + mc.MethodType = getNotEmptyStr(mc.MethodType, rc.MethodType) + mc = transformMethodConfig(mc) + mcm[mc.MethodName] = mc + } + return mcm +} + +// function will return first not empty string .. +func getNotEmptyStr(args ...string) string { + var r string + for _, t := range args { + if len(t) > 0 { + r = t + break + } + } + return r +} + +// transformMethodConfig +func transformMethodConfig(methodConfig *config.RestMethodConfig) *config.RestMethodConfig { + if len(methodConfig.PathParamsMap) == 0 && len(methodConfig.PathParams) > 0 { + paramsMap, err := parseParamsString2Map(methodConfig.PathParams) + if err != nil { + logger.Warnf("[Rest Config] Path Param parse error:%v", err) + } else { + methodConfig.PathParamsMap = paramsMap + } + } + if len(methodConfig.QueryParamsMap) == 0 && len(methodConfig.QueryParams) > 0 { + paramsMap, err := parseParamsString2Map(methodConfig.QueryParams) + if err != nil { + logger.Warnf("[Rest Config] Argument Param parse error:%v", err) + } else { + methodConfig.QueryParamsMap = paramsMap + } + } + if len(methodConfig.HeadersMap) == 0 && len(methodConfig.Headers) > 0 { + headersMap, err := parseParamsString2Map(methodConfig.Headers) + if err != nil { + logger.Warnf("[Rest Config] Argument Param parse error:%v", err) + } else { + methodConfig.HeadersMap = headersMap + } + } + return methodConfig +} + +// transform a string to a map +// for example: +// string "0:id,1:name" => map [0:id,1:name] +func parseParamsString2Map(params string) (map[int]string, error) { + m := make(map[int]string, 8) + for _, p := range strings.Split(params, ",") { + pa := strings.Split(p, ":") + key, err := strconv.Atoi(pa[0]) + if err != nil { + return nil, err + } + m[key] = pa[1] + } + return m, nil +} diff --git a/protocol/rest/config/reader/rest_config_reader_test.go b/protocol/rest/config/reader/rest_config_reader_test.go new file mode 100644 index 0000000000000000000000000000000000000000..d2dba40b9b85a6cd7772e0fee619720c79e91eb4 --- /dev/null +++ b/protocol/rest/config/reader/rest_config_reader_test.go @@ -0,0 +1,50 @@ +/* + * 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 reader + +import ( + "bytes" + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common/yaml" + "github.com/apache/dubbo-go/protocol/rest/config" +) + +func TestRestConfigReader_ReadConsumerConfig(t *testing.T) { + bs, err := yaml.LoadYMLConfig("./testdata/consumer_config.yml") + assert.NoError(t, err) + configReader := NewRestConfigReader() + err = configReader.ReadConsumerConfig(bytes.NewBuffer(bs)) + assert.NoError(t, err) + assert.NotEmpty(t, config.GetRestConsumerServiceConfigMap()) +} + +func TestRestConfigReader_ReadProviderConfig(t *testing.T) { + bs, err := yaml.LoadYMLConfig("./testdata/provider_config.yml") + assert.NoError(t, err) + configReader := NewRestConfigReader() + err = configReader.ReadProviderConfig(bytes.NewBuffer(bs)) + assert.NoError(t, err) + assert.NotEmpty(t, config.GetRestProviderServiceConfigMap()) +} diff --git a/protocol/rest/config/reader/testdata/consumer_config.yml b/protocol/rest/config/reader/testdata/consumer_config.yml new file mode 100644 index 0000000000000000000000000000000000000000..27d7fdafeff017ef8ee2720cc06d954056f02f05 --- /dev/null +++ b/protocol/rest/config/reader/testdata/consumer_config.yml @@ -0,0 +1,74 @@ +# dubbo client yaml configure file + +filter: "" + +config_type: + rest: "rest" + +# client +request_timeout : "100ms" +# connect timeout +connect_timeout : "100ms" +check: true +rest_server: "resty" +rest_produces: "*/*" +rest_consumes: "*/*" + +# application config +application: + organization : "ikurento.com" + name : "BDTService" + module : "dubbogo user-info client" + version : "0.0.1" + owner : "ZX" + environment : "dev" + +registries : + + "hangzhouzk": + protocol: "zookeeper" + timeout : "3s" + address: "127.0.0.1:2181" + username: "" + password: "" + "shanghaizk": + protocol: "zookeeper" + timeout : "3s" + address: "127.0.0.1:2182" + username: "" + password: "" + +references: + "UserProvider": + registry: "hangzhouzk,shanghaizk" + filter: "" + protocol : "rest" + version: "1.0" + group: "as" + interface : "com.ikurento.user.UserProvider" + url: "dubbo://127.0.0.1:20000/UserProvider" + cluster: "failover" + timeout: "3s" + rest_client: "resty1" + rest_produces: "application/xml" + rest_consumes: "application/xml" + methods : + - name: "GetUser" + retries: "3" + timeout: "5s" + rest_query_params: "1:userid,2:username" + rest_headers: "3:age" + rest_path_params: "4:time,2:name" + rest_body: 0 + rest_produces: "application/xml" + rest_consumes: "application/xml" + + params: + "serviceid": + "soa.com.ikurento.user.UserProvider" + "forks": 5 + +shutdown_conf: + timeout: 60s + step_timeout: 10s + diff --git a/protocol/rest/config/reader/testdata/provider_config.yml b/protocol/rest/config/reader/testdata/provider_config.yml new file mode 100644 index 0000000000000000000000000000000000000000..71d056e7277f1a0420536e282ea31d34dddf3e14 --- /dev/null +++ b/protocol/rest/config/reader/testdata/provider_config.yml @@ -0,0 +1,88 @@ +# dubbo server yaml configure file + +filter: "" + +config_type: + rest: "rest" + +# application config +application: + organization : "ikurento.com" + name : "BDTService" + module : "dubbogo user-info server" + version : "0.0.1" + owner : "ZX" + environment : "dev" + +registries : + "hangzhouzk": + protocol: "zookeeper" + timeout : "3s" + address: "127.0.0.1:2181" + username: "" + password: "" + "shanghaizk": + protocol: "zookeeper" + timeout : "3s" + address: "127.0.0.1:2182" + username: "" + password: "" + +rest_server: "go-restful" +rest_produces: "*/*" +rest_consumes: "*/*" + +services: + "UserProvider": + registry: "hangzhouzk,shanghaizk" + filter: "" + # the name of limiter + tps.limiter: "default" + # the time unit of interval is ms + tps.limit.interval: 60000 + tps.limit.rate: 200 + # the name of strategy + tps.limit.strategy: "slidingWindow" + # the name of RejectedExecutionHandler + tps.limit.rejected.handler: "default" + # the concurrent request limitation of this service + # if the value < 0, it will not be limited. + execute.limit: "200" + # the name of RejectedExecutionHandler + execute.limit.rejected.handler: "default" + protocol : "rest" + # equivalent to interface of dubbo.xml + interface : "com.ikurento.user.UserProvider" + loadbalance: "random" + version: "1.0" + group: "as" + warmup: "100" + cluster: "failover" + rest_server: "go-restful1" + rest_produces: "*/*" + rest_consumes: "*/*" + methods: + - name: "GetUser" + retries: 1 + loadbalance: "random" + # the concurrent request limitation of this method + # if the value < 0, it will not be limited. + execute.limit: "200" + # the name of RejectedExecutionHandler + execute.limit.rejected.handler: "default" + rest_query_params: "1:userid,2:username" + rest_headers: "3:age" + rest_path_params: "4:time,2:name" + rest_body: 0 + rest_produces: "application/xml" + rest_consumes: "application/xml" + +protocols: + "rest": + name: "rest" + ip : "127.0.0.1" + port : 20000 + + + + diff --git a/protocol/rest/config/rest_config.go b/protocol/rest/config/rest_config.go new file mode 100644 index 0000000000000000000000000000000000000000..168ec8ce525fc7fd5d4a30d4f11ba7bf42d1c921 --- /dev/null +++ b/protocol/rest/config/rest_config.go @@ -0,0 +1,153 @@ +/* + * 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 config + +import ( + "github.com/creasty/defaults" +) + +var ( + restConsumerServiceConfigMap map[string]*RestServiceConfig + restProviderServiceConfigMap map[string]*RestServiceConfig +) + +// RestConsumerConfig ... +type RestConsumerConfig struct { + Client string `default:"resty" yaml:"rest_client" json:"rest_client,omitempty" property:"rest_client"` + Produces string `default:"application/json" yaml:"rest_produces" json:"rest_produces,omitempty" property:"rest_produces"` + Consumes string `default:"application/json" yaml:"rest_consumes" json:"rest_consumes,omitempty" property:"rest_consumes"` + RestServiceConfigsMap map[string]*RestServiceConfig `yaml:"references" json:"references,omitempty" property:"references"` +} + +// UnmarshalYAML ... +func (c *RestConsumerConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { + if err := defaults.Set(c); err != nil { + return err + } + type plain RestConsumerConfig + if err := unmarshal((*plain)(c)); err != nil { + return err + } + return nil +} + +// RestProviderConfig ... +type RestProviderConfig struct { + Server string `default:"go-restful" yaml:"rest_server" json:"rest_server,omitempty" property:"rest_server"` + Produces string `default:"*/*" yaml:"rest_produces" json:"rest_produces,omitempty" property:"rest_produces"` + Consumes string `default:"*/*" yaml:"rest_consumes" json:"rest_consumes,omitempty" property:"rest_consumes"` + RestServiceConfigsMap map[string]*RestServiceConfig `yaml:"services" json:"services,omitempty" property:"services"` +} + +// UnmarshalYAML ... +func (c *RestProviderConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { + if err := defaults.Set(c); err != nil { + return err + } + type plain RestProviderConfig + if err := unmarshal((*plain)(c)); err != nil { + return err + } + return nil +} + +// RestServiceConfig ... +type RestServiceConfig struct { + InterfaceName string `required:"true" yaml:"interface" json:"interface,omitempty" property:"interface"` + Url string `yaml:"url" json:"url,omitempty" property:"url"` + Path string `yaml:"rest_path" json:"rest_path,omitempty" property:"rest_path"` + Produces string `yaml:"rest_produces" json:"rest_produces,omitempty" property:"rest_produces"` + Consumes string `yaml:"rest_consumes" json:"rest_consumes,omitempty" property:"rest_consumes"` + MethodType string `yaml:"rest_method" json:"rest_method,omitempty" property:"rest_method"` + Client string `yaml:"rest_client" json:"rest_client,omitempty" property:"rest_client"` + Server string `yaml:"rest_server" json:"rest_server,omitempty" property:"rest_server"` + RestMethodConfigs []*RestMethodConfig `yaml:"methods" json:"methods,omitempty" property:"methods"` + RestMethodConfigsMap map[string]*RestMethodConfig +} + +// UnmarshalYAML ... +func (c *RestServiceConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { + if err := defaults.Set(c); err != nil { + return err + } + type plain RestServiceConfig + if err := unmarshal((*plain)(c)); err != nil { + return err + } + return nil +} + +// RestMethodConfig ... +type RestMethodConfig struct { + InterfaceName string + MethodName string `required:"true" yaml:"name" json:"name,omitempty" property:"name"` + Url string `yaml:"url" json:"url,omitempty" property:"url"` + Path string `yaml:"rest_path" json:"rest_path,omitempty" property:"rest_path"` + Produces string `yaml:"rest_produces" json:"rest_produces,omitempty" property:"rest_produces"` + Consumes string `yaml:"rest_consumes" json:"rest_consumes,omitempty" property:"rest_consumes"` + MethodType string `yaml:"rest_method" json:"rest_method,omitempty" property:"rest_method"` + PathParams string `yaml:"rest_path_params" json:"rest_path_params,omitempty" property:"rest_path_params"` + PathParamsMap map[int]string + QueryParams string `yaml:"rest_query_params" json:"rest_query_params,omitempty" property:"rest_query_params"` + QueryParamsMap map[int]string + Body int `default:"-1" yaml:"rest_body" json:"rest_body,omitempty" property:"rest_body"` + Headers string `yaml:"rest_headers" json:"rest_headers,omitempty" property:"rest_headers"` + HeadersMap map[int]string +} + +// UnmarshalYAML ... +func (c *RestMethodConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { + if err := defaults.Set(c); err != nil { + return err + } + type plain RestMethodConfig + if err := unmarshal((*plain)(c)); err != nil { + return err + } + return nil +} + +// GetRestConsumerServiceConfig ... +func GetRestConsumerServiceConfig(path string) *RestServiceConfig { + return restConsumerServiceConfigMap[path] +} + +// GetRestProviderServiceConfig ... +func GetRestProviderServiceConfig(path string) *RestServiceConfig { + return restProviderServiceConfigMap[path] +} + +// SetRestConsumerServiceConfigMap ... +func SetRestConsumerServiceConfigMap(configMap map[string]*RestServiceConfig) { + restConsumerServiceConfigMap = configMap +} + +// SetRestProviderServiceConfigMap ... +func SetRestProviderServiceConfigMap(configMap map[string]*RestServiceConfig) { + restProviderServiceConfigMap = configMap +} + +// GetRestConsumerServiceConfigMap ... +func GetRestConsumerServiceConfigMap() map[string]*RestServiceConfig { + return restConsumerServiceConfigMap +} + +// GetRestProviderServiceConfigMap ... +func GetRestProviderServiceConfigMap() map[string]*RestServiceConfig { + return restProviderServiceConfigMap +} diff --git a/protocol/rest/rest_exporter.go b/protocol/rest/rest_exporter.go new file mode 100644 index 0000000000000000000000000000000000000000..470d525ad806687e7a732ce5681eb372eb431a63 --- /dev/null +++ b/protocol/rest/rest_exporter.go @@ -0,0 +1,49 @@ +/* + * 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 rest + +import ( + "sync" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/protocol" +) + +type RestExporter struct { + protocol.BaseExporter +} + +func NewRestExporter(key string, invoker protocol.Invoker, exporterMap *sync.Map) *RestExporter { + return &RestExporter{ + BaseExporter: *protocol.NewBaseExporter(key, invoker, exporterMap), + } +} + +func (re *RestExporter) Unexport() { + serviceId := re.GetInvoker().GetUrl().GetParam(constant.BEAN_NAME_KEY, "") + re.BaseExporter.Unexport() + err := common.ServiceMap.UnRegister(REST, serviceId) + if err != nil { + logger.Errorf("[RestExporter.Unexport] error: %v", err) + } + return +} diff --git a/protocol/rest/rest_invoker.go b/protocol/rest/rest_invoker.go new file mode 100644 index 0000000000000000000000000000000000000000..0c82035ac5eb9a52ab188baa971dbdf1b864e970 --- /dev/null +++ b/protocol/rest/rest_invoker.go @@ -0,0 +1,109 @@ +/* + * 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 rest + +import ( + "context" + "fmt" +) + +import ( + perrors "github.com/pkg/errors" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/protocol" + invocation_impl "github.com/apache/dubbo-go/protocol/invocation" + "github.com/apache/dubbo-go/protocol/rest/client" + "github.com/apache/dubbo-go/protocol/rest/config" +) + +type RestInvoker struct { + protocol.BaseInvoker + client client.RestClient + restMethodConfigMap map[string]*config.RestMethodConfig +} + +func NewRestInvoker(url common.URL, client *client.RestClient, restMethodConfig map[string]*config.RestMethodConfig) *RestInvoker { + return &RestInvoker{ + BaseInvoker: *protocol.NewBaseInvoker(url), + client: *client, + restMethodConfigMap: restMethodConfig, + } +} + +func (ri *RestInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { + inv := invocation.(*invocation_impl.RPCInvocation) + methodConfig := ri.restMethodConfigMap[inv.MethodName()] + var ( + result protocol.RPCResult + body interface{} + pathParams map[string]string + queryParams map[string]string + headers map[string]string + err error + ) + if methodConfig == nil { + result.Err = perrors.Errorf("[RestInvoker] Rest methodConfig:%s is nil", inv.MethodName()) + return &result + } + if pathParams, err = restStringMapTransform(methodConfig.PathParamsMap, inv.Arguments()); err != nil { + result.Err = err + return &result + } + if queryParams, err = restStringMapTransform(methodConfig.QueryParamsMap, inv.Arguments()); err != nil { + result.Err = err + return &result + } + if headers, err = restStringMapTransform(methodConfig.HeadersMap, inv.Arguments()); err != nil { + result.Err = err + return &result + } + if len(inv.Arguments()) > methodConfig.Body && methodConfig.Body >= 0 { + body = inv.Arguments()[methodConfig.Body] + } + + req := &client.RestRequest{ + Location: ri.GetUrl().Location, + Produces: methodConfig.Produces, + Consumes: methodConfig.Consumes, + Method: methodConfig.MethodType, + Path: methodConfig.Path, + PathParams: pathParams, + QueryParams: queryParams, + Body: body, + Headers: headers, + } + result.Err = ri.client.Do(req, inv.Reply()) + if result.Err == nil { + result.Rest = inv.Reply() + } + return &result +} + +func restStringMapTransform(paramsMap map[int]string, args []interface{}) (map[string]string, error) { + resMap := make(map[string]string, len(paramsMap)) + for k, v := range paramsMap { + if k >= len(args) || k < 0 { + return nil, perrors.Errorf("[Rest Invoke] Index %v is out of bundle", k) + } + resMap[v] = fmt.Sprint(args[k]) + } + return resMap, nil +} diff --git a/protocol/rest/rest_invoker_test.go b/protocol/rest/rest_invoker_test.go new file mode 100644 index 0000000000000000000000000000000000000000..42a4fbd358955e8bd2d78a80818090baf62fa784 --- /dev/null +++ b/protocol/rest/rest_invoker_test.go @@ -0,0 +1,196 @@ +/* + * 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 rest + +import ( + "context" + "testing" + "time" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/config" + "github.com/apache/dubbo-go/protocol/invocation" + "github.com/apache/dubbo-go/protocol/rest/client" + "github.com/apache/dubbo-go/protocol/rest/client/client_impl" + rest_config "github.com/apache/dubbo-go/protocol/rest/config" +) + +func TestRestInvoker_Invoke(t *testing.T) { + // Refer + proto := GetRestProtocol() + defer proto.Destroy() + url, err := common.NewURL("rest://127.0.0.1:8877/com.ikurento.user.UserProvider?anyhost=true&" + + "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" + + "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" + + "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" + + "side=provider&timeout=3000×tamp=1556509797245") + assert.NoError(t, err) + _, err = common.ServiceMap.Register(url.Protocol, &UserProvider{}) + assert.NoError(t, err) + con := config.ProviderConfig{} + config.SetProviderConfig(con) + configMap := make(map[string]*rest_config.RestServiceConfig) + methodConfigMap := make(map[string]*rest_config.RestMethodConfig) + queryParamsMap := make(map[int]string) + queryParamsMap[1] = "age" + queryParamsMap[2] = "name" + pathParamsMap := make(map[int]string) + pathParamsMap[0] = "userid" + headersMap := make(map[int]string) + headersMap[3] = "Content-Type" + methodConfigMap["GetUserOne"] = &rest_config.RestMethodConfig{ + InterfaceName: "", + MethodName: "GetUserOne", + Path: "/GetUserOne", + Produces: "application/json", + Consumes: "application/json", + MethodType: "POST", + PathParams: "", + PathParamsMap: nil, + QueryParams: "", + QueryParamsMap: nil, + Body: 0, + } + methodConfigMap["GetUserTwo"] = &rest_config.RestMethodConfig{ + InterfaceName: "", + MethodName: "GetUserTwo", + Path: "/GetUserTwo", + Produces: "application/json", + Consumes: "application/json", + MethodType: "POST", + PathParams: "", + PathParamsMap: nil, + QueryParams: "", + QueryParamsMap: nil, + Body: 0, + } + methodConfigMap["GetUserThree"] = &rest_config.RestMethodConfig{ + InterfaceName: "", + MethodName: "GetUserThree", + Path: "/GetUserThree", + Produces: "application/json", + Consumes: "application/json", + MethodType: "POST", + PathParams: "", + PathParamsMap: nil, + QueryParams: "", + QueryParamsMap: nil, + Body: 0, + } + methodConfigMap["GetUserFour"] = &rest_config.RestMethodConfig{ + InterfaceName: "", + MethodName: "GetUserFour", + Path: "/GetUserFour", + Produces: "application/json", + Consumes: "application/json", + MethodType: "POST", + PathParams: "", + PathParamsMap: nil, + QueryParams: "", + QueryParamsMap: nil, + Body: 0, + } + methodConfigMap["GetUserFive"] = &rest_config.RestMethodConfig{ + InterfaceName: "", + MethodName: "GetUserFive", + Path: "/GetUserFive", + Produces: "*/*", + Consumes: "*/*", + MethodType: "GET", + } + methodConfigMap["GetUser"] = &rest_config.RestMethodConfig{ + InterfaceName: "", + MethodName: "GetUser", + Path: "/GetUser/{userid}", + Produces: "application/json", + Consumes: "application/json", + MethodType: "GET", + PathParams: "", + PathParamsMap: pathParamsMap, + QueryParams: "", + QueryParamsMap: queryParamsMap, + Body: -1, + HeadersMap: headersMap, + } + + configMap["com.ikurento.user.UserProvider"] = &rest_config.RestServiceConfig{ + Server: "go-restful", + RestMethodConfigsMap: methodConfigMap, + } + rest_config.SetRestProviderServiceConfigMap(configMap) + proxyFactory := extension.GetProxyFactory("default") + proto.Export(proxyFactory.GetInvoker(url)) + time.Sleep(5 * time.Second) + configMap = make(map[string]*rest_config.RestServiceConfig) + configMap["com.ikurento.user.UserProvider"] = &rest_config.RestServiceConfig{ + RestMethodConfigsMap: methodConfigMap, + } + restClient := client_impl.NewRestyClient(&client.RestOptions{ConnectTimeout: 3 * time.Second, RequestTimeout: 3 * time.Second}) + invoker := NewRestInvoker(url, &restClient, methodConfigMap) + user := &User{} + inv := invocation.NewRPCInvocationWithOptions(invocation.WithMethodName("GetUser"), + invocation.WithArguments([]interface{}{1, int32(23), "username", "application/json"}), invocation.WithReply(user)) + res := invoker.Invoke(context.Background(), inv) + assert.NoError(t, res.Error()) + assert.Equal(t, User{Id: 1, Age: int32(23), Name: "username"}, *res.Result().(*User)) + now := time.Now() + inv = invocation.NewRPCInvocationWithOptions(invocation.WithMethodName("GetUserOne"), + invocation.WithArguments([]interface{}{&User{1, &now, int32(23), "username"}}), invocation.WithReply(user)) + res = invoker.Invoke(context.Background(), inv) + assert.NoError(t, res.Error()) + assert.NotNil(t, res.Result()) + assert.Equal(t, 1, res.Result().(*User).Id) + assert.Equal(t, now.Unix(), res.Result().(*User).Time.Unix()) + assert.Equal(t, int32(23), res.Result().(*User).Age) + assert.Equal(t, "username", res.Result().(*User).Name) + // test 1 + inv = invocation.NewRPCInvocationWithOptions(invocation.WithMethodName("GetUserTwo"), + invocation.WithArguments([]interface{}{&User{1, &now, int32(23), "username"}}), invocation.WithReply(user)) + res = invoker.Invoke(context.Background(), inv) + assert.NoError(t, res.Error()) + assert.NotNil(t, res.Result()) + assert.Equal(t, "username", res.Result().(*User).Name) + // test 2 + inv = invocation.NewRPCInvocationWithOptions(invocation.WithMethodName("GetUserThree"), + invocation.WithArguments([]interface{}{&User{1, &now, int32(23), "username"}}), invocation.WithReply(user)) + res = invoker.Invoke(context.Background(), inv) + assert.NoError(t, res.Error()) + assert.NotNil(t, res.Result()) + assert.Equal(t, "username", res.Result().(*User).Name) + // test 3 + inv = invocation.NewRPCInvocationWithOptions(invocation.WithMethodName("GetUserFour"), + invocation.WithArguments([]interface{}{[]User{User{1, nil, int32(23), "username"}}}), invocation.WithReply(user)) + res = invoker.Invoke(context.Background(), inv) + assert.NoError(t, res.Error()) + assert.NotNil(t, res.Result()) + assert.Equal(t, "username", res.Result().(*User).Name) + // test 4 + inv = invocation.NewRPCInvocationWithOptions(invocation.WithMethodName("GetUserFive"), invocation.WithReply(user)) + res = invoker.Invoke(context.Background(), inv) + assert.Error(t, res.Error(), "test error") + + err = common.ServiceMap.UnRegister(url.Protocol, "com.ikurento.user.UserProvider") + assert.NoError(t, err) +} diff --git a/protocol/rest/rest_protocol.go b/protocol/rest/rest_protocol.go new file mode 100644 index 0000000000000000000000000000000000000000..47ecb6093b4cfa12a1d3397fa45d59b1e173a93a --- /dev/null +++ b/protocol/rest/rest_protocol.go @@ -0,0 +1,156 @@ +/* + * 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 rest + +import ( + "strings" + "sync" + "time" +) + +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/config" + "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/protocol/rest/client" + _ "github.com/apache/dubbo-go/protocol/rest/client/client_impl" + rest_config "github.com/apache/dubbo-go/protocol/rest/config" + _ "github.com/apache/dubbo-go/protocol/rest/config/reader" + "github.com/apache/dubbo-go/protocol/rest/server" + _ "github.com/apache/dubbo-go/protocol/rest/server/server_impl" +) + +var ( + restProtocol *RestProtocol +) + +const REST = "rest" + +func init() { + extension.SetProtocol(REST, GetRestProtocol) +} + +type RestProtocol struct { + protocol.BaseProtocol + serverLock sync.Mutex + serverMap map[string]server.RestServer + clientLock sync.Mutex + clientMap map[client.RestOptions]client.RestClient +} + +func NewRestProtocol() *RestProtocol { + return &RestProtocol{ + BaseProtocol: protocol.NewBaseProtocol(), + serverMap: make(map[string]server.RestServer, 8), + clientMap: make(map[client.RestOptions]client.RestClient, 8), + } +} + +func (rp *RestProtocol) Export(invoker protocol.Invoker) protocol.Exporter { + url := invoker.GetUrl() + serviceKey := url.ServiceKey() + exporter := NewRestExporter(serviceKey, invoker, rp.ExporterMap()) + restServiceConfig := rest_config.GetRestProviderServiceConfig(strings.TrimPrefix(url.Path, "/")) + if restServiceConfig == nil { + logger.Errorf("%s service doesn't has provider config", url.Path) + return nil + } + rp.SetExporterMap(serviceKey, exporter) + restServer := rp.getServer(url, restServiceConfig.Server) + restServer.Deploy(invoker, restServiceConfig.RestMethodConfigsMap) + return exporter +} + +func (rp *RestProtocol) Refer(url common.URL) protocol.Invoker { + // create rest_invoker + var requestTimeout = config.GetConsumerConfig().RequestTimeout + requestTimeoutStr := url.GetParam(constant.TIMEOUT_KEY, config.GetConsumerConfig().Request_Timeout) + connectTimeout := config.GetConsumerConfig().ConnectTimeout + if t, err := time.ParseDuration(requestTimeoutStr); err == nil { + requestTimeout = t + } + restServiceConfig := rest_config.GetRestConsumerServiceConfig(strings.TrimPrefix(url.Path, "/")) + if restServiceConfig == nil { + logger.Errorf("%s service doesn't has consumer config", url.Path) + return nil + } + restOptions := client.RestOptions{RequestTimeout: requestTimeout, ConnectTimeout: connectTimeout} + restClient := rp.getClient(restOptions, restServiceConfig.Client) + invoker := NewRestInvoker(url, &restClient, restServiceConfig.RestMethodConfigsMap) + rp.SetInvokers(invoker) + return invoker +} + +func (rp *RestProtocol) getServer(url common.URL, serverType string) server.RestServer { + restServer, ok := rp.serverMap[url.Location] + if ok { + return restServer + } + _, ok = rp.ExporterMap().Load(url.ServiceKey()) + if !ok { + panic("[RestProtocol]" + url.ServiceKey() + "is not existing") + } + rp.serverLock.Lock() + defer rp.serverLock.Unlock() + restServer, ok = rp.serverMap[url.Location] + if ok { + return restServer + } + restServer = extension.GetNewRestServer(serverType) + restServer.Start(url) + rp.serverMap[url.Location] = restServer + return restServer +} + +func (rp *RestProtocol) getClient(restOptions client.RestOptions, clientType string) client.RestClient { + restClient, ok := rp.clientMap[restOptions] + if ok { + return restClient + } + rp.clientLock.Lock() + defer rp.clientLock.Unlock() + restClient, ok = rp.clientMap[restOptions] + if ok { + return restClient + } + restClient = extension.GetNewRestClient(clientType, &restOptions) + rp.clientMap[restOptions] = restClient + return restClient +} + +func (rp *RestProtocol) Destroy() { + // destroy rest_server + rp.BaseProtocol.Destroy() + for key, server := range rp.serverMap { + server.Destroy() + delete(rp.serverMap, key) + } + for key := range rp.clientMap { + delete(rp.clientMap, key) + } +} + +func GetRestProtocol() protocol.Protocol { + if restProtocol == nil { + restProtocol = NewRestProtocol() + } + return restProtocol +} diff --git a/protocol/rest/rest_protocol_test.go b/protocol/rest/rest_protocol_test.go new file mode 100644 index 0000000000000000000000000000000000000000..8af73a1839c159fdf58c64d12e039c20bb3221c6 --- /dev/null +++ b/protocol/rest/rest_protocol_test.go @@ -0,0 +1,186 @@ +/* + * 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 rest + +import ( + "context" + "errors" + "fmt" + "strings" + "testing" + "time" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/extension" + _ "github.com/apache/dubbo-go/common/proxy/proxy_factory" + "github.com/apache/dubbo-go/config" + rest_config "github.com/apache/dubbo-go/protocol/rest/config" +) + +func TestRestProtocol_Refer(t *testing.T) { + // Refer + proto := GetRestProtocol() + url, err := common.NewURL("rest://127.0.0.1:20000/com.ikurento.user.UserProvider?anyhost=true&" + + "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" + + "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" + + "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" + + "side=provider&timeout=3000×tamp=1556509797245") + assert.NoError(t, err) + con := config.ConsumerConfig{ + ConnectTimeout: 5 * time.Second, + RequestTimeout: 5 * time.Second, + } + config.SetConsumerConfig(con) + configMap := make(map[string]*rest_config.RestServiceConfig) + configMap["com.ikurento.user.UserProvider"] = &rest_config.RestServiceConfig{ + Client: "resty", + } + rest_config.SetRestConsumerServiceConfigMap(configMap) + invoker := proto.Refer(url) + + // make sure url + eq := invoker.GetUrl().URLEqual(url) + assert.True(t, eq) + + // make sure invokers after 'Destroy' + invokersLen := len(proto.(*RestProtocol).Invokers()) + assert.Equal(t, 1, invokersLen) + proto.Destroy() + invokersLen = len(proto.(*RestProtocol).Invokers()) + assert.Equal(t, 0, invokersLen) +} + +func TestRestProtocol_Export(t *testing.T) { + // Export + proto := GetRestProtocol() + url, err := common.NewURL("rest://127.0.0.1:8888/com.ikurento.user.UserProvider?anyhost=true&" + + "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" + + "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" + + "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" + + "side=provider&timeout=3000×tamp=1556509797245") + assert.NoError(t, err) + _, err = common.ServiceMap.Register(url.Protocol, &UserProvider{}) + assert.NoError(t, err) + con := config.ProviderConfig{} + config.SetProviderConfig(con) + configMap := make(map[string]*rest_config.RestServiceConfig) + methodConfigMap := make(map[string]*rest_config.RestMethodConfig) + queryParamsMap := make(map[int]string) + queryParamsMap[1] = "age" + queryParamsMap[2] = "name" + pathParamsMap := make(map[int]string) + pathParamsMap[0] = "userid" + methodConfigMap["GetUser"] = &rest_config.RestMethodConfig{ + InterfaceName: "", + MethodName: "GetUser", + Path: "/GetUser/{userid}", + Produces: "application/json", + Consumes: "application/json", + MethodType: "GET", + PathParams: "", + PathParamsMap: pathParamsMap, + QueryParams: "", + QueryParamsMap: queryParamsMap, + Body: -1, + } + configMap["com.ikurento.user.UserProvider"] = &rest_config.RestServiceConfig{ + Server: "go-restful", + RestMethodConfigsMap: methodConfigMap, + } + rest_config.SetRestProviderServiceConfigMap(configMap) + proxyFactory := extension.GetProxyFactory("default") + exporter := proto.Export(proxyFactory.GetInvoker(url)) + // make sure url + eq := exporter.GetInvoker().GetUrl().URLEqual(url) + assert.True(t, eq) + // make sure exporterMap after 'Unexport' + fmt.Println(url.Path) + _, ok := proto.(*RestProtocol).ExporterMap().Load(strings.TrimPrefix(url.Path, "/")) + assert.True(t, ok) + exporter.Unexport() + _, ok = proto.(*RestProtocol).ExporterMap().Load(strings.TrimPrefix(url.Path, "/")) + assert.False(t, ok) + + // make sure serverMap after 'Destroy' + _, ok = proto.(*RestProtocol).serverMap[url.Location] + assert.True(t, ok) + proto.Destroy() + _, ok = proto.(*RestProtocol).serverMap[url.Location] + assert.False(t, ok) + err = common.ServiceMap.UnRegister(url.Protocol, "com.ikurento.user.UserProvider") + assert.NoError(t, err) +} + +type UserProvider struct { +} + +func (p *UserProvider) Reference() string { + return "com.ikurento.user.UserProvider" +} + +func (p *UserProvider) GetUser(ctx context.Context, id int, age int32, name string, contentType string) (*User, error) { + return &User{ + Id: id, + Time: nil, + Age: age, + Name: name, + }, nil +} + +func (p *UserProvider) GetUserOne(ctx context.Context, user *User) (*User, error) { + return user, nil +} + +func (p *UserProvider) GetUserTwo(ctx context.Context, req []interface{}, rsp *User) error { + m := req[0].(map[string]interface{}) + rsp.Name = m["Name"].(string) + return nil +} + +func (p *UserProvider) GetUserThree(ctx context.Context, user interface{}) (*User, error) { + m := user.(map[string]interface{}) + + u := &User{} + u.Name = m["Name"].(string) + return u, nil +} + +func (p *UserProvider) GetUserFour(ctx context.Context, user []interface{}, id string) (*User, error) { + m := user[0].(map[string]interface{}) + + u := &User{} + u.Name = m["Name"].(string) + return u, nil +} + +func (p *UserProvider) GetUserFive(ctx context.Context, user []interface{}) (*User, error) { + return nil, errors.New("test error") +} + +type User struct { + Id int + Time *time.Time + Age int32 + Name string +} diff --git a/protocol/rest/server/rest_server.go b/protocol/rest/server/rest_server.go new file mode 100644 index 0000000000000000000000000000000000000000..c10c98a7b677d47c43b64643a69d5b3768a6c663 --- /dev/null +++ b/protocol/rest/server/rest_server.go @@ -0,0 +1,31 @@ +/* + * 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 server + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/protocol/rest/config" +) + +type RestServer interface { + Start(url common.URL) + Deploy(invoker protocol.Invoker, restMethodConfig map[string]*config.RestMethodConfig) + UnDeploy(restMethodConfig map[string]*config.RestMethodConfig) + Destroy() +} diff --git a/protocol/rest/server/server_impl/go_restful_server.go b/protocol/rest/server/server_impl/go_restful_server.go new file mode 100644 index 0000000000000000000000000000000000000000..3ea25531d62f5bd5fdb3b4be3e0fd3892b6b6b54 --- /dev/null +++ b/protocol/rest/server/server_impl/go_restful_server.go @@ -0,0 +1,311 @@ +/* + * 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 server_impl + +import ( + "context" + "fmt" + "net" + "net/http" + "reflect" + "strconv" + "strings" + "time" +) + +import ( + "github.com/emicklei/go-restful/v3" + 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/protocol" + "github.com/apache/dubbo-go/protocol/invocation" + "github.com/apache/dubbo-go/protocol/rest/config" + "github.com/apache/dubbo-go/protocol/rest/server" +) + +func init() { + extension.SetRestServer(constant.DEFAULT_REST_SERVER, GetNewGoRestfulServer) +} + +type GoRestfulServer struct { + srv *http.Server + container *restful.Container +} + +func NewGoRestfulServer() *GoRestfulServer { + return &GoRestfulServer{} +} + +func (grs *GoRestfulServer) Start(url common.URL) { + grs.container = restful.NewContainer() + grs.srv = &http.Server{ + Handler: grs.container, + } + ln, err := net.Listen("tcp", url.Location) + if err != nil { + panic(perrors.New(fmt.Sprintf("Restful Server start error:%v", err))) + } + + go func() { + err := grs.srv.Serve(ln) + if err != nil && err != http.ErrServerClosed { + logger.Errorf("[Go Restful] http.server.Serve(addr{%s}) = err{%+v}", url.Location, err) + } + }() +} + +func (grs *GoRestfulServer) Deploy(invoker protocol.Invoker, restMethodConfig map[string]*config.RestMethodConfig) { + svc := common.ServiceMap.GetService(invoker.GetUrl().Protocol, strings.TrimPrefix(invoker.GetUrl().Path, "/")) + for methodName, config := range restMethodConfig { + // get method + method := svc.Method()[methodName] + argsTypes := method.ArgsType() + replyType := method.ReplyType() + ws := new(restful.WebService) + ws.Path(config.Path). + Produces(strings.Split(config.Produces, ",")...). + Consumes(strings.Split(config.Consumes, ",")...). + Route(ws.Method(config.MethodType).To(getFunc(methodName, invoker, argsTypes, replyType, config))) + grs.container.Add(ws) + } + +} + +func getFunc(methodName string, invoker protocol.Invoker, argsTypes []reflect.Type, + replyType reflect.Type, config *config.RestMethodConfig) func(req *restful.Request, resp *restful.Response) { + return func(req *restful.Request, resp *restful.Response) { + var ( + err error + args []interface{} + ) + if (len(argsTypes) == 1 || len(argsTypes) == 2 && replyType == nil) && + argsTypes[0].String() == "[]interface {}" { + args = getArgsInterfaceFromRequest(req, config) + } else { + args = getArgsFromRequest(req, argsTypes, config) + } + result := invoker.Invoke(context.Background(), invocation.NewRPCInvocation(methodName, args, make(map[string]string))) + if result.Error() != nil { + err = resp.WriteError(http.StatusInternalServerError, result.Error()) + if err != nil { + logger.Errorf("[Go Restful] WriteError error:%v", err) + } + return + } + err = resp.WriteEntity(result.Result()) + if err != nil { + logger.Error("[Go Restful] WriteEntity error:%v", err) + } + } +} +func (grs *GoRestfulServer) UnDeploy(restMethodConfig map[string]*config.RestMethodConfig) { + for _, config := range restMethodConfig { + ws := new(restful.WebService) + ws.Path(config.Path) + err := grs.container.Remove(ws) + if err != nil { + logger.Warnf("[Go restful] Remove web service error:%v", err) + } + } +} + +func (grs *GoRestfulServer) Destroy() { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := grs.srv.Shutdown(ctx); err != nil { + logger.Errorf("[Go Restful] Server Shutdown:", err) + } + logger.Infof("[Go Restful] Server exiting") +} + +func getArgsInterfaceFromRequest(req *restful.Request, config *config.RestMethodConfig) []interface{} { + argsMap := make(map[int]interface{}, 8) + maxKey := 0 + for k, v := range config.PathParamsMap { + if maxKey < k { + maxKey = k + } + argsMap[k] = req.PathParameter(v) + } + for k, v := range config.QueryParamsMap { + if maxKey < k { + maxKey = k + } + params := req.QueryParameters(v) + if len(params) == 1 { + argsMap[k] = params[0] + } else { + argsMap[k] = params + } + } + for k, v := range config.HeadersMap { + if maxKey < k { + maxKey = k + } + argsMap[k] = req.HeaderParameter(v) + } + if config.Body >= 0 { + if maxKey < config.Body { + maxKey = config.Body + } + m := make(map[string]interface{}) + // TODO read as a slice + if err := req.ReadEntity(&m); err != nil { + logger.Warnf("[Go restful] Read body entity as map[string]interface{} error:%v", perrors.WithStack(err)) + } else { + argsMap[config.Body] = m + } + } + args := make([]interface{}, maxKey+1) + for k, v := range argsMap { + if k >= 0 { + args[k] = v + } + } + return args +} + +func getArgsFromRequest(req *restful.Request, argsTypes []reflect.Type, config *config.RestMethodConfig) []interface{} { + argsLength := len(argsTypes) + args := make([]interface{}, argsLength) + for i, t := range argsTypes { + args[i] = reflect.Zero(t).Interface() + } + var ( + err error + param interface{} + i64 int64 + ) + for k, v := range config.PathParamsMap { + if k < 0 || k >= argsLength { + logger.Errorf("[Go restful] Path param parse error, the args:%v doesn't exist", k) + continue + } + t := argsTypes[k] + kind := t.Kind() + if kind == reflect.Ptr { + t = t.Elem() + } + if kind == reflect.Int { + param, err = strconv.Atoi(req.PathParameter(v)) + } else if kind == reflect.Int32 { + i64, err = strconv.ParseInt(req.PathParameter(v), 10, 32) + if err == nil { + param = int32(i64) + } + } else if kind == reflect.Int64 { + param, err = strconv.ParseInt(req.PathParameter(v), 10, 64) + } else if kind == reflect.String { + param = req.PathParameter(v) + } else { + logger.Warnf("[Go restful] Path param parse error, the args:%v of type isn't int or string", k) + continue + } + if err != nil { + logger.Errorf("[Go restful] Path param parse error, error is %v", err) + continue + } + args[k] = param + } + for k, v := range config.QueryParamsMap { + if k < 0 || k >= argsLength { + logger.Errorf("[Go restful] Query param parse error, the args:%v doesn't exist", k) + continue + } + t := argsTypes[k] + kind := t.Kind() + if kind == reflect.Ptr { + t = t.Elem() + } + if kind == reflect.Slice { + param = req.QueryParameters(v) + } else if kind == reflect.String { + param = req.QueryParameter(v) + } else if kind == reflect.Int { + param, err = strconv.Atoi(req.QueryParameter(v)) + } else if kind == reflect.Int32 { + i64, err = strconv.ParseInt(req.QueryParameter(v), 10, 32) + if err == nil { + param = int32(i64) + } + } else if kind == reflect.Int64 { + param, err = strconv.ParseInt(req.QueryParameter(v), 10, 64) + } else { + logger.Errorf("[Go restful] Query param parse error, the args:%v of type isn't int or string or slice", k) + continue + } + if err != nil { + logger.Errorf("[Go restful] Query param parse error, error is %v", err) + continue + } + args[k] = param + } + + if config.Body >= 0 && config.Body < len(argsTypes) { + t := argsTypes[config.Body] + kind := t.Kind() + if kind == reflect.Ptr { + t = t.Elem() + } + var ni interface{} + if t.String() == "[]interface {}" { + ni = make([]map[string]interface{}, 0) + } else if t.String() == "interface {}" { + ni = make(map[string]interface{}) + } else { + n := reflect.New(t) + if n.CanInterface() { + ni = n.Interface() + } + } + if err := req.ReadEntity(&ni); err != nil { + logger.Errorf("[Go restful] Read body entity error:%v", err) + } else { + args[config.Body] = ni + } + } + + for k, v := range config.HeadersMap { + param := req.HeaderParameter(v) + if k < 0 || k >= argsLength { + logger.Errorf("[Go restful] Header param parse error, the args:%v doesn't exist", k) + continue + } + t := argsTypes[k] + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + if t.Kind() == reflect.String { + args[k] = param + } else { + logger.Errorf("[Go restful] Header param parse error, the args:%v of type isn't string", k) + } + } + + return args +} + +func GetNewGoRestfulServer() server.RestServer { + return NewGoRestfulServer() +} diff --git a/registry/base_registry.go b/registry/base_registry.go index 5b9aef82928d491d4b8f4dbe3caa4bd64a185dad..3b64e93e2f6b5b58a70650f589dec3ca092376c1 100644 --- a/registry/base_registry.go +++ b/registry/base_registry.go @@ -69,11 +69,20 @@ func init() { */ type FacadeBasedRegistry interface { Registry + + // CreatePath create the path in the registry CreatePath(string) error + // DoRegister actually do the register job DoRegister(string, string) error + // DoSubscribe actually subscribe the URL DoSubscribe(conf *common.URL) (Listener, error) + // CloseAndNilClient close the client and then reset the client in registry to nil + // you should notice that this method will be invoked inside a lock. + // So you should implement this method as light weighted as you can. CloseAndNilClient() + // CloseListener close listeners CloseListener() + // InitListeners init listeners InitListeners() } @@ -153,7 +162,7 @@ func (r *BaseRegistry) service(c common.URL) string { func (r *BaseRegistry) RestartCallBack() bool { // copy r.services - services := []common.URL{} + services := make([]common.URL, 0, len(r.services)) for _, confIf := range r.services { services = append(services, confIf) } @@ -227,9 +236,11 @@ func (r *BaseRegistry) providerRegistry(c common.URL, params url.Values) (string return "", "", perrors.Errorf("conf{Path:%s, Methods:%s}", c.Path, c.Methods) } dubboPath = fmt.Sprintf("/dubbo/%s/%s", r.service(c), common.DubboNodes[common.PROVIDER]) - r.cltLock.Lock() - err = r.facadeBasedRegistry.CreatePath(dubboPath) - r.cltLock.Unlock() + func() { + r.cltLock.Lock() + defer r.cltLock.Unlock() + err = r.facadeBasedRegistry.CreatePath(dubboPath) + }() if err != nil { logger.Errorf("facadeBasedRegistry.CreatePath(path{%s}) = error{%#v}", dubboPath, perrors.WithStack(err)) return "", "", perrors.WithMessagef(err, "facadeBasedRegistry.CreatePath(path:%s)", dubboPath) @@ -251,10 +262,11 @@ func (r *BaseRegistry) providerRegistry(c common.URL, params url.Values) (string logger.Debugf("provider url params:%#v", params) var host string if c.Ip == "" { - host = localIP + ":" + c.Port + host = localIP } else { - host = c.Ip + ":" + c.Port + host = c.Ip } + host += ":" + c.Port rawURL = fmt.Sprintf("%s://%s%s?%s", c.Protocol, host, c.Path, params.Encode()) // Print your own registration service providers. @@ -271,17 +283,25 @@ func (r *BaseRegistry) consumerRegistry(c common.URL, params url.Values) (string err error ) dubboPath = fmt.Sprintf("/dubbo/%s/%s", r.service(c), common.DubboNodes[common.CONSUMER]) - r.cltLock.Lock() - err = r.facadeBasedRegistry.CreatePath(dubboPath) - r.cltLock.Unlock() + + func() { + r.cltLock.Lock() + defer r.cltLock.Unlock() + err = r.facadeBasedRegistry.CreatePath(dubboPath) + + }() if err != nil { logger.Errorf("facadeBasedRegistry.CreatePath(path{%s}) = error{%v}", dubboPath, perrors.WithStack(err)) return "", "", perrors.WithStack(err) } dubboPath = fmt.Sprintf("/dubbo/%s/%s", r.service(c), common.DubboNodes[common.PROVIDER]) - r.cltLock.Lock() - err = r.facadeBasedRegistry.CreatePath(dubboPath) - r.cltLock.Unlock() + + func() { + r.cltLock.Lock() + defer r.cltLock.Unlock() + err = r.facadeBasedRegistry.CreatePath(dubboPath) + }() + if err != nil { logger.Errorf("facadeBasedRegistry.CreatePath(path{%s}) = error{%v}", dubboPath, perrors.WithStack(err)) return "", "", perrors.WithStack(err) @@ -345,9 +365,9 @@ func (r *BaseRegistry) Subscribe(url *common.URL, notifyListener NotifyListener) // closeRegisters close and remove registry client and reset services map func (r *BaseRegistry) closeRegisters() { + logger.Infof("begin to close provider client") r.cltLock.Lock() defer r.cltLock.Unlock() - logger.Infof("begin to close provider client") // Close and remove(set to nil) the registry client r.facadeBasedRegistry.CloseAndNilClient() // reset the services map