protobuf-go - 如何将文件解码为 descriptorpb.FileDescriptorProto

标签 protobuf-go

我正在为 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;

}

我的目标是通用地使用 protobufs 来定义函数,而不是使用例如直接去结构。

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

IIUC 这个原始文本文件不能直接使用,需要转换成描述 rune 件:

protoc \
--proto_path=./protos \
--descriptor_set_out=descriptor.pb \
protos/adder.proto

我假设(显然是错误的)我可以读入这个并将其解码为 descriptorpb.FileDescriptorProto:


b, err := ioutil.ReadFile("/path/to/descriptor.pb")
if err != nil {
    log.Fatal(err)
}

fdProto := &descriptorpb.FileDescriptorProto{}
proto.Unmarshal(b, fdProto)

问题:我应该怎么做?

假设客户端将调用上面的 Adder 等服务,服务器将需要一些服务才能将 pbany.Any 参数解码为正确的类型,调用函数并反转结果的过程。

奖励问题:

  • SDK 是否要求我使用 protoregistry管理这些传入类型?或者有更简单的方法吗?调用将包括函数以及参数和结果的类型,因此,应该始终可以即时编码消息。
  • 有没有使用 protoregistry 的例子?我对看似脱节的 FilesTypes
  • 感到困惑

最佳答案

好的,对于主要问题:

b, err := ioutil.ReadFile("/path/to/descriptor.pb")
if err != nil {
    log.Fatal(err)
}

fds := &descriptorpb.FileDescriptorSet{}
proto.Unmarshal(b, fds)

NOTE a FileDescriptorSet not a FileDescriptorProto

我仍然对不相交的 FilesTypes 感到困惑,但我怀疑开发人员有意移动,例如从文件中获取的消息转换为类型。

我解决了眼前的问题:

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)

    }
}

NOTE This may seem roundabout because I have pb.AdderRequest but I'm looking for a solution where this only available by definition.

NOTE The code duplicates the approach: byPath and byName as I'm unsure which approach I will want.

关于protobuf-go - 如何将文件解码为 descriptorpb.FileDescriptorProto,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62031061/

相关文章:

go - 无法使用 protobuf 解码字节

go - 在Golang中解码Protobuf(proto2)后如何设置可选参数?

go - 如何获取 golang proto 生成的复杂结构中的所有字段名称

go - 将ptypes/struct值转换为BSON

go - 将文档插入 mongodb,其中一个字段具有动态结构

go - 递归数据结构解码在 Go Lang Protobuf 中给出错误 "cannot parse invalid wire-format data"

Golang grpc.server : Understanding notions of server, 和服务

go - 如何使用 gogo/protobuf 获取自定义 go 类型