gRPC-GateWay入门

在某些需要维护向后兼容性或者那些不支持 gRPC 的客户端,需要提供传统的 HTTP/JSON API。但为RPC服务再编写一个服务只为对外提供 HTTP/JSON API 是一项相当耗时和乏味的任务。

GRPC-Gateway 是 Google protocol buffers 编译器 protoc 的一个插件,能同时提供 gRPC 和 RESTful 风格的 API。它读取 Protobuf 服务定义并生成一个反向代理服务器,该服务器将 RESTful HTTP API 转换为 gRPC。此服务器根据 gRPC 定义中的自定义选项生成。

快速上手

创建gw目录,里面创建pb子目录,pb里新建hello.proto

ini 复制代码
syntax = "proto3";

package helloworld;

option go_package="hello/pb";

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

生成代码(gw目录下执行)

css 复制代码
protoc -I=./  --go_out=pb --go_opt=module="hello/pb" --go-grpc_out=pb --go-grpc_opt=module="hello/pb"  pb/hello.proto

server端(gw目录下)

go 复制代码
type server struct {
	pb.UnimplementedGreeterServer
}

func NewServer() *server {
	return &server{}
}

func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
	return &pb.HelloReply{Message: in.Name + " world"}, nil
}

func main() {
	// Create a listener on TCP port
	lis, err := net.Listen("tcp", ":8080")
	if err != nil {
		log.Fatalln("Failed to listen:", err)
	}

	s := grpc.NewServer()
	pb.RegisterGreeterServer(s, &server{})
	log.Println("Serving gRPC on 0.0.0.0:8080")
	log.Fatal(s.Serve(lis))
}

现在已经有了 gRPC 服务器,接下来需要添加 gRPC-Gateway 注释。注释定义了 gRPC 服务如何映射到 JSON 请求和响应。使用 protobuf时,每个 RPC 服务必须使用 google.api.HTTP 注释来定义 HTTP 方法和路径。

google/api/http.proto 导入到 proto 文件中。还需要添加所需的 HTTP-> gRPC 映射。在本例中,我们将 POST /v1/example/echo 映射到 SayHello RPC。

修改后的hello.proto文件:

arduino 复制代码
import "google/api/annotations.proto";

// 定义一个Greeter服务
service Greeter {
  // 打招呼方法
  rpc SayHello (HelloRequest) returns (HelloReply) {
    // 这里添加了google.api.http注释
    option (google.api.http) = {
        post: "/v1/example/echo"
        body: "*"
    };
  }
}

现在已经将 gRPC-Gateway 注释添加到 proto 文件中,接下来需要使用 gRPC-Gateway 生成器来生成存根。

googleapis 的一个子集从官方库复制到目录中。拷贝后的目录:

go 复制代码
greeter
├── go.mod
├── go.sum
├── main.go
└── proto
    ├── google
    │   └── api
    │       ├── annotations.proto
    │       └── http.proto
    └── helloworld
        ├── hello_world.pb.go
        ├── hello_world.proto
        └── hello_world_grpc.pb.go

安装protoc-gen-grpc-gateway插件来生成对应的 grpc-gateway 代码。

bash 复制代码
go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@v2

现在将 gRPC-Gateway 生成器添加到 protoc 的调用命令中:

css 复制代码
protoc -I=./  --go_out=pb --go_opt=module="hello/pb" --go-grpc_out=pb --go-grpc_opt=module="hello/pb" --grpc-gateway_out=pb --grpc-gateway_opt=module="hello/pb" pb/hello.proto

执行上述命令应该会生成一个 *.gw.pb.go 文件。

还需在 main.go 文件中添加和启动gRPC-Gateway mux。按如下代码所示修改我们的main函数。

go 复制代码
import (
	"context"
	"log"
	"net"
	"net/http"

	"gw/pb"

	"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

type server struct {
	pb.UnimplementedGreeterServer
}

func NewServer() *server {
	return &server{}
}

func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
	return &pb.HelloReply{Message: in.Name + " world"}, nil
}

func main() {
	lis, err := net.Listen("tcp", ":8080")
	if err != nil {
		log.Fatalln("Failed to listen:", err)
	}

	s := grpc.NewServer()
	pb.RegisterGreeterServer(s, &server{})
	log.Println("Serving gRPC on 0.0.0.0:8080")
	// log.Fatal(s.Serve(lis))

	go func ()  {
		log.Fatalln(s.Serve(lis))
	}()

	conn, _ := grpc.DialContext(context.Background(), "localhost:8080", grpc.WithBlock(), grpc.WithTransportCredentials(insecure.NewCredentials()))
	
	gwmux := runtime.NewServeMux()
	pb.RegisterGreeterHandler(context.Background(),gwmux, conn)

	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())
}

注意

  1. 导入的"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"是v2版本。
  2. 需要使用单独的goroutine启动gRPC服务。

启动服务后使用 curl 发送 HTTP 请求:

bash 复制代码
curl -X POST -k http://localhost:8090/v1/example/echo -d '{"name": " hello"}'

得到响应结果。

json 复制代码
{"message":"hello world"}

同一个端口提供HTTP API和gRPC API

上面的程序在8080端口提供了gRPC API,在8090端口提供了HTTP API。有些场景希望由同一个端口同时提供gRPC API和HTTP API两种服务,由请求方来决定具体使用哪个协议。

下面的代码将同时在本机的8091端口对外提供gRPC API和HTTP API服务。

因为示例中没有启用 TLS加密,所以使用h2c包实现对HTTP/2的支持。h2c 协议是 HTTP/2的非 TLS 版本。

go 复制代码
func listenInSamePort(){
	lis, _ := net.Listen("tcp", ":8091")
	s := grpc.NewServer()
	pb.RegisterGreeterServer(s, &server{})
	gwmux := runtime.NewServeMux()
	dops := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}
	pb.RegisterGreeterHandlerFromEndpoint(context.Background(), gwmux, ":8091", dops)
	mux := http.NewServeMux()
	mux.Handle("/", gwmux)
	gwServer := &http.Server{
		Addr: ":8091",
		Handler: grpcHandlerFunc(s, mux),
	}
	log.Panicln("serving on http://127.0.0.1:8091")
	log.Panicln(gwServer.Serve(lis))
}

func grpcHandlerFunc(grpcServer *grpc.Server, otherHandler http.Handler) http.Handler {
	return h2c.NewHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.ProtoMajor == 2 && strings.Contains(r.Header.Get("Content-Type"), "application/grpc") {
			grpcServer.ServeHTTP(w, r)
		}else{
			otherHandler.ServeHTTP(w, r)
		}
	}), &http2.Server{})	
}

func main(){
    listenInSamePort()
}

测试:

go 复制代码
// grpc client
func main(){
	conn, _ := grpc.Dial("localhost:8091", grpc.WithTransportCredentials(insecure.NewCredentials()))
	defer conn.Close()

	client := pb.NewGreeterClient(conn)
	req := &pb.HelloRequest{Name: "yesssss"}
	reply, _ := client.SayHello(context.Background(), req)
	fmt.Println(reply.GetMessage())
}

http

bash 复制代码
curl -X POST -k http://127.0.0.1:8091/v1/example/echo -d '{"name": " hello"}'
相关推荐
磐石区6 天前
gRPC etcd 服务注册与发现、自定义负载均衡
服务发现·负载均衡·etcd·grpc·picker
Mindfulness code11 天前
使用 gRPC
开发语言·grpc
crossoverJie16 天前
OpenTelemetry 实战:gRPC 监控的实现原理
grpc·opentelemetry
czl3891 个月前
gRPC golang开发实践
golang·grpc·protobuf
AskHarries2 个月前
Spring Boot集成grpc快速入门demo
java·spring boot·后端·grpc
Donkor-3 个月前
grpc学习golang版( 一、基本概念与安装 )
rpc·golang·grpc
Donkor-3 个月前
grpc学习golang版( 五、多proto文件示例 )
开发语言·学习·golang·grpc
Donkor-3 个月前
grpc学习golang版( 三、proto文件数据类型 )
开发语言·学习·golang·grpc
Donkor-3 个月前
grpc学习golang版(六、服务器流式传输)
服务器·学习·golang·grpc
dapeng-大鹏3 个月前
Gone框架介绍29 - 在Gone中使用gRPC通信
golang·grpc·gone