前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >GRPC怎么用

GRPC怎么用

作者头像
用户2825413
发布2022-04-19 09:39:31
4.4K0
发布2022-04-19 09:39:31
举报
文章被收录于专栏:呆呆熊的技术路

本文主题

本文大部分都是代码案例, 如果您对 grpc 感兴趣, 可以作为基础参考的一部分.

  1. 介绍了 proto 生成 pb 文件常用命令
  2. 如何构造 grpc 服务, header头传递、拦截器一些基本操作.
  3. 如何使用 protoc-gen-grpc-gateway 插件生成同时支持 grpc 和 http 访问请求.

声明 proto

定义proto文件, 返回Msg

代码语言:javascript
复制
syntax = "proto3";

package server;

option go_package = ".;testServer";

service testServer {
  rpc SayHello (Request) returns(Response){
  
  }
}

message Request {

}

message Response {
  string Msg = 1;
}

生成pb message结构文件

代码语言:javascript
复制
protoc -I pb/ --go_out=./pb server.proto

生成grpc服务文件

代码语言:javascript
复制
protoc  -I pb/ --go-grpc_out=./pb server.proto

-I 表示指定proto的目录 *_out 表示指定输出的目录 *.proto表示源proto的文件名

执行完成后, 我们将会看到 server.pb.go 和 server_grpc.pb.go 两个文件

grpc 服务实现

grpc server服务实现

业务逻辑部分(pb rpc中定义的接口实现)
代码语言:javascript
复制
type Server struct {
 testServer.UnimplementedTestServerServer
}

func (s *Server) SayHello(ctx context.Context, t *testServer.Request) (*testServer.Response, error) {
 //获取传递的metadata
  md, _ := metadata.FromIncomingContext(ctx)

 log.Println("xxxx", md)
 return &testServer.Response{Msg: "Hello world"}, nil
}

UnimplementedTestServerServer 的作用是将源pb文件 rpc 方法全部生成为默认实现, 这么做的目的在我们在变更 rpc 接口时, 不会因为未全部实现 导致 register 失败而无法运行, 而是将会执行 UnimplementedTestServerServer 的方法, 例如:

代码语言:javascript
复制
func (UnimplementedTestServerServer) SayHello(context.Context, *Request) (*Response, error) {
 return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented")
}

metadata.FromIncomingContext 是获取 grpc 请求的头部参数,

http1.* 传递参数通过header头, 而对应的 http2 传递 header 参数主要依赖metadata, grpc底层依赖 http2, 所以也是依赖 metadata 获取 header 头参数的.

metadata 在实现上是比较简单的, 底层就是一个 map 的结构.

代码语言:javascript
复制
type MD map[string][]string

func FromIncomingContext(ctx context.Context) (MD, bool) {
 md, ok := ctx.Value(mdIncomingKey{}).(MD)
 if !ok {
  return nil, false
 }
 out := MD{}
 for k, v := range md {
  // We need to manually convert all keys to lower case, because MD is a
  // map, and there's no guarantee that the MD attached to the context is
  // created using our helper functions.
  key := strings.ToLower(k)
  out[key] = v
 }
 return out, true
}
grpc server 服务

grpc.UnaryServerInterceptor 是我们的拦截器, 通过实现 unaryServerInterceptor 方法, 我们将会在入口拦截请求参数进行处理, 比如验证鉴权操作

Server对象就是我们上面 rpc 接口的实现, 我们通过 RegisterTestServerServer 注册到 srv上

代码语言:javascript
复制
//拦截器
ints := []grpc.UnaryServerInterceptor{
  unaryServerInterceptor(),
}
grpcOpts := []grpc.ServerOption{
  grpc.ChainUnaryInterceptor(ints...),
}

//new grpc server
srv := grpc.NewServer(grpcOpts...)

//grpc server 注册server服务
server := &Server{}
testServer.RegisterTestServerServer(srv, server)

//grpc server绑定端口
listener, err := net.Listen("tcp", ":12345")
if err != nil {
  log.Fatalf("failed to listen: %v", err)
}

err = srv.Serve(listener)
if err != nil {
  log.Fatalf("failed to serve: %v", err)
}

unaryServerInterceptor 拦截器实现

代码语言:javascript
复制
func unaryServerInterceptor() grpc.UnaryServerInterceptor {
 return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
  //获取md参数并打印
  md, _ := metadata.FromIncomingContext(ctx)
  log.Println(md)

  replyHeader := metadata.MD{}

  //todo

  h := func(ctx context.Context, req interface{}) (interface{}, error) {
   return handler(ctx, req)
  }

  reply, err := h(ctx, req)

    //设置响应头
  if len(replyHeader) > 0 {
   _ = grpc.SetHeader(ctx, replyHeader)
  }
  return reply, err
 }
}

grpc client 调用

代码语言:javascript
复制

conn, err := grpc.Dial(":12345", grpc.WithInsecure())
if err != nil {
 panic(err)
}

client := testServer.NewTestServerClient(conn)

ctx := context.Background()
md := metadata.New(map[string]string{"key1": "小文文", "key2": "小方方"})
ctx = metadata.NewOutgoingContext(ctx, md)

response, err := client.SayHello(ctx, &testServer.Request{})
log.Println(response, err, ctx)

HTTP1.* 和 GRPC 共同提供服务

如何使用一套proto协议可以同时支持 http1.* 和 grpc 调用呢?

这里我们使用一个proto的插件 protoc-gen-grpc-gateway, 来实现这个功能.

代码语言:javascript
复制
//安装插件
go get github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway

http需要指定访问路径, 我们需要稍微改造下上面的 proto 协议

代码语言:javascript
复制
//...
import "google/api/annotations.proto";
//...

service testServer {
  rpc SayHello (Request) returns(Response){
    option (google.api.http) = {
      get: "/hello",
    };
  }
}

message Request {

}

message Response {
  string Msg = 1;
}

这里我们使用option命令声明了一个 get 请求 /hello

代码语言:javascript
复制
protoc -I pb/ --proto_path=./ --go_out=./pb --go-grpc_out=./pb --grpc-gateway_out=./pb server.proto

我们新增加了一个插件 --grpc-gateway_out , 生成 pb.gw.go文件, 提供 RegisterTestServerHandler 方法进行路由注册.

代码语言:javascript
复制
//register server (http->SayHello)
func RegisterTestServerHandlerServer(ctx context.Context, mux *runtime.ServeMux, server TestServerServer) error {

 mux.Handle("GET", pattern_TestServer_SayHello_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
  //...
    
    //调用的本地
    resp, md, err := local_request_TestServer_SayHello_0(rctx, inboundMarshaler, server, req, pathParams)
   
    //...
 })

 return nil
}
代码语言:javascript
复制
//register client (http -> grpc -> SayHello)
func RegisterTestServerHandlerClient(ctx context.Context, mux *runtime.ServeMux, client TestServerClient) error {

 mux.Handle("GET", pattern_TestServer_SayHello_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
  
  //...
    //调用的grpc服务
  resp, md, err := request_TestServer_SayHello_0(rctx, inboundMarshaler, client, req, pathParams)
  
  //...
 })

 return nil
}

这里可以看到两种 register 是不一样的, 一种是本地直接调用 pb rpc 声明的方法, 另一种是 http 再调用 grpc 服务, 下面我们看下示例

RegisterTestServerHandlerServer 注册示例

代码语言:javascript
复制
gwmux := runtime.NewServeMux(
 runtime.WithIncomingHeaderMatcher(func(key string) (string, bool) { //自定义header头过滤规则
  //todo 此处需要返回header key, 才能在 md 中获取到header头信息
  return key, true
 }),
)

err = testServer.RegisterTestServerHandlerServer(context.Background(), gwmux, server)
if err != nil {
 log.Fatalln("Failed to register gateway:", err)
}

gwServer := &http.Server{
 Addr:    ":8888",
 Handler: gwmux,
}
log.Fatalln(gwServer.ListenAndServe())

RegisterTestServerHandlerClient注册示例

代码语言:javascript
复制
//首先链接grpc服务地址
conn, err := grpc.Dial(
 ":12345",
  grpc.WithInsecure(),
)
if err != nil {
 log.Fatalln("Failed to dial server:", err)
}
client := testServer.NewTestServerClient(conn)

gwmux := runtime.NewServeMux(
 runtime.WithIncomingHeaderMatcher(func(key string) (string, bool) { //自定义header头过滤规则
  //todo 此处需要返回header key, 才能在 md 中获取到header头信息
  return key, true
 }),
)

err = testServer.RegisterTestServerHandlerClient(context.Background(), gwmux, client)
if err != nil {
 log.Fatalln("Failed to register gateway:", err)
}

gwServer := &http.Server{
 Addr:    ":8888",
 Handler: gwmux,
}
log.Fatalln(gwServer.ListenAndServe())

参考

https://github.com/grpc-ecosystem/grpc-gateway

https://www.lixueduan.com/post/grpc/07-grpc-gateway/

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-02-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 小宇技术研究所 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 本文主题
  • 声明 proto
  • grpc 服务实现
    • 业务逻辑部分(pb rpc中定义的接口实现)
      • grpc server 服务
      • HTTP1.* 和 GRPC 共同提供服务
      • 参考
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档