Go 中 Any 类型消息的自定义 protobuf 选项

Custom protobuf options of message of type Any in Go

我有一个 GRPC 服务定义如下:

message SendEventRequest {
  string producer = 1;
  google.protobuf.Any event = 2;
}

message SendEventResponse {
  string event_name = 1;
  string status = 2;
}

service EventService {
  rpc Send(SendEventRequest) returns (SendEventResponse);
}

我还定义了自定义消息选项:

extend google.protobuf.MessageOptions {
  // event_name is the unique name of the event sent by the clients
  string event_name = 50000;
}

我想要实现的是让客户创建自定义原型消息,将 event_name 选项设置为“常量”。例如:

message SomeCustomEvent {
  option (mypackage.event_name) = "some_custom_event";

  string data = 1;
  ...
}

这样服务就可以跟踪正在发送的事件。当我做这样的事情时,我能够从特定的 proto.Message:

中获取选项的值
_, md := descriptor.MessageDescriptorProto(SomeCustomEvent)
mOpts := md.GetOptions()
eventName := proto.GetExtension(mOpts, mypackage.E_EventName)

但是,当消息的类型为 github.com/golang/protobuf/ptypes/any.Any 时,选项为零。如何从消息中检索 event_name?我遇到了 protoregistry.MessageTypeResolver,这看起来可能有帮助,但我需要想出一种方法来在客户端集成时动态更新事件的原型定义。

any.ANY type in Go contains the TypeUrl field which contains the type of the message that was sent. You can then use that to UnmarshalAny 到正确的生成的 Go 类型。

Link 到 complete guide 如何使用 any.ANI

为了获得 Any 类型的选项,您需要其特定的 protoreflect.MessageType 以便您可以将其解组为特定的消息。为了获取消息类型,您需要 MessageTypeResolver.

Any 包含一个 type_url 字段,可用于该目的。为了将 Any 对象解组为现有消息类型的消息:

// GlobalTypes contains information about the proto message types
var res protoregistry.MessageTypeResolver = protoregistry.GlobalTypes
typeUrl := anyObject.GetTypeUrl()
msgType, _ := res.FindMessageByURL(typeUrl)

msg := msgType.New().Interface()
unmarshalOptions := proto.UnmarshalOptions{Resolver: res}
unmarshalOptions.Unmarshal(anyObject.GetValue(), msg)

收到特定消息后,您可以简单地获取您需要的选项:

msgOpts := msg.ProtoReflect().Descriptor().Options()
eventName := proto.GetExtension(msgOpts, mypackage.E_EventName)

请注意,如果消息未扩展 event_name 选项,proto.GetExtension 将崩溃,并且需要恢复。这个块可以加在函数的开头:

defer func() {
    if r := recover(); r != nil {
        // err is a named return parameter of the outer function
        err = fmt.Errorf("recovering from panic while extracting event_name from proto message: %s", r)
    }
}()

编辑:请注意,应用程序必须导入包含原型定义的包,以便 protoregistry.GlobalTypes 识别类型。你可以在你的代码中做这样的事情:

var _ mypackage.SomeEvent

博彦的回答有点补充,我发现它需要传递protoregistry.ExtensionTypeResolver变量来分配proto.UnmarshalOptions.Resolver,看起来像:

var extRes protoregistry.ExtensionTypeResolver = protoregistry.GlobalTypes
unmarshalOptions := proto.UnmarshalOptions{Resolver: extRes}