gRPC教程 — grpc-gateway
- 1 前言
- 2 Grpc网关介绍
- 2.1 原因
- 2.2 补充
- 2.3 流程
- 2.4 流程图
- 3 环境配置
- 3.1 需要的依赖
- 3.1.1 proto转go
- 3.1.2 grpc
- 3.1.3 grpc-gateway
- 3.1.4 对客户端提供服务的API依赖
- 3.2 下载结果
- 4 代码
- 4.1 加入google/api/annotations.proto
- 4.2 在代码中的位置及整个项目结构
- 4.3 创建protobuf文件
- 4.4 将 `ProdGrpcGateWay.proto` 转成相关go文件
- 4.4.1 生成 `ProdGrpcGateWay.pb.go`、`ProdGrpcGateWay_grpc.pb.go` 的命令:
- 4.4.2 生成 grpc-gateway使用的 `ProdGrpcGateWay.pb.gw.go` 命令:
- 4.5 添加认证证书
- 4.6 服务端代码
- 4.7 客户端服务代码
- 5 测试
- 5.1 启动服务
- 5.2 访问clientServer服务的接口
- 5.3 测试结果
1 前言
本文使用的认证方式为 双向认证,也可改为其他认证方式,可参考 gRPC教程 — TLS单向认证、双向认证、Token认证、拦截器 进行改造,本文不再赘述。
代码:https://gitee.com/XiMuQi/go-grpc
2 Grpc网关介绍
2.1 原因
etcd3 API全面升级为gRPC后,同时还要提供REST API服务,维护两个版本的服务就显得不太合理,所以 grpc-gateway 诞生了。通过使用protobuf的自定义option实现了一个网关,服务端可以同时开启Grpc服务和HTTP服务,HTTP服务负责接收客户端请求,然后将请求信息转换protobuf格式作为 Grpc 请求数据,再发送至Grpc服务,HTTP服务等从Grpc服务获取响应后再转为JSON数据返回给客户端。
2.2 补充
Grpc-Gateway是Protocol Buffers编译器协议的一个插件。它读取Protobuf服务定义并生成一个反向代理服务器,该服务器将RESTful HTTP API转换为gRPC。这种服务是根据google.api.http annotations
注解生成的,所以在项目中会使用到该注解。
2.3 流程
请求流程,当客户端发送http请求时候,grpc-gateway接受请求,生成grpc的client去请求grpc的server端;grpc-gateway实际上做了反向代理的作用。因此会产生两个服务,一个是grpc-gateway产生的http服务,负责接受客户端的http请求,一个grpc的server服务,负责处理client端的请求。
2.4 流程图
3 环境配置
3.1 需要的依赖
3.1.1 proto转go
go get -u google.golang.org/protobuf/cmd/protoc-gen-go
3.1.2 grpc
go get -u google.golang.org/grpc/cmd/protoc-gen-go-grpc
3.1.3 grpc-gateway
go get -u github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway
3.1.4 对客户端提供服务的API依赖
go get -u github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2
3.2 下载结果
4 代码
4.1 加入google/api/annotations.proto
在1.2中我们讲到需要 google/api/annotations.proto
,所以还需要将这些文件到项目中,在前面环境配置完成后,去项目的 External Libraries
下 将grpc-gateway包中google包下的内容全部拷贝到项目的一个单独的目录下,注意是用 v1.16.0
版本下的,v2.10.2
下没有 。
图一
图二
4.2 在代码中的位置及整个项目结构
将 google
文件夹放到了 pbfiles
下:
4.3 创建protobuf文件
新建的 ProdGrpcGateWay.proto
在 pbfiles 目录下,与google是同级。有的 讲解文档 创建的是 Prod.proto
,其实都一样,但是在本项目中会出现一个小问题,等后面运行时可能会报:
{"code":12, "message":"method GetProdStock not implemented", "details":[]}
出现这个问题的原因是在本项目中集成了 gRPC教程 — TLS单向认证、双向认证、Token认证、拦截器 的代码,多个示例中可能会出现 protobuf
文件内容相同的情况,在实现 GetProdStock 方法时有可能实现的是别的示例接口中的,所以报这个错误。
解决办法
1、修改Prod.proto
里的方法名;(本文使用的是方法)
2、单独一个项目;
ProdGrpcGateWay.proto
内容:
syntax = "proto3"; //proto3的语法,不写会默认为proto2
package services; //包名,通过protoc生成go文件时使用
option go_package = "../service"; //添加生成go文件的路径
//必须要导入
import "google/api/annotations.proto";
message GrpcGateWayRequest {
int32 goods_id = 1; //传入的商品id
}
message GrpcGateWayResponse {
int32 goods_stock =1; //商品库存
}
service GrpcGateWayService {
rpc GetGrpcGateWayStock (GrpcGateWayRequest) returns (GrpcGateWayResponse){
option (google.api.http) = {
get: "/v1/prod/{goods_id}" //注意这里的路径参数要和上面 GrpcGateWayRequest 中定义的保持一致
};
}
}
4.4 将 ProdGrpcGateWay.proto
转成相关go文件
在 pbfiles目录下执行以下命令,注意不要写错!
4.4.1 生成 ProdGrpcGateWay.pb.go
、ProdGrpcGateWay_grpc.pb.go
的命令:
protoc --go_out=. --go-grpc_out=. *.proto
4.4.2 生成 grpc-gateway使用的 ProdGrpcGateWay.pb.gw.go
命令:
protoc --grpc-gateway_out=logtostderr=true:../service ProdGrpcGateWay.proto
4.5 添加认证证书
将在 双向认证 示例中的相关证书复制一份过来,本文是复制到了 keys2
下,关于生成相关证书的讲解可看前言中的相关链接。
4.6 服务端代码
server.go
package main
import (
"context"
"crypto/tls"
"crypto/x509"
"go-grpc/grpc-gateway/service"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/grpclog"
"io/ioutil"
"log"
"net"
)
const (
// Address gRPC服务地址
ServerAddress = "127.0.0.1:8888"
)
//1、声明一个server,里面是未实现的字段
type server struct {
service.UnimplementedGrpcGateWayServiceServer
}
//2、必须要实现在 ProdGrpcGateWay.proto 里声明的远程调用接口,否则客户端会报:
//rpc error: code = Unimplemented desc = method GetGrpcGateWayStock not implemente
func (s *server) GetGrpcGateWayStock(ctx context.Context, in *service.GrpcGateWayRequest) (*service.GrpcGateWayResponse, error) {
return &service.GrpcGateWayResponse{GoodsStock: in.GetGoodsId()}, nil
}
func main() {
//1、创建带ca证书验证的服务端
rpcServer := grpc.NewServer(grpc.Creds(GetServeCreds()))
//2、注册服务
service.RegisterGrpcGateWayServiceServer(rpcServer, &server{})
//3、设置传输协议和监听地址
listen, err := net.Listen("tcp", ServerAddress)
if err != nil {
log.Fatal("服务监听端口失败", err)
}
log.Println("Server listen on " + ServerAddress + " with TLS")
//4、启动服务
rpcServer.Serve(listen)
}
//加入服务端认证证书
func GetServeCreds() credentials.TransportCredentials {
// TLS认证
//从证书相关文件中读取和解析信息,得到证书公钥、密钥对
cert, err := tls.LoadX509KeyPair("grpc-gateway/keys2/server.pem", "grpc-gateway/keys2/server.key")
if err != nil {
grpclog.Fatalf("Failed to find server credentials %v", err)
}
certPool := x509.NewCertPool() //初始化一个CertPool
ca, err := ioutil.ReadFile("grpc-gateway/keys2/ca.pem")
if err != nil {
grpclog.Fatalf("Failed to find root credentials %v", err)
}
certPool.AppendCertsFromPEM(ca) //解析传入的证书,解析成功会将其加到池子中
creds := credentials.NewTLS(&tls.Config{ //构建基于TLS的TransportCredentials选项
Certificates: []tls.Certificate{cert}, //服务端证书链,可以有多个
ClientAuth: tls.RequireAndVerifyClientCert, //要求必须验证客户端证书
ClientCAs: certPool, //设置根证书的集合,校验方式使用 ClientAuth 中设定的模式
})
return creds
}
4.7 客户端服务代码
clientServer.go
:
package main
import (
"context"
"crypto/tls"
"crypto/x509"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"go-grpc/grpc-gateway/service"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/grpclog"
"io/ioutil"
"log"
"net/http"
)
const (
// ServerAddress gRPC服务地址
ServerAddress = "127.0.0.1:8888"
//ClientAddress 是浏览器等发送http请求时的地址
ClientAddress = "127.0.0.1:8080"
)
func main() {
ctx := context.Background()
ctx, cancelFunc := context.WithCancel(ctx)
defer cancelFunc()
//1、创建路由
mux := runtime.NewServeMux()
//2、加入认证证书
opt := []grpc.DialOption{grpc.WithTransportCredentials(GetClientCreds())}
//3、注册请求服务端的方法
err := service.RegisterGrpcGateWayServiceHandlerFromEndpoint(ctx, mux, ServerAddress, opt)
if err != nil {
log.Fatalf("cannot start grpc gateway: %v", err)
}
//4、启动并监听http请求
err = http.ListenAndServe(ClientAddress, mux)
if err != nil {
log.Fatalf("cannot listen and server: %v", err)
}
log.Println("ClientServer listen on " + ServerAddress + " with TLS")
}
//加入客户端认证证书
func GetClientCreds() credentials.TransportCredentials {
// TLS连接
//从证书相关文件中读取和解析信息,得到证书公钥、密钥对
cert, err := tls.LoadX509KeyPair("grpc-gateway/keys2/client.pem", "grpc-gateway/keys2/client.key")
if err != nil {
grpclog.Fatalf("Failed to find client credentials %v", err)
}
certPool := x509.NewCertPool()
ca, err := ioutil.ReadFile("grpc-gateway/keys2/ca.pem")
if err != nil {
grpclog.Fatalf("Failed to find root credentials %v", err)
}
certPool.AppendCertsFromPEM(ca)
creds := credentials.NewTLS(&tls.Config{
Certificates: []tls.Certificate{cert}, //客户端证书
ServerName: "ximu.info", //注意这里的参数为配置文件中所允许的ServerName,也就是其中配置的DNS...
RootCAs: certPool,
})
return creds
}
附: client.go
(测试与服务端通信是否正常的客户端)
package main
import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"go-grpc/grpc-gateway/service"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"io/ioutil"
"log"
)
const (
// Address gRPC服务地址
Address = "127.0.0.1:8888"
)
func main() {
// 证书认证-双向认证
// 从证书相关文件中读取和解析信息,得到证书公钥、密钥对
cert, _ := tls.LoadX509KeyPair("grpc-gateway/keys2/client.pem", "grpc-gateway/keys2/client.key")
// 创建一个新的、空的 CertPool
certPool := x509.NewCertPool()
ca, _ := ioutil.ReadFile("grpc-gateway/keys2/ca.pem")
//注意这里只能解析pem类型的根证书,所以需要的是ca.pem
// 尝试解析所传入的 PEM 编码的证书。如果解析成功会将其加到 CertPool 中,便于后面的使用
certPool.AppendCertsFromPEM(ca)
// 构建基于 TLS 的 TransportCredentials 选项
creds := credentials.NewTLS(&tls.Config{
// 设置证书链,允许包含一个或多个
Certificates: []tls.Certificate{cert},
ServerName: "ximu.info", //注意这里的参数为配置文件中所允许的ServerName,也就是其中配置的DNS...
RootCAs: certPool,
})
//1、 建立连接
conn, err := grpc.Dial(Address, grpc.WithTransportCredentials(creds))
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
request := &service.GrpcGateWayRequest{
GoodsId: 123,
}
// 2. 调用 ProdGrpcGateWay_grpc.pb.go 中的 NewGrpcGateWayServiceClient 方法建立客户端
query := service.NewGrpcGateWayServiceClient(conn)
//3、调用rpc方法
res, err := query.GetGrpcGateWayStock(context.Background(), request)
if err != nil {
log.Fatal("调用gRPC方法错误: ", err)
}
fmt.Println("grpc-gateway:调用gRPC方法成功,GoodsStock = ", res)
}
5 测试
5.1 启动服务
先启动 server.go
,再启动 clientServer.go
5.2 访问clientServer服务的接口
http://localhost:8080/v1/prod/25
5.3 测试结果