gRPC中Any類型的使用(Java和NodeJs端)


  工作中要把原來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<string, google.protobuf.Any> 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<Testservice.GenericMap> responseObserver) { 4 5    Testservice.Param stringValue = Testservice.Param 6              .newBuilder() 7           .setValue("this is String type") 8              .build(); 9 10    List<String> tempList = Lists.newArrayList("this is", "List type"); 11    Testservice.ListParam listValue = Testservice.ListParam 12           .newBuilder() 13                 .addAllValue() 14               .build(); 15 16    Map<String, Any> 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<String, Any> reMap = bkStub.getMapData(param).get().getValueMap();
14 
15     Map<String, Object> 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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM