如何统一 grpc-gateway 响应的外层结构?

1. 问题描述?

我们的后端服务采用 gRPC 方式开发,Protobuf 定义如下:

message XXXResp {
    string name = 1;
    string email = 2;
}

通过 grpc-gateway 提供给客户端 RESTful API,返回如下响应结果:

{
    "name": "XXXX",
    "email": "xxxx@xxxx.com"
}

但是客户端希望统一响应的结构,例如:

{
    "code": 200,
    "message": "successful",
    "data": {
        "name": "XXXX",
        "email": "xxxx@xxxx.com"
    }
}

我们的 gRPC 服务也会有后端的服务调用,不希望在 Protobuf 的响应里做一层封装。

所以想问下有没有办法在 grpc-gateway 层对 gRPC 响应做统一的封装,使最终响应的 HTTP Response Body 的结构一致?

或者有没有什么更好的解决方案或最佳实践呢?

最佳答案

基于 Gin 实现一个对外网关,做协议转换,向内部发起grpc调用,成功就直接扔data里包装返回给前端,失败处理 error,然后组装一个 error response 给前端就行了,核心思路就是自己实现把 http url 做分割,比如 /package.version/service/method,然后用 github.com/jhump/protoreflect 加载本地的proto文件,基于 package 信息拿到 service 信息,然后拿到 method 以及具体的 request message ,动态创建一个 message,动态创建 stub,然后发起调用就行了

11个月前 评论
GeorgeKing (楼主) 11个月前
讨论数量: 4

基于 Gin 实现一个对外网关,做协议转换,向内部发起grpc调用,成功就直接扔data里包装返回给前端,失败处理 error,然后组装一个 error response 给前端就行了,核心思路就是自己实现把 http url 做分割,比如 /package.version/service/method,然后用 github.com/jhump/protoreflect 加载本地的proto文件,基于 package 信息拿到 service 信息,然后拿到 method 以及具体的 request message ,动态创建一个 message,动态创建 stub,然后发起调用就行了

11个月前 评论
GeorgeKing (楼主) 11个月前
import (
    "context"
    "encoding/json"
    "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
    "google.golang.org/protobuf/encoding/protojson"
    "io"
)

// Custom Marshaler

type CustomMarshaler struct {
    m *runtime.JSONPb
}

type Response struct {
    Code    uint        `json:"code"`
    Message string      `json:"message"`
    Data    interface{} `json:"data"`
}

func (c *CustomMarshaler) Marshal(v interface{}) ([]byte, error) {
    return c.m.Marshal(&Response{
        Code:    200,
        Message: "successful",
        Data:    v,
    })
}
func (c *CustomMarshaler) Unmarshal(data []byte, v interface{}) error {
    return c.m.Unmarshal(data, v)
}

func (c *CustomMarshaler) NewDecoder(r io.Reader) runtime.Decoder {
    return c.m.NewDecoder(r)
}
func (c *CustomMarshaler) NewEncoder(w io.Writer) runtime.Encoder {
    return c.m.NewEncoder(w)
}
func (c *CustomMarshaler) ContentType(v interface{}) string {
    return "application/json"
}

opts := []runtime.ServeMuxOption{
    runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.HTTPBodyMarshaler{
        Marshaler: &CustomMarshaler{
            m: &runtime.JSONPb{
                MarshalOptions:   protojson.MarshalOptions{},
                UnmarshalOptions: protojson.UnmarshalOptions{},
            }},
    }),
}

gws := runtime.NewServeMux(opts...)
11个月前 评论
SoulKiller 10个月前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!