https://github.com/grpc-ecosystem/grpc-gateway
单纯用grpc实现的服务端代码,只能用grpc客户端调用,(比如用 gRPC 官方提供的 Go、Python、Java 等 SDK 进行调用)现实开发中,不是所有客户端都支持 gRPC,比如:
对比项 | 直接使用 gRPC | 使用 gRPC-Gateway |
---|---|---|
协议 | gRPC(HTTP/2 + Protobuf) | HTTP/JSON 转 gRPC |
客户端支持 | 仅限 gRPC 客户端(Go、Python、Java…) | 兼容 HTTP 客户端(curl、浏览器、前端) |
调用方式 | 需要 gRPC Stub(代码生成的客户端) | 直接使用 HTTP/JSON |
网络环境 | 需要支持 HTTP/2(可能受限于代理、网络设备) | 兼容 HTTP/1.1,适用于任何 HTTP 设备 |
gRPC-Gateway 主要用于 让 gRPC 服务同时支持 HTTP/JSON 调用,方便 Web 前端、第三方系统或传统 REST 客户端对接。如果你的 gRPC 服务只在后端内部使用,不需要对外提供 REST API,那就可以不用 gRPC-Gateway。
go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@latest
检查版本
protoc-gen-grpc-gateway --version
代码生成:根据 .proto
文件中的 HTTP 注解,生成反向代理代码 .pb.gw.go
协议转换:将 HTTP 请求映射到对应的 gRPC 方法,并处理参数和响应转换。
syntax = "proto3";
package echo;
option go_package = "echo/proto";
message User{
int64 id = 1;
string name = 2;
int32 age = 3;
string phone = 4;
Addr addr = 5;
}
message Addr{
string province = 1;
string city = 2;
string county = 3;
}
// 在 gRPC-Gateway 中,HTTP 方法(如 GET 和 POST)的映射是由 grpc-gateway 生成的代码和 .proto 文件中的选项配置决定的。
// 具体来说,grpc-gateway 使用 .proto 文件中的 google.api.http 选项来定义每个 gRPC 方法对应的 HTTP 方法和路径。
// 当 grpc-gateway 没有找到 google.api.http 选项时,它会根据 gRPC 方法的签名(即方法名和参数类型)生成默认的 HTTP 映射规则。
// GET 请求:如果 gRPC 方法只接收一个请求消息并且返回一个响应消息,并且请求消息中的字段可以作为 URL 路径参数或查询参数,则 grpc-gateway 会为该方法生成一个 GET 请求。
// POST 请求:对于所有其他情况,默认生成 POST 请求。
// 这里则都默认生成post而不生成get
// 路径参数:适合唯一标识符,不宜包含多个字段或复杂结构。
// 查询参数:适合少量的过滤条件或附加信息,不宜包含复杂数据结构。
// User 消息:包含多个字段和嵌套结构,不适合直接映射为路径参数或查询参数,
// 因此 grpc-gateway 默认选择 POST 请求以确保完整性和灵活性。
service Echo{
rpc Get(User) returns (User) {}
rpc AddOrUpdate(User) returns (User) {}
rpc Delete(User) returns (User) {}
}
main
包功能:应用程序的入口点,负责启动 gRPC 服务器和 gRPC-Gateway。
main.go
功能:启动 gRPC 服务器和 gRPC-Gateway,并处理信号以优雅地关闭服务器。
关键点:
run()
函数创建并启动 gRPC 服务器,监听在 :50051
端口。gateway.Run()
启动 HTTP 服务器,监听在 :8081
端口。signal.NotifyContext
监听中断信号(如 Ctrl+C),以便优雅地关闭服务器。func main() {
go func() {
if err := run(); err != nil {
log.Fatalln(err)
}
}()
time.Sleep(time.Second)
go func() {
if err := gateway.Run(); err != nil {
log.Fatalln(err)
}
}()
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
defer stop()
<-ctx.Done()
}
func run() error {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalln(err)
}
s := grpc.NewServer()
userServiceServer := server.NewServer()
proto.RegisterEchoServer(s, userServiceServer)
return s.Serve(lis)
}
server
包功能:实现了 gRPC 服务的具体逻辑。
server.go
功能:定义了 echoServer
结构体及其方法实现。
关键点:
echoServer
结构体:嵌套了 UnimplementedEchoServer
,确保未实现的方法不会导致 panic。通过值嵌入而不是指针嵌入,避免 nil 指针解引用问题。NewServer
函数:创建并返回一个新的 EchoServer
实例。Get
, AddOrUpdate
, Delete
方法简单地打印传入的 User
对象并返回它。type echoServer struct {
proto.UnimplementedEchoServer
}
func NewServer() proto.EchoServer {
return &echoServer{}
}
func (s *echoServer) Get(ctx context.Context, in *proto.User) (*proto.User, error) {
fmt.Printf("#{in}\n")
return in, nil
}
func (s *echoServer) AddOrUpdate(ctx context.Context, in *proto.User) (*proto.User, error) {
fmt.Printf("#{in}\n")
return in, nil
}
func (s *echoServer) Delete(ctx context.Context, in *proto.User) (*proto.User, error) {
fmt.Printf("#{in}\n")
return in, nil
}
proto
包功能:定义了 gRPC 服务的消息格式和服务接口。
echo.proto
功能:定义了 User
和 Addr
消息以及 Echo
服务。
关键点:
User
和 Addr
消息定义了用户和地址的数据结构。Echo
服务定义了三个 RPC 方法:Get
, AddOrUpdate
, Delete
,每个方法接收和返回 User
消息。User
消息包含多个字段和嵌套结构,不适合直接映射为路径参数或查询参数,因此 grpc-gateway
默认选择 POST 请求以确保完整性和灵活性。syntax = "proto3";
package echo;
option go_package = "echo/proto";
message User {
int64 id = 1;
string name = 2;
int32 age = 3;
string phone = 4;
Addr addr = 5;
}
message Addr {
string province = 1;
string city = 2;
string county = 3;
}
service Echo {
rpc Get(User) returns (User) {}
rpc AddOrUpdate(User) returns (User) {}
rpc Delete(User) returns (User) {}
}
gateway
包功能:配置并启动 gRPC-Gateway,将 HTTP 请求转换为 gRPC 调用。
gateway.go
功能:配置并启动 gRPC-Gateway,将 HTTP 请求转发给 gRPC 服务器。
关键点:
runtime.NewServeMux()
创建一个 HTTP 处理多路复用器。grpc.WithTransportCredentials(insecure.NewCredentials())
表示使用不安全的连接(仅用于开发环境)。gw.RegisterEchoHandlerFromEndpoint
将 gRPC 方法映射为 HTTP 请求,连接到 gRPC 服务器(默认端口 :50051
)。:8081
端口。func Run() error {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
mux := runtime.NewServeMux()
opts := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}
err := gw.RegisterEchoHandlerFromEndpoint(ctx, mux, *grpcServerEndpoint, opts)
if err != nil {
return err
}
return http.ListenAndServe(":8081", mux)
}
main
包:应用程序的入口点,负责启动 gRPC 服务器和 gRPC-Gateway,并处理信号以优雅地关闭服务器。server
包:实现了 gRPC 服务的具体逻辑,提供了 Get
, AddOrUpdate
, Delete
方法。proto
包:定义了 gRPC 服务的消息格式和服务接口,确保客户端和服务器之间的通信协议一致。gateway
包:配置并启动 gRPC-Gateway,将 HTTP 请求转换为 gRPC 调用,提供 RESTful API 接口。https://github.com/0voice