谷歌的protobuf.Any字段在grpc中始终为nil。

huangapple go评论89阅读模式
英文:

google.protobuf.Any field with grpc is always nil

问题

我正在使用golang的gRPC。我有一个非常简单的proto定义和一个gRPC服务。proto定义中的Endorsement字段的类型是google/protobuf/any。gRPC服务无法将此字段映射到输入值,它始终被初始化为nil。

proto定义如下:

syntax = "proto3";

option go_package = "service";
option java_multiple_files = true;
option java_package = "io.grpc.consensus";

import "google/protobuf/any.proto";

package service;

service MyService {
  rpc Verify (Payload) returns (Response) {}
}

message Response {
  string policyId = 1;
  string txnId = 2;
}

message Endorsement {
  string endorserId = 1;
  // This is being initialised to nil by gRPC
  google.protobuf.Any data = 2;
  string signature = 3;
  bool isVerified = 4;
}

message Payload {
  string policyId = 1;
  string txnId = 2;
  repeated Endorsement endorsements = 3;
}

使用这个定义,实现了一个简单的gRPC服务:

package service

import (
	"log"

	"golang.org/x/net/context"
)

type ServiceServerImpl struct {
}

func NewServiceServerImpl() *ServiceServerImpl {
	return &ServiceServerImpl{}
}

func (s *ServiceServerImpl) Verify(ctx context.Context, txnPayload *Payload) (*Response, error) {
	log.Printf("Got verification request: %s", txnPayload.TxnId)
	for _, endorsement := range txnPayload.Endorsements {
		j, err := endorsement.Data.UnmarshalNew()
		if err != nil {
			log.Print("Error while unmarshaling the endorsement")
		}
		if j == nil {
			//This gets printed as payload's endorsement data is always null for google/protobuf/any type
			log.Print("Data is null for endorsement")
		}
	}
	return &Response{TxnId: txnPayload.TxnId, PolicyId: txnPayload.PolicyId}, nil
}

输入数据如下:

{
  "policyId": "9dd97b1e-b76f-4c49-b067-22143c954e75",
  "txnId": "231-4dc0-8e54-58231df6f0ce",
  "endorsements": [
    {
      "endorserId": "67e1dfbd-1716-4d91-94ec-83dde64e4b80",
      "data": {
        "type": "issueTx",
        "userId": 1,
        "transaction": {
            "amount": 10123.50
        }
    },
      "signature": "MEUCIBkooxG2uFZeSEeaf5Xh5hWLxcKGMxCZzfnPshOh22y2AiEAwVLAaGhccUv8UhgC291qNWtxrGawX2pPsI7UUA/7QLM=",
      "isVerified": false
    }
  ]
}

客户端代码如下:

type Data struct {
	Type        string      `json:"type"`
	UserId      int16       `json:"userId"`
	Transaction Transaction `json:"transaction"`
}

type Transaction struct {
	Amount float32 `json:"amount"`
}

data := &Data{Type: "Buffer", UserId: 1, Transaction: Transaction{Amount: 10123.50}}
byteData, err := json.Marshal(data)
if err != nil {
	log.Printf("Could not convert data input to bytes")
}

e := &any.Any{
	TypeUrl: "anything",
	Value:   byteData,
}

endosement := &Endorsement{EndorserId: "1", Signature: "MEUCIBkooxG2uFZeSEeaf5Xh5hWLxcKGMxCZzfnPshOh22y2AiEAwVLAaGhccUv8UhgC291qNWtxrGawX2pPsI7UUA/7QLM=", Data: e, IsVerified: false}
var endosements = make([]*Endorsement, 1)
endosements[0] = endosement
t := &Payload{TxnId: "123", PolicyId: "456", Endorsements: endosements}

response, err := c.Verify(context.Background(), t)
if err != nil {
	log.Fatalf("Error when calling SayHello: %s", err)
}

如何为未知类型解组google/protobuf/any类型?

m, err := e.Data.UnmarshalNew()
if err != nil {
	log.Print("Error while unmarshaling the endorsement")
}

抛出错误:s:"not found"

英文:

I am using gRPC with golang. I have a very simple proto definition and a gRPC service. The proto definition has a field in Endorsement of type google/protobuf/any. gRPC service is unable to map this field to input value and it's always getting initialised to nil

proto definition:

syntax = "proto3";

option go_package = "service";
option java_multiple_files = true;
option java_package = "io.grpc.consensus";

import "google/protobuf/any.proto";

package service;

service MyService {
  rpc Verify (Payload) returns (Response) {}
}

message Response {
  string policyId =1;
  string txnId =2;
}

message Endorsement {
  string endorserId=1;
  // This is being initialise to nil by gRPC
  google.protobuf.Any data = 2;
  string signature=3;
  bool isVerified=4;
}


message Payload {
  string policyId =1;
  string txnId =2;
  repeated Endorsement endorsements=3;
}

Using this, a simple gRPC service is implemented:

package service

import (
	"log"

	"golang.org/x/net/context"
)

type ServiceServerImpl struct {
}

func NewServiceServerImpl() *ServiceServerImpl {
	return &ServiceServerImpl{}
}

func (s *ServiceServerImpl) Verify(ctx context.Context, txnPayload *Payload) (*Response, error) {
	log.Printf("Got verification request: %s", txnPayload.TxnId)
	for _, endorsement := range txnPayload.Endorsements {
		j, err := endorsement.Data.UnmarshalNew()
		if err != nil {
			log.Print("Error while unmarshaling the endorsement")
		}
		if j==nil {
       //This gets printed as payload's endorsement data is always null for google/protobuf/any type
			log.Print("Data is null for endorsement")
		}
	}
	return &Response{TxnId: txnPayload.TxnId,  PolicyId: txnPayload.PolicyId}, nil
}

Input Data:

{
  "policyId": "9dd97b1e-b76f-4c49-b067-22143c954e75",
  "txnId": "231-4dc0-8e54-58231df6f0ce",
  "endorsements": [
    {
      "endorserId": "67e1dfbd-1716-4d91-94ec-83dde64e4b80",
      "data": {
        "type": "issueTx",
        "userId": 1,
        "transaction": {
            "amount": 10123.50
        }
    },
      "signature": "MEUCIBkooxG2uFZeSEeaf5Xh5hWLxcKGMxCZzfnPshOh22y2AiEAwVLAaGhccUv8UhgC291qNWtxrGawX2pPsI7UUA/7QLM=",
      "isVerified": false
    }
  ]
}

Client:

type Data struct {
	Type        string      `json:"type"`
	UserId      int16       `json:"userId"`
	Transaction Transaction `json:"transaction"`
}

type Transaction struct {
	Amount float32 `json:"amount"`
}

data := &Data{Type: "Buffer", UserId: 1, Transaction: Transaction{Amount: 10123.50}}
	byteData, err := json.Marshal(data)
	if err != nil {
		log.Printf("Could not convert data input to bytes")
	}

	e := &any.Any{
		TypeUrl: "anything",
		Value:   byteData,
	}

	endosement := &Endorsement{EndorserId: "1", Signature: "MEUCIBkooxG2uFZeSEeaf5Xh5hWLxcKGMxCZzfnPshOh22y2AiEAwVLAaGhccUv8UhgC291qNWtxrGawX2pPsI7UUA/7QLM=", Data: e, IsVerified: false}
	var endosements = make([]*Endorsement, 1)
	endosements[0] = endosement
	t := &Payload{TxnId: "123", PolicyId: "456", Endorsements: endosements}

	response, err := c.Verify(context.Background(), t)
	if err != nil {
		log.Fatalf("Error when calling SayHello: %s", err)
	}

How should google/protobuf/any type be Unmarshal for unknown types?

m, err := e.Data.UnmarshalNew()
	if err != nil {
		log.Print("Error while unmarshaling the endorsement")
	}

> throws an error: s:"not found"

答案1

得分: 0

从包<sup>1</sup>文档解组 Any

UnmarshalNew 使用全局类型注册表来解析消息类型并构造一个新的实例,以便进行解组。为了使消息类型出现在全局注册表中,表示该 protobuf 消息类型的 Go 类型必须链接到 Go 二进制文件中。对于由 protoc-gen-go 生成的消息,可以通过导入表示 .proto 文件的生成 Go 包来实现。

类型注册表是 protoregistry.GlobalTypes。类型查找使用 Any.TypeUrl 字段进行,该字段由 gRPC 客户端在编组原始消息时设置为具体类型的 URL。

关于 Any 的令人困惑的细节是它可以是任何 protobuf 消息,但该 protobuf 消息必须在某处定义

你的 .proto 文件中没有与输入中的 data 对象匹配的消息定义。可能是 Data 消息在其他地方定义(不在你自己的 proto 文件中),但无论如何,你必须导入生成的消息所在的 Go 包

否则,如果输入不来自已定义的 proto 消息,你可以自己在 proto 中添加一个消息定义,然后使用 UnmarshalTo

// proto 文件
message Data {
    string type = 1;
    int user_id = 2;
    Transaction transaction = 3;
}

message Transaction {
    float amount = 1;
}

然后:

    for _, endorsement := range txnPayload.Endorsements {
        data := generated.Data{}
        err := endorsement.Data.UnmarshalTo(&data)
        if err != nil {
            log.Print("解组认可时出错")
        }
    }

<hr>

如果你只需要一个任意的字节序列,即一个真正未知的类型,那么使用 proto 类型 bytes 并将其视为 JSON 负载。

将其建模为 Go 结构体:

type Data struct {
    Type     string `json:"type"`
    UserID   int    `json:"userId"`
    Transaction struct{
        Amount float64 `json:"amount"`
    } `json:"transaction"`

}

或者如果客户端可能发送任何内容,则建模为 map[string]interface{}

然后在处理函数中:

    for _, endorsement := range txnPayload.Endorsements {
        data := Data{} // 或 `map[string]interface{}`
        err := json.Unmarshal(endorsement.Data, &data)
        if err != nil {
            log.Print("解组认可时出错")
        }
    }
英文:

From the package<sup>1</sup> documentation Unmarshaling an Any:

> UnmarshalNew uses the global type registry to resolve the message type and construct a new instance of that message to unmarshal into. In order for a message type to appear in the global registry, the Go type representing that protobuf message type must be linked into the Go binary. For messages generated by protoc-gen-go, this is achieved through an import of the generated Go package representing a .proto file.

The type registry is protoregistry.GlobalTypes. The type lookup is done using the Any.TypeUrl field, which is set to the concrete type's url by the gRPC client when marshalling the original message.

The confusing detail about Any is that it can be any protobuf message, but that protobuf message must be defined somewhere.

Your .proto file has no message definition that matches the data object in your input. It could be that this Data message is defined somewhere else (not in your own proto files), but irrespective of that you have to import the Go package where the generated message is.

Otherwise if the input doesn't come from an already defined proto message, you can add a message definition to your proto yourself, then use UnmarshalTo:

// proto file
message Data {
    string type = 1;
    int user_id = 2;
    Transaction transaction = 3;
}

message Transaction {
    float amount = 1;
}

And then:

    for _, endorsement := range txnPayload.Endorsements {
        data := generated.Data{}
        err := endorsement.Data.UnmarshalTo(&amp;data)
        if err != nil {
            log.Print(&quot;Error while unmarshaling the endorsement&quot;)
        }
    }

<hr>

If you only need an arbitrary sequence of bytes, i.e. a truly unknown type, then use the proto type bytes and treat it as a JSON payload.

Model it as a Go struct:

type Data struct {
    Type     string `json:&quot;type&quot;`
    UserID   int    `json:&quot;userId&quot;`
    Transaction struct{
        Amount float64 `json:&quot;amount&quot;`
    } `json:&quot;transaction&quot;`

}

Or as a map[string]interface{} if clients could be sending literally anything.

Then in your handler func:

    for _, endorsement := range txnPayload.Endorsements {
        data := Data{} // or `map[string]interface{}`
        err := json.Unmarshal(endorsement.Data, &amp;data)
        if err != nil {
            log.Print(&quot;Error while unmarshaling the endorsement&quot;)
        }
    }

答案2

得分: -1

你好,以下是翻译好的内容:

正如你所知,google.protobuf.Any 是一种消息类型,所以当你没有为它设置任何值时,它将为 nil。

你必须使用你的结构体对数据进行反序列化,可以参考下面的代码示例:

// 序列化 any
foo := &pb.Foo{...}
any, err := anypb.New(foo)
if err != nil {
    // 错误处理
}

// 反序列化 any
foo := &pb.Foo{}
if err := any.UnmarshalTo(foo); err != nil {
    // 错误处理
}

或者你可以使用指针接口 (&interface{}),像这样:

d := &interface{}{}
if err := endorsement.Data.UnmarshalTo(d); err != nil {
    // 错误处理
}

你可以在这里找到更多关于 protobuf 的示例代码。

英文:

As you know google.protobuf.Any is messageType, so when you didn't set any value for it, It will be nil.

You must unmarshal your data with your struct, see code below from example of protobuf

      // marshal any
      foo := &amp;pb.Foo{...}
      any, err := anypb.New(foo)
      if err != nil {
        ...
      }
      // unmarshal any 
      foo := &amp;pb.Foo{}
      if err := any.UnmarshalTo(foo); err != nil {
        ...
      }

Or I think you can use it with pointer interface (&interface{})
like this :

      d := &amp;interface{}{}
      if err := endorsement.Data.UnmarshalTo(d); err != nil {
        ...
      }

huangapple
  • 本文由 发表于 2021年7月13日 12:06:56
  • 转载请务必保留本文链接:https://go.coder-hub.com/68356352.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定