Golang gRPC-Gateway:gRPC转换为HTTP协议对外提供服务

1、概述

gRPC常用于服务端之间的相互调用,如果想把服务暴露给前端,虽然动手修改服务端也能实现,但似乎增加了不少工作量,此时还可以选择gRPC-Gateway方式来快速将gRPC服务以http的方式暴露出来;

gRPC-Gateway 是 Google protocol buffers compiler protoc 的插件。 它读取 protobuf service 定义并生成反向代理服务器( reverse-proxy server) ,这个反向代理运行起来后,对外提供RESTful服务,收到RESTful请求后通过gRPC调用原来的gRPC服务。该服务器是根据服务定义中的 google.api.http 批注(annotations)生成的,gRPC-Gateway原理如下图:

 

本文展示了gRPC-Gateway v2环境搭建、开发、验证的整个过程,由以下步骤组成:

  1. 安装gRPC-Gateway v2插件;
  2. 编写proto文件;
  3. 根据proto文件生成gRPC、gRPC-Gateway源码;
  4. 添加业务代码;
  5. 编译、运行、验证;

2、环境配置

2.1 安装配置protocol buffers和protoc-gen-go

步骤参见:Mac下安装配置Protocol Buffers 

2.2 安装配置gRPC

步骤参见:Golang gRPC概述及入门示例 

2.3 安装配置gRPC-Gateway插件

下载当前最新稳定版本的gRPC-Gateway v2插件

1
go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@latest

3、gRPC-Gateway示例

在开始开发之前,先说说我们的目标。

在这个grpc-gateway-practice项目中,我希望实现一个功能,客户端通过restful接口发送消息给网关,然后网关作为gRPC客户端将消息代理到gRPC服务端,gRPC服务端收到消息后返回响应给网关,然后网关再将消息返回给客户端。

项目结构如下:

注意: 这是整个项目所有文件生成完后的结构,所有.proto和.go文件都是在3.1及其后步骤生成的,go.mod内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module grpc-gateway-practice
 
go 1.17
 
require (
    github.com/grpc-ecosystem/grpc-gateway/v2 v2.10.3
    google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd
    google.golang.org/grpc v1.46.2
    google.golang.org/protobuf v1.28.0
)
 
require (
    github.com/golang/protobuf v1.5.2 // indirect
    golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
    golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
    golang.org/x/text v0.3.7 // indirect
)

3.1 定义服务

正如前面所说的,在开发serverclient之前,我们需要先定义服务。这里直接粘贴下示例proto文件内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 文件路径grpc-gateway-practice/pkg/proto/message.proto
 
syntax = "proto3";
option go_package = "../pb;pb";
 
import "google/api/annotations.proto";
 
message MessageResponse {
  string responseSomething = 1;
}
 
message MessageRequest {
  string saySomething = 1;
}
 
// 定义的服务名
service MessageSender {
  // 具体的远程服务方法
  rpc Send(MessageRequest) returns (MessageResponse) {
    // gRPC-Gateway 批注, 定义HTTP->gRPC 映射,此示例 POST /sendMessage -> gRPC Send
    option (google.api.http) = {
      post: "/sendMessage"
      body: "*"
    };
  }
}

很容易可以看出,我们定义了一个service,称为MessageSender,这个服务中有一个rpc方法,名为Send。这个方法会发送一个MessageRequest,然后返回一个MessageResponse

注意观察message.proto文件内容,和之前rpc服务的proto文件相比rpc服务方法里面多了如下代码片段:

1
2
3
4
5
// gRPC-Gateway 批注, 定义HTTP->gRPC 映射,此示例 POST /sendMessage -> gRPC Send
    option (google.api.http) = {
      post: "/sendMessage"
      body: "*"
    };

此代码片段为 gRPC-Gateway 批注,批注定义了 gRPC 服务如何映射到 JSON 请求和响应。
使用 protocol buffers 时,每个 RPC 必须使用 google.api.http 批注定义 HTTP 方法和路径。因此,我们需要将 google/api/annotations.proto 导入添加到 proto 文件中,google/api/annotations.proto文件引自于github.com/googleapis/googleapis包。

接着在grpc-gateway-practice/pkg/proto目录下执行如下命令:

1
2
3
protoc -I=. -I=$GOPATH/pkg/mod/github.com/googleapis/googleapis@v0.0.0-20220602015621-13f9b8908d84 --go_out=. message.proto
protoc -I=. -I=$GOPATH/pkg/mod/github.com/googleapis/googleapis@v0.0.0-20220602015621-13f9b8908d84 --go-grpc_out=. message.proto
protoc -I=. -I=$GOPATH/pkg/mod/github.com/googleapis/googleapis@v0.0.0-20220602015621-13f9b8908d84 --grpc-gateway_out=. message.proto  

这三条命令会grpc-gateway-practice/pkg/pb目录中分别生成message.pb.go、message_grpc.pb.go、message.pb.gw.go这三个文件。在这三个文件中,包含了我们定义方法的go语言实现,也包含了我们定义的请求与相应的go语言实现。

当然也可以一条命令就把这三个文件生成出来:

1
protoc -I=. -I=$GOPATH/pkg/mod/github.com/googleapis/googleapis@v0.0.0-20220602015621-13f9b8908d84 --go_out=.  --go-grpc_out=. --grpc-gateway_out=. message.proto

3.2 服务端

3.2.1 实现服务定义的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 文件路径grpc-gateway-practice/pkg/serviceImpl/MessageSenderServerImpl.go
 
package serviceImpl
 
import (
    "context"
    "grpc-gateway-practice/pkg/pb"
    "log"
)
 
type MessageSenderServerImpl struct {
    *pb.UnimplementedMessageSenderServer
}
 
func (MessageSenderServerImpl) Send(context context.Context, request *pb.MessageRequest) (*pb.MessageResponse, error) {
    log.Println("receive message:", request.GetSaySomething())
    resp := &pb.MessageResponse{}
    resp.ResponseSomething = "roger that!"
    return resp, nil
}

很容易可以看出,MessageSenderServerImpl实现了MessageSenderServer接口,并实现定义。也就是说,这一部分是需要我们在Server端实现这个send方法的。

3.2.2 gRPC服务端注册定义的服务并监听

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 文件路径grpc-gateway-practice/pkg/service/main.go
 
package main
 
import (
    "google.golang.org/grpc"
    "grpc-gateway-practice/pkg/pb"
    "grpc-gateway-practice/pkg/serviceImpl"
    "log"
    "net"
)
 
func main() {
    srv := grpc.NewServer()
    pb.RegisterMessageSenderServer(srv,serviceImpl.MessageSenderServerImpl{})
    listener, err := net.Listen("tcp", ":8002")
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
 
    err = srv.Serve(listener)
    if err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}

很容易可以看出,我们在这一部分创建了一个grpcServer,然后注册了我们的Service,在注册函数的第二个参数中,我们传进去了一个MessageSenderServerImpl实例。

监听过程跟golang的web服务器是很像的,也是创建Handler,然后对端口进行监听,监听8002端口的TCP连接,然后启动服务器。

至此,服务端开发完毕。

3.3 gRPC-Gateway(反向代理Reverse Proxy)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 文件路径grpc-gateway-practice/pkg/gateway/main.go
 
package main
 
import (
    "context"
    "flag"
    "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
    "google.golang.org/grpc"
    "grpc-gateway-practice/pkg/pb"
    "log"
    "net/http"
)
 
var (
    echoEndpoint = flag.String("echo_endpoint", "localhost:8002", "endpoint of grpcService")
)
 
func main() {
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()
    mux := runtime.NewServeMux()
    opts := []grpc.DialOption{grpc.WithInsecure()}
    err := pb.RegisterMessageSenderHandlerFromEndpoint(ctx, mux, *echoEndpoint, opts)
 
    if err != nil {
        log.Fatalf("failed to RegisterMessageSenderHandlerFromEndpoint: %v", err)
    }
 
    err = http.ListenAndServe(":9090", mux)
    if err != nil {
        log.Fatalf("failed to Listen: %v", err)
    }
}

以上代码需要注意以下三点:

  • 第一处要注意的地方,是调用http.ListenAndServe监听9090端口,这是对外提供RESTful服务的端口;
  • 第二处要注意的地方,是echoEndpoint配置了将外部RESTful请求转发到server.go提供gRPC服务的入口处;
  • 第三处要注意的地方,是调用了自动生成代码中的RegisterMessageSenderHandlerFromEndpoint方法完成上下游调用的绑定;

4、验证

分别将gRPC服务端和gRPC-Gateway都跑起来,然后,我们使用 cURL 发送 HTTP 请求:

1
2
3
4
curl \
> -X POST \
> -d '{"saySomething": "hello go-grpc-gateway"}' \
> 127.0.0.1:9090/sendMessage

收到响应如下,这是来自gRPC服务端的内容,可见http请求通过Reserve Proxy到达了真实的gRPC服务提供者,并顺利返回给调用方:

1
{"responseSomething":"roger that!"

去看gRPC服务端的日志如下:

至此,gRPC-Gateway示例成功。

参考:https://blog.csdn.net/o__cc/article/details/115562868

参考:https://xinchen.blog.csdn.net/article/details/111399854

posted @   人艰不拆_zmc  阅读(2543)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
历史上的今天:
2020-06-02 centos7安装部署kafka_2.13-2.4.1集群
2020-06-02 (转)zookeeper-3.5.5安装报错:找不到或无法加载主类 org.apache.zookeeper.server.quorum.QuorumPeerMain
2018-06-02 (转)Fabric CA环境的集成
点击右上角即可分享
微信分享提示