前言
由于工作中使用的 rpc 框架是 dubbo,经常需要调试不同环境的 dubbo 接口,例如本地环境、开发环境和测试环境。而为了同一管理 http 接口和 dubbo 接口,希望使用统一的调试工具,例如 PostMan 或 ApiPost 等,因此萌生了开发一个 dubbo 的 http 代理工具的念头。
准备
由于是通用的 dubbo 代理,因此肯定需要使用泛化调用。而我们使用的注册中心是 nacos,因此也需要使用 nacos-sdk 来获取 provider 的实例信息。
实现
项目结构
1 | │ |
go.mod
1 | module dubbo-proxy |
返回数据格式
dubbo/models.go
:
1 | type DataResult struct { |
获取 nacos 元信息
根据环境创建 nacos client
1 | func buildClient(env string, serverCfgs []constant.ServerConfig) naming_client.INamingClient { |
获取服务实例
1 | func SelectInstance(env, servName string) (string, bool) { |
完整代码
dubbo/nacos.go
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51package dubbo
import (
"fmt"
"github.com/nacos-group/nacos-sdk-go/v2/clients"
"github.com/nacos-group/nacos-sdk-go/v2/clients/naming_client"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/vo"
)
var cliMap = make(map[string]naming_client.INamingClient)
func init() {
serverCfgs := []constant.ServerConfig{
*constant.NewServerConfig("127.0.0.1", 6801, constant.WithContextPath("/nacos")),
}
cliMap["local"] = buildClient("local", serverCfgs)
cliMap["dev"] = buildClient("develop", serverCfgs)
cliMap["test"] = buildClient("test", serverCfgs)
}
func buildClient(env string, serverCfgs []constant.ServerConfig) naming_client.INamingClient {
client, _ := clients.NewNamingClient(
vo.NacosClientParam{
ClientConfig: constant.NewClientConfig(
constant.WithNamespaceId(env),
constant.WithNotLoadCacheAtStart(true),
),
ServerConfigs: serverCfgs,
},
)
return client
}
func SelectInstance(env, servName string) (string, bool) {
cli, ok := cliMap[env]
if !ok {
return "client not found from " + env, false
}
instances, e := cli.SelectInstances(vo.SelectInstancesParam{
ServiceName: fmt.Sprintf("providers:%s:1.0.0:", servName),
HealthyOnly: true,
})
if e != nil {
return "instance not found, " + e.Error(), false
}
if len(instances) <= 0 {
return "instance not found", false
}
return fmt.Sprintf("dubbo://%s:%d", instances[0].Ip, instances[0].Port), true
}
泛化调用
dubbo root 配置
1 | var dubboRoot = cfg.NewRootConfigBuilder().SetProtocols(map[string]*cfg.ProtocolConfig{ |
泛化调用
1 | func GenericInvoke(iName, method, env string, req []byte) DataResult { |
注意25-30
行要根据业务自身的返回数据格式包装结果:1
2
3
4
5
6
7
8
9
10
11
12
13
14/*
这个例子的 dubbo 调用都会返回通过的结构:
{
"code": "",
"message": "",
"data": // 真正的调用结果
}
*/
rawResult := raw.(map[interface{}]interface{})
result := DataResult{
Code: rawResult["code"].(string),
Message: rawResult["message"].(string),
Data: rawResult["data"],
}
完整代码
dubbo/generic.go
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54package dubbo
import (
"context"
"dubbo-proxy/utils"
cfg "dubbo.apache.org/dubbo-go/v3/config"
"dubbo.apache.org/dubbo-go/v3/config/generic"
_ "dubbo.apache.org/dubbo-go/v3/imports"
"dubbo.apache.org/dubbo-go/v3/protocol/dubbo"
hessian "github.com/apache/dubbo-go-hessian2"
)
var dubboRoot = cfg.NewRootConfigBuilder().SetProtocols(map[string]*cfg.ProtocolConfig{
dubbo.DUBBO: {
Params: map[string]interface{}{
"getty-session-param": map[string]interface{}{
"max-msg-len": 1024000,
},
},
},
}).Build()
func GenericInvoke(iName, method, env string, req []byte) DataResult {
instance, ok := SelectInstance(env, iName)
if !ok {
return DataResult{
Code: "ERROR",
Message: instance,
}
}
cfg.Load(cfg.WithRootConfig(dubboRoot))
refConf := cfg.ReferenceConfig{
InterfaceName: iName,
Cluster: "failover",
Protocol: dubbo.DUBBO,
Generic: "true",
Version: "1.0.0",
URL: instance,
}
refConf.Init(dubboRoot)
refConf.GenericLoad("dubbo-proxy")
var args = utils.Unmarshal(req, &map[string]hessian.Object{})
raw, err := refConf.GetRPCService().(*generic.GenericService).Invoke(context.Background(), method, nil, []hessian.Object{args})
if err != nil {
panic(err)
}
rawResult := raw.(map[interface{}]interface{})
result := DataResult{
Code: rawResult["code"].(string),
Message: rawResult["message"].(string),
Data: utils.ConvertAs(rawResult["data"], map[string]interface{}{}),
}
return result
}
提供 http 服务
dubbo/generic.go
:
1 | package web |
启动
main.go
:
1 | package main |