让 gRPC 服务同时支持 HTTP/JSON 的gRPC-Gateway
gRPC 基于 HTTP/2 和 Protobuf,性能优秀但二进制协议不便于前端或传统 HTTP 客户端直接调用。
gRPC-Gateway 是 Google 官方开源的插件,它通过 Protobuf 扩展注解,自动将 gRPC 服务转换为 RESTful HTTP/JSON 接口,让一套服务同时支持 gRPC 和 HTTP 两种调用方式,是微服务架构中解决 gRPC 兼容性问题的首选方案。
什么是 gRPC-Gateway?
gRPC-Gateway 是一个反向代理,它读取 Protobuf 文件中的 google.api.http 注解,自动生成 HTTP 接口代码,将 HTTP 请求转换为 gRPC 请求转发给后端服务,再将 gRPC 响应转换为 JSON 返回给客户端。
工作流程
- 定义 Protobuf:在 gRPC 服务定义中添加
google.api.http注解,指定 HTTP 方法、路径和请求体映射。 - 生成代码:使用
protoc-gen-grpc-gateway插件生成 HTTP 网关代码。 - 启动服务:同时启动 gRPC 服务和 gRPC-Gateway HTTP 服务。
- 请求处理:客户端发送 HTTP 请求 → Gateway 转换为 gRPC 请求 → 后端 gRPC 服务处理 → Gateway 转换为 JSON 响应 → 返回客户端。
编写 Protobuf 文件
这是 gRPC-Gateway 最关键的一步,通过 google.api.http 注解定义 HTTP 接口映射。
导入解析
// 导入 google/api/annotations.proto(必须)
import "google/api/annotations.proto";
定义消息
message User {
uint32 id = 1;
string username = 2;
string email = 3;
}
message GetUserRequest { uint32 user_id = 1; }
message GetUserResponse { User user = 1; }
message CreateUserRequest {
string username = 1;
string email = 2;
}
message CreateUserResponse { User user = 1; }
定义 gRPC 服务
service UserService {
// 1. GET 请求:路径参数映射
rpc GetUser(GetUserRequest) returns (GetUserResponse) {
// google.api.http 注解:定义 HTTP 接口
option (google.api.http) = {
get: "/api/v1/user/{user_id}" // {user_id} 映射到 GetUserRequest.user_id
};
}
// 2. POST 请求:请求体映射
rpc CreateUser(CreateUserRequest) returns (CreateUserResponse) {
option (google.api.http) = {
post: "/api/v1/user"
body: "*" // "*" 表示整个请求体映射到 CreateUserRequest
};
}
}
| 注解参数 | 作用 | 示例 |
|---|---|---|
get/post/put/delete/patch | 指定 HTTP 方法 | get: "/api/user/{id}" |
{field_name} | 路径参数映射到消息字段 | {user_id} → GetUserRequest.user_id |
body: "*" | 整个 HTTP 请求体映射到消息 | body: "*" |
body: "field_name" | 请求体映射到消息的指定字段 | body: "user" |
生成 Go 代码
# 在项目根目录执行,生成 gRPC 代码和 Gateway 代码
protoc -I . \
--go_out . --go_opt paths=source_relative \
--go-grpc_out . --go-grpc_opt paths=source_relative \
--grpc-gateway_out . --grpc-gateway_opt paths=source_relative \
proto/user.proto
生成文件:
user.pb.go:消息结构user_grpc.pb.go:gRPC 服务接口user_gw.pb.go:gRPC-Gateway HTTP 接口(核心)
实现 gRPC-Gateway
gRPC 服务端(可跳过):注意:需要异步启动 gRPC 服务
type UserServer struct {
pb.UnimplementedUserServiceServer
}
// 实现 GetUser 方法
func (s *UserServer) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.GetUserResponse, error) {
// 核心业务逻辑:查询数据库等
user := &pb.User{
Id: req.UserId,
Username: "zhangsan",
Email: "zhangsan@example.com",
}
return &pb.GetUserResponse{User: user}, nil
}
// 实现 CreateUser 方法
func (s *UserServer) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.CreateUserResponse, error) {
// 核心业务逻辑:创建用户等
user := &pb.User{
Id: 1,
Username: req.Username,
Email: req.Email,
}
return &pb.CreateUserResponse{User: user}, nil
}
func main() {
// 启动 gRPC 服务(逻辑简化)
lis, _ := net.Listen("tcp", ":50051")
s := grpc.NewServer()
pb.RegisterUserServiceServer(s, &UserServer{})
go s.Serve(lis) // 异步启动 gRPC 服务
// 启动 Gateway HTTP 服务(见下文)
// ...
}
启动Gateway服务
func main() {
// 1. 异步启动 gRPC 服务(见上文)
// ...
// 2. 创建 Gateway mux
gwMux := runtime.NewServeMux()
// 3. 配置 gRPC 服务端连接(开发环境跳过 TLS)
opts := []grpc.DialOption{
grpc.WithTransportCredentials(insecure.NewCredentials()),
}
// 4. 注册 UserService 的 Gateway handler
err := pb.RegisterUserServiceHandlerFromEndpoint(
context.Background(),
gwMux,
"localhost:50051", // gRPC 服务端地址
opts,
)
if err != nil {
panic(err)
}
// 5. 启动 HTTP 服务器
http.ListenAndServe(":8080", gwMux)
}
| 语法 | 说明 |
|---|---|
runtime.NewServeMux() | 创建 Gateway 的 HTTP 路由 mux |
pb.RegisterXxxServiceHandlerFromEndpoint(ctx, mux, endpoint, opts) | 注册 gRPC 服务的 Gateway handler |
http.ListenAndServe(addr, mux) | 启动 Gateway HTTP 服务 |
自定义 HTTP 响应格式
默认的 Gateway 响应格式可能不符合项目规范(如需要统一的 code/msg/data 结构),可以通过自定义 Marshaler 实现。
// 自定义响应格式
type CustomResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data any `json:"data"`
}
// 自定义 Marshaler(逻辑简化,实际需实现完整接口)
func main() {
// 创建 Gateway mux 时使用自定义 Marshaler
gwMux := runtime.NewServeMux(
runtime.WithMarshalerOption(runtime.MIMEWildcard, &CustomJSONMarshaler{}),
)
// ... 后续代码不变
}