#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}