使用Golang中的gRPC和gRPC-Gateway进行集成

发表时间: 2023-12-10 06:54

1.本节知识点

  • 了解gRPC与gRPC-Gateway
  • 了解buf
  • proto文件配置
  • 代码演示

2. 了解gRPC与gRPC-Gateway

gRPC: gRPC(gRPC Remote Procedure Calls)是由Google开发的开源RPC(远程过程调用)框架,它使用Protocol Buffers作为接口定义语言。

gRPC特点

  1. 基于HTTP/2: gRPC使用HTTP/2作为传输协议,提供了低延迟、多路复用和流控制等特性。HTTP/2相较于旧版本的HTTP,更加高效和性能更好。
  2. 多语言支持: gRPC支持多种编程语言,包括但不限于C, C++, Java, Python, Go, Ruby,以及其他一些语言。
  3. IDL(接口定义语言): 使用Protocol Buffers(ProtoBuf)作为接口定义语言,这使得定义服务和消息变得简单、清晰,并且具有很好的可读性。
  4. 双向流和流控制: gRPC支持双向流通信,服务端和客户端可以同时发送多个消息。同时,它还提供了流控制机制,允许客户端和服务器以可控制的方式进行通信。
  5. 自动代码生成: 根据服务和消息的ProtoBuf定义,gRPC可以生成用于不同编程语言的客户端和服务器端的代码,这样可以在不同的语言中无缝调用gRPC服务。
  6. 支持拦截器: gRPC允许使用拦截器(Interceptors)来处理请求和响应。这使得可以在请求和响应的不同阶段进行自定义处理。
  7. 跨语言、跨平台: 由于使用了标准的ProtoBuf和HTTP/2,gRPC可以在不同语言和平台之间进行通信,使得各种系统和服务能够协同工作。

gRPC-Gateway: 是一个用于将 gRPC 服务转换为 RESTful JSON API 的代理工具,使得可以通过HTTP/JSON 访问原本基于 gRPC 的服务

gRPC-Gateway特点

  1. RESTful JSON API: gRPC-Gateway 提供了一种将 gRPC 服务转换为 RESTful JSON API 的方法。这样可以方便地与不支持 gRPC 的客户端进行通信。
  2. 自动生成: gRPC-Gateway 使用 Protocol Buffers 文件中的 gRPC 服务定义,并通过代码生成的方式创建反向代理。这意味着你可以专注于定义 gRPC 服务,而无需手动编写 HTTP 接口。
  3. 基于 HTTP/JSON: 通过 gRPC-Gateway,你可以使用 HTTP/JSON 协议而不是 gRPC 协议来与服务进行通信。这对于与一些仅支持 HTTP/JSON 的工具和框架进行集成非常有用。
  4. Swagger 文档生成: gRPC-Gateway 支持生成 Swagger 文档,使得你可以轻松地了解和测试你的 API。
  5. 多语言支持: gRPC-Gateway 支持多种编程语言,因此你可以在多种语言中使用 gRPC 服务,同时提供 HTTP/JSON 接口。
  6. 反向代理服务: gRPC-Gateway 充当 gRPC 服务的反向代理,将 HTTP 请求转发到对应的 gRPC 服务,并将 gRPC 响应转换为 HTTP 响应。

3. 了 解buf工具

buf 是一个用于 Protocol Buffers 的构建和 lint 工具。它提供了一种规范和一致性的方式来管理 Protocol Buffers 文件

4. 步骤说明(有代码)

  1. 安装 工具过程 省略, 自行网络搜索安装吧
  2. 编写demo.proto文件,代码如下
syntax = "proto3";//proto的包名package demo;//生成go文件的包名option go_package="/gen;demobp";message Request {  string name = 1;}message Response {  string message = 1;}service HelloService {  rpc SayHello(Request) returns (Response) {  }}
  1. 创建一个buf.gen.yaml文件,用于buf工具
version: v1plugins:#插件  - plugin: go  #输出到指定当前路径的指定目录    out: gen    opt:      - paths=source_relative  - plugin: go-grpc    out: gen    opt:      - paths=source_relative  - plugin: grpc-gateway    out: gen    opt:      - paths=source_relative      - generate_unbound_methods=true      #gateway要依赖的自定义url的yaml文件      - grpc_api_configuration=demo.yaml


  1. 创建一个demo.yaml文件,用于存放grpc-gateway自己定义url
type: google.aip.Serviceconfig_version: 3http:#路由规则 demo.HelloService.SayHello 第一部分demo 这里对应着是 demo.proto文件里定义的包名 package demo; 如果改为package demo1;则selector 也要改成相应的demo1.HelloService.SayHello才可以 ,第二部分HelloService 是服务名,第三部分SayHello 是服务的方法名#参数中的{name} 要和demo.proto中的message中Request中的name相同  rules:    - selector: demo.HelloService.SayHello      get: /v1/hello/{name}      body:"*"
  1. 执行 buf命令
 #说明: 如果不加参数,需要与buf.gen.yaml在同一个目录下执行,否则要使用参数指定配置文件才可以buf generate
  1. 目录结构如下

demo
├── bp
│ ├── buf.gen.yaml
│ ├── demo.proto
│ ├── demo.yaml
│ ├── gen
│ │ ├── demo.pb.go
│ │ ├── demo.pb.gw.go
│ │ └── demo_grpc.pb.go
│ └── genpb.sh
├── client
│ └── main.go
├── go.mod
├── go.sum
├── gw
│ └── main.go
└── server
└── main.go

5. 编写 go代码(只是用来演示使用,代码质量差)

  1. service端
package mainimport (	bp "bpdemo/bp/gen"	"context"	"google.golang.org/grpc"	"log"	"net")type server struct {    //必须有,向下兼容的处理	bp.UnimplementedHelloServiceServer}func (s *server) SayHello(ctx context.Context, req *bp.Request) (*bp.Response, error) {	return &bp.Response{Message: "Hello " + req.Name}, nil}func main() {	lis, err := net.Listen("tcp", ":8080")	if err != nil {		panic(err)	}	s := grpc.NewServer()	bp.RegisterHelloServiceServer(s, &server{})	if err := s.Serve(lis); err != nil {		log.Fatalf("faild to server %v", err)	}}
  1. gateway端
package mainimport (	"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"	"google.golang.org/grpc"	demobp "bpdemo/bp/gen"	"context"	"fmt"	"net/http")func main() {	ctx := context.Background()	ctx, cancelFunc := context.WithCancel(ctx)	defer cancelFunc()    //自定义服务	mux := runtime.NewServeMux()	err := demobp.RegisterHelloServiceHandlerFromEndpoint(ctx, mux, "127.0.0.1:8080", []grpc.DialOption{grpc.WithInsecure()})	if err != nil {		panic(err)	}	err = http.ListenAndServe(":9090", mux)	fmt.Println(err)}
  1. client端
package mainimport (	bp "bpdemo/bp/gen"	"context"	"google.golang.org/grpc"	"google.golang.org/grpc/credentials/insecure"	"log"	"time")func main() {	conn, err := grpc.Dial("localhost:8080", grpc.WithTransportCredentials(insecure.NewCredentials()))	if err != nil {		panic(err)	}	defer conn.Close()	c := bp.NewHelloServiceClient(conn)	ctx, cancelFunc := context.WithTimeout(context.Background(), 10*time.Second)	defer cancelFunc()	r, err := c.SayHello(ctx, &bp.Request{Name: "hello"})	if err != nil {		log.Fatalf("get name err: %v", err)	}	log.Printf("收到内容:%v", r.GetMessage())}
  1. 测试
  2. curl http://localhost:9090/v1/hello/123
    #返回数据
    {
    "message":"Hello 123"}