我正在与Golang Protobuf APIv2作斗争。
我正在尝试封装 protobuf 消息,以便可以调用服务器上的函数;编译时未知的函数。某种形式:
syntax = "proto3";
package p;
import "google/protobuf/any.proto";
option go_package = "...";
message FunctionRequest {
string name = 1;
google.protobuf.Any parameters = 3;
}
message FunctionResponse {
google.protobuf.Any result = 1;
string error = 2;
}
Run Code Online (Sandbox Code Playgroud)
我的目标是使用 protobuf 来定义函数,而不是直接使用 Go 结构体。
Google SDK 很复杂,我无法找到示例。
例如
syntax = "proto3";
package e;
option go_package = "...";
service Adder {
rpc add(AdderRequest) returns (AdderResponse) {};
}
message AdderRequest {
int32 a = 1;
int32 b = 2;
}
message AdderResponse {
int32 result = 1;
}
Run Code Online (Sandbox Code Playgroud)
IIUC 这个原始文本文件不能直接使用,需要转换为描述符文件:
protoc \
--proto_path=./protos \
--descriptor_set_out=descriptor.pb \
protos/adder.proto
Run Code Online (Sandbox Code Playgroud)
我假设(显然是错误的)我可以读入此内容并将其解组为descriptorpb.FileDescriptorProto:
b, err := ioutil.ReadFile("/path/to/descriptor.pb")
if err != nil {
log.Fatal(err)
}
fdProto := &descriptorpb.FileDescriptorProto{}
proto.Unmarshal(b, fdProto)
Run Code Online (Sandbox Code Playgroud)
问题:我应该怎么做?
假设客户端将发起对上述服务的调用Adder,服务器将需要一些能够将pbany.Any参数解组为正确类型、调用函数并反转结果的过程。
奖励问题:
protoregistry来管理这些传入类型吗?或者有更简单的方法吗?调用将包括函数以及参数和结果的类型,因此应该始终可以即时编组消息。protoregistry吗?我对看似脱节的Files事物感到困惑Types好的,对于主要问题:
b, err := ioutil.ReadFile("/path/to/descriptor.pb")
if err != nil {
log.Fatal(err)
}
fds := &descriptorpb.FileDescriptorSet{}
proto.Unmarshal(b, fds)
Run Code Online (Sandbox Code Playgroud)
注意a
FileDescriptorSet不是 aFileDescriptorProto
我仍然对这种不相交感到困惑Files,Types但我怀疑开发人员的意图是能够将从文件中获取的消息移动到某种类型中。
我解决了眼前的问题:
package main
import (
"fmt"
"io/ioutil"
"log"
pb ".../protos"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protodesc"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/reflect/protoregistry"
"google.golang.org/protobuf/types/descriptorpb"
"google.golang.org/protobuf/types/dynamicpb"
"google.golang.org/protobuf/types/known/anypb"
)
var _ protodesc.Resolver = (*protoregistry.Files)(nil)
const (
projectRoot = "."
descriptorFilename = "descriptor.pb" // The output from `protoc --descriptor_set_out ...`
protoFilename = "adder.proto" // The protoFilename will be one (of possibly several) in desc
packageName = "e"
serviceName = "Adder"
)
var (
// By convention, the request message to be serviceName+"Request"
message = service + "Request"
)
func toAny(t string, m protoreflect.ProtoMessage) (*anypb.Any, error) {
v, err := proto.Marshal(m)
if err != nil {
return nil, err
}
return &anypb.Any{
TypeUrl: t,
Value: v,
}, nil
}
func readDescriptorSetFile(name string) (*descriptorpb.FileDescriptorSet, error) {
b, err := ioutil.ReadFile(name)
if err != nil {
return nil, err
}
fds := &descriptorpb.FileDescriptorSet{}
proto.Unmarshal(b, fds)
return fds, nil
}
func byPath(ff *protoregistry.Files, path string, messageName string) (protoreflect.MessageDescriptor, error) {
fd, err := ff.FindFileByPath(path)
if err != nil {
return nil, err
}
mds := fd.Messages()
return mds.ByName(protoreflect.Name(messageName)), nil
}
func byName(ff *protoregistry.Files, packageName string, messageName string) (protoreflect.MessageDescriptor, error) {
d, err := ff.FindDescriptorByName(protoreflect.FullName(fmt.Sprintf("%s.%s", packageName, messageName)))
if err != nil {
log.Fatal(err)
}
md, ok := d.(protoreflect.MessageDescriptor)
if !ok {
return nil, fmt.Errorf("type assertion to MessageDescriptor failed: %s", d.FullName())
}
return md, nil
}
func unmarshal(md protoreflect.MessageDescriptor, a *anypb.Any) (*dynamicpb.Message, error) {
m := dynamicpb.NewMessage(md)
err := proto.Unmarshal(a.GetValue(), m)
if err != nil {
return nil, err
}
return m, nil
}
func main() {
// protoc \
// --proto_path=./protos \
// --descriptor_set_out=descriptor.pb \
// protos/adder.proto
fds, err := readDescriptorSetFile(fmt.Sprintf("%s/%s", projectRoot, descriptorFilename))
ff, err := protodesc.NewFiles(fds)
if err != nil {
log.Fatal(err)
}
a, err := toAny(
fmt.Sprintf("%s.%s", packageName, message),
&pb.AdderRequest{
A: 39,
B: 3,
})
if err != nil {
log.Fatal(err)
}
{
md, err := byPath(ff, protoFilename, message)
if err != nil {
log.Fatal(err)
}
m, err := unmarshal(md, a)
if err != nil {
log.Fatal(err)
}
log.Printf("%+v", m)
}
{
md, err := byName(ff, packageName, message)
if err != nil {
log.Fatal(err)
}
m, err := unmarshal(md, a)
if err != nil {
log.Fatal(err)
}
log.Printf("%+v", m)
}
}
Run Code Online (Sandbox Code Playgroud)
注意这可能看起来很迂回,因为我已经找到了,
pb.AdderRequest但我正在寻找一个只能根据定义使用的解决方案。注意该代码重复了该方法:
byPath并且byName因为我不确定我想要哪种方法。
| 归档时间: |
|
| 查看次数: |
3031 次 |
| 最近记录: |