Пользовательские параметры протобуфа сообщения типа Any в Go

#go #protocol-buffers #any

#Вперед #буферы протокола #Любой

Вопрос:

У меня есть служба 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 , который выглядит так, как будто это может помочь, но мне нужно было бы найти способ динамического обновления прототипов событий при интеграции клиентов.

Комментарии:

1. Если вы используете тип ANY, разве в коде Go не добавлена функция, позволяющая узнать, какой тип был отправлен? Или вы не можете выполнить утверждение типа? Это должно сделать статическое значение ненужным.

Ответ №1:

Чтобы получить параметры 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)
 

Обратите внимание, что proto.GetExtension это вызовет панику, если сообщение не расширяет event_name опцию, и его необходимо восстановить. Этот блок может быть добавлен в начале функции:

 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

Ответ №2:

Тип any.ANY в Go содержит TypeUrl поле, которое содержит тип отправленного сообщения. Затем вы можете использовать это, чтобы отменить привязку к правильному, сгенерированному типу Go.

Ссылка на полное руководство по работе с any.ANI .

Комментарии:

1. Это помогло мне только наполовину. Но я нашел решение, которое я опубликую в качестве решения позже сегодня.

Ответ №3:

Небольшое дополнение к ответу Бояна, я обнаружил protoregistry.ExtensionTypeResolver , что для назначения необходимо передать переменную proto.UnmarshalOptions.Resolver , которая выглядит как:

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