工作中要把原来Java服务端基于SpringMVC的服务改为使用gRPC直接调用。由于原Service的返回值为动态的Map类型,key值不确定,且value的类型不唯一,因此使用了protobuf 3中的map和Any类型。在这个过程中遇到了一些困难,查阅资料时发现这一块的资料不是很多,尤其是在NodeJS的gRPC-Client处理google.protobuf.Any类型,完全找不到相关的资料。好在自己摸索和调试后解决了问题,因此记录下来以供他人之需。
testservice.proto:
1 syntax = "proto3";
2
3 import "google/protobuf/any.proto";
4 option java_package = "com.zfp.demo.grpc";
5
6 service TestService {
7 rpc getMapData (Param) returns (GenericMap);
8 }
9
10 message GenericMap {
11 map value = 1;
12 }
13
14 message Param{
15 string value = 1;
16 }
17
18 message ListParam{
19 repeated string value = 1;
20 }
其中,Any类型的作用是在protobuf中不需要明确定义值的结构和类型,而是在gRPC的Server端通过pack()将任何message打包成Any类型(不可以直接打包Java Object),在client可以通过unPack()将message从Any中取出,实现了protobuf对泛型的支持。
Java Server:
1 import com.google.protobuf.Any;
2 @Overried
3 public void getMapData(Testservice.Param request, StreamObserver responseObserver) {
4
5 Testservice.Param stringValue = Testservice.Param
6 .newBuilder()
7 .setValue("this is String type")
8 .build();
9
10 List tempList = Lists.newArrayList("this is", "List type");
11 Testservice.ListParam listValue = Testservice.ListParam
12 .newBuilder()
13 .addAllValue()
14 .build();
15
16 Map reMap = Maps.newHashMap();
17 reMap.put("k1", Any.pack(stringValue));
18 reMap.put("k2", Any.pack(listValue));
19
20 Testservice.GenericMap genericMap = Testservice.GenericMap
21 .newBuilder()
22 .putAllValue(reMap)
23 .build();
24
25 responseObserver.onNext(genericMap);
26 responseObserver.onCompleted();
27 }
Java Client:
1 @Test
2 public void getMapDataTest() throws ExecutionException, InterruptedException {
3
4 ManagedChannel channel = ManagedChannelBuilder.forAddress("127.0.0.1", 6565)
5 .usePlaintext(true)
6 .build();
7
8 TestServiceGrpc.RoomServiceBlockingStub bkStub = TestServiceGrpc.newBlockingStub(channel);
9
10
11 Testservice.Param param = Testservice.Param.newBuilder().setValue("test param").build();
12
13 Map reMap = bkStub.getMapData(param).get().getValueMap();
14
15 Map dataMap = Maps.newHashMap();
16
17 reMap.forEach((k, v) -> {
18 if (k.equals("k1")) {
19 try {
20 dataMap.put(k, v.unpack(Testservice.Param.class).getValue());
21 } catch (InvalidProtocolBufferException e) {
22 e.printStackTrace();
23 }
24 } else {
25 try {
26 dataMap.put(k, v.unpack(Testservice.ListParam.class).getValueList());
27 } catch (InvalidProtocolBufferException e) {
28 e.printStackTrace();
29 }
30 }
31 });
32
33 logger.info(JSON.toJSONString(dataMap, true));
34 }
NodeJS Client:
var messages = require(‘./testservice_pb‘);
var services = require(‘./testservice_grpc_pb‘); // 这两个文件是利用protoc命令根据 testservice.proto 自动生成的
var grpc = require(‘grpc‘);
var prob = require(‘./node_modules/google-protobuf/google/protobuf/any_pb‘); // 使用了Any类型必须引入这个文件
var jspb = require(‘google-protobuf‘);
main = function () {
var client = new services.TestServiceClient(‘localhost:6565‘,
grpc.credentials.createInsecure());
var request = new messages.Param();
request.setValue("test param");
client.getMapData(request, function (err, res) {
var reMap = unPackGpMap(res);
console.log(reMap);
});
};
/**
* 解包 google.protobuf.Any 对象,并返回结果
* @param {!google.protobuf.Any} gpAny
*/
unPackAny = function (gpAny) {
var typeName = gpAny.getTypeName(); // 获取Any包装的message对象的类型名称
var deserialize;
switch (typeName) {
case "ListParam":
deserialize = messages.ListParam.deserializeBinary; // 从Uint8Array反序列化ListParam的function
return unPackAny_List(gpAny, deserialize, typeName);
case "Param":
deserialize = messages.Param.deserializeBinary;
return unPackAny_OneField(gpAny, deserialize, typeName);
case "ObjParam":
deserialize = messages.ObjParam.deserializeBinary;
return unPackAny_ComplexObject(gpAny, deserialize, typeName);
default:
return "the Message type \‘" + typeName +
"\‘ is not defiend in .proto file";
}
};
/** * Any包装的message只含有一个名为value的字段时使用 */
unPackAny_OneField = function (gpAny, deserialize, typeName) {
return gpAny.unpack(deserialize, typeName).toObject()["value"];
};
/** * Any包装的message含有一个名为value的repeated字段时使用 */
unPackAny_List = function (gpAny, deserialize, typeName) {
return gpAny.unpack(deserialize, typeName).toObject()["valueList"];
};
/** * Any包装的message含有多个field时使用(message嵌套也同样适用) */
unPackAny_ComplexObject = function (gpAny, deserialize, typeName) {
return gpAny.unpack(deserialize, typeName).toObject();
};
/**
* 将 GenericMap 中需要的Map数据取出,并解包Any型的value, 组装成可读的reMap并返回
* @param gpMap
* @returns {{}}
*/
unPackGpMap = function (gpMap) {
var dataMap = gpMap[‘wrappers_‘][‘1‘][‘map_‘];
var fieldList = Object.keys(dataMap);
var reMap = {};
for (var i = 0; i < fieldList.length; i++) {
reMap[fieldList[i]] = unPackAny(dataMap[fieldList[i]]["valueWrapper"]);
}
return reMap;
};
main();
本文只展示了google.protobuf.Any的使用,Java和NodeJS中gRpc项目的具体构建可以参考以下项目:
https://github.com/LogNet/grpc-spring-boot-starter
https://github.com/grpc/grpc/tree/master/examples
时间: 02-12