gRPC-Gateway 是protocalBufffer的编译插件,根据protobuf的服务定义自动生成反向代理服务器,并将Restful Http API 转化为 gRPC通信协议。反向代理服务器根据 google.api.http 注解生成。
gRPC底层是使用HTTP2 协议进行数据通信,固定为HTTP POST请求,通过gateway 方向代理可以将目前通用的http1.1转换为HTTP2协议 ,并且根据自身需要构建出符合业务场景的Http Restful Api.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9HNXxQ0g-1668943700569)(https://github.com/grpc-ecosystem/grpc-gateway/raw/master/docs/assets/images/architecture_introduction_diagram.svg)]
# 1. 创建项目
mkdir grpc-gateway
# 2. 项目初始化
cd grpc-gateway
go mod init example/grpc-gateway/greetings
# 1. 创建proto文件夹
mkdir -p proto/google/api
# 2. 下载google依赖 放置 api 文件夹
cd proto/google/api
wget https://github.com/googleapis/googleapis/tree/master/google/api/http.proto
wget https://github.com/googleapis/googleapis/tree/master/google/api/annotations.proto
在proto文件夹下,创建example.proto
syntax = "proto3";
// go_package example/grpc-gateway/greetings 跟模块初始化名称一致方便引用
// proto 为文件目录
option go_package = "example/grpc-gateway/greetings/proto";
package proto;
import "google/api/annotations.proto";
// The greeting service definition.
service HttpGreeter {
// Sends a greeting
rpc SayHello (HttpHelloRequest) returns (HttpHelloReply) {
option (google.api.http) = {
post: "/post_hello",
body: "*"
};
}
rpc SayGetHello (HttpHelloRequest) returns (HttpHelloReply) {
option (google.api.http) = {
get: "/get_hello"
};
}
}
// The request message containing the user's name.
message HttpHelloRequest {
string name = 1;
}
// The response message containing the greetings
message HttpHelloReply {
string message = 1;
}
安装gateway依赖包
go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@v2
编译脚本
#在项目跟目录下执行
protoc -I=proto \
--go_out=proto --go_opt=paths=source_relative \
--go-grpc_out=proto --go-grpc_opt=paths=source_relative \
--grpc-gateway_out=proto --grpc-gateway_opt=paths=source_relative \
example.proto
参数说明:
最后使用脚本,下载相关依赖: go mod tidy
完整项目结构如下
├── go.mod
├── go.sum
├── proto
│ ├── example.pb.go
│ ├── example.pb.gw.go
│ ├── example.proto
│ ├── example_grpc.pb.go
│ └── google
│ └── api
│ ├── annotations.proto
│ └── http.proto
├── proxy
│ ├── grpc
│ │ └── server.go
│ └── http
│ └── server.go
└── server
└── grpcServer.go
package main
import (
"context"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
pb "google.golang.org/grpc/examples/httpget/proto"
"log"
"net"
"net/http"
)
type server struct {
pb.UnimplementedHttpGreeterServer
}
func NewServer() *server {
return &server{}
}
func (s *server) SayHello(ctx context.Context, in *pb.HttpHelloRequest) (*pb.HttpHelloReply, error) {
return &pb.HttpHelloReply{Message: in.Name + " world, post HttpRequest"}, nil
}
func (s *server) SayGetHello(ctx context.Context, in *pb.HttpHelloRequest) (*pb.HttpHelloReply, error) {
return &pb.HttpHelloReply{Message: in.Name + " world, get HttpRequest"}, nil
}
func main() {
lis, err := net.Listen("tcp", ":9999")
if err != nil {
log.Fatalln("Failed to listen:", err)
}
s := grpc.NewServer()
pb.RegisterHttpGreeterServer(s, &server{})
log.Println("Serving gRPC on port 9999")
go func() {
log.Fatalln(s.Serve(lis))
}()
// 构建grpc服务上下文
conn, err := grpc.DialContext(
context.Background(),
"0.0.0.0:9999",
grpc.WithBlock(),
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
log.Fatalln("Failed to dial server:", err)
}
gwmux := runtime.NewServeMux()
// 注册Greeter
err = pb.RegisterHttpGreeterHandler(context.Background(), gwmux, conn)
if err != nil {
log.Fatalln("Failed to register gateway:", err)
}
gwServer := &http.Server{
Addr: ":8090",
Handler: gwmux,
}
// 8090端口提供gRPC-Gateway服务
log.Println("Serving gRPC-Gateway on http://0.0.0.0:8090")
log.Fatalln(gwServer.ListenAndServe())
}
出现代码报错,请执行:go mod tidy
curl -v POST -k http://localhost:8090/post_hello -d '{"name": " hello"}'
curl -v http://localhost:8090/get_hello/kobe
之前为了测试方便,服务端代码将gRPC代码、gateway代码结合在一起,这在真实的项目中是不可取的,理由是:任何一方出现系统问题都会导致整个服务不可用。分开后的代码如下:
package main
import (
"context"
pb "example/grpc-gateway/greetings/proto"
"google.golang.org/grpc"
"log"
"net"
)
const (
port = ":9999"
)
type server struct {
pb.UnimplementedHttpGreeterServer
}
func NewServer() *server {
return &server{}
}
func (s *server) SayHello(ctx context.Context, in *pb.HttpHelloRequest) (*pb.HttpHelloReply, error) {
return &pb.HttpHelloReply{Message: in.Name + " world, post HttpRequest"}, nil
}
func (s *server) SayGetHello(ctx context.Context, in *pb.HttpHelloRequest) (*pb.HttpHelloReply, error) {
return &pb.HttpHelloReply{Message: in.Name + " world, get HttpRequest"}, nil
}
func main() {
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterHttpGreeterServer(s, &server{})
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
package main
import (
"flag"
"net/http"
pb "example/grpc-gateway/greetings/proto"
"github.com/golang/glog"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"golang.org/x/net/context"
"google.golang.org/grpc"
)
var (
tasklistEndpoint = flag.String("tasklist_endpoint", "localhost:9999", "endpoint of YourService")
)
func run() error {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
mux := runtime.NewServeMux()
opts := []grpc.DialOption{grpc.WithInsecure()}
//分离后的代码 使用的api不一致,仅此而已
err := pb.RegisterHttpGreeterHandlerFromEndpoint(ctx, mux, *tasklistEndpoint, opts)
if err != nil {
return err
}
return http.ListenAndServe(":8090", mux)
}
func main() {
flag.Parse()
defer glog.Flush()
if err := run(); err != nil {
glog.Fatal(err)
}
}