ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

11-gRPC进阶

2022-05-22 01:31:16  阅读:199  来源: 互联网

标签:11 进阶 err proto gRPC fmt grpc context Println


一 grpc metadata机制

gRPC让我们可以像本地调用一样实现远程调用,对于每一次的RPC调用中,都可能会有一些有用的数据,而这些数据就可以通过metadata来传递。metadata是以key-value的形式存储数据的,其中key是string类型,而value是[]string,即一个字符串数组类型。

metadata使得client和server能够为对方提供关于本次调用的一些信息,就像一次http请求的RequestHeader和ResponseHeader一样。

http中header的生命周周期是一次http请求,那么metadata的生命周期就是一次RPC调用

// ****************1创建metadata****************
//MD 类型实际上是map,key是string,value是string类型的slice。
type MD map[string][]string
//创建的时候可以像创建普通的map类型一样使用new关键字进行创建:

//第一种方式
md := metadata.New(map[string]string{"key1": "val1", "key2": "val2"})
//第二种方式 key不区分大小写,会被统一转成小写。
md := metadata.Pairs(
    "key1", "val1",
    "key1", "val1-2", // "key1" will have map value []string{"val1", "val1-2"}
    "key2", "val2",
)


// ****************2发送metadata*****************
md := metadata.Pairs("key", "val")
// 新建一个有 metadata 的 context
ctx := metadata.NewOutgoingContext(context.Background(), md)
// 单向 RPC
response, err := client.SomeRPC(ctx, someRequest)


// ****************3接收metadata*****************

func (s *server) SomeRPC(ctx context.Context, in *pb.SomeRequest) (*pb.SomeResponse, err) {
    md, ok := metadata.FromIncomingContext(ctx)
    // do something with metadata
}

1.1 proto

syntax = "proto3";
option go_package = ".;proto";


service Hello{
  rpc Hello(HelloRequest) returns(HelloResponse);
}

message HelloRequest {
  string name = 1;
}

message HelloResponse {
  string reply = 1;
}

1.2 生成go文件

protoc --go_out=. ./hello.proto
protoc --go-grpc_out=. --go-grpc_opt=require_unimplemented_servers=false  ./hello.proto

1.3 服务端

package main

import (
	"context"
	"fmt"
	"go_test_learn/meta_proto/proto"
	"google.golang.org/grpc"
	"google.golang.org/grpc/metadata"
	"net"
)

type HelloServer struct {
}

func (s *HelloServer) Hello(context context.Context, request *proto.HelloRequest) (*proto.HelloResponse, error) {
	// 取出meta
	md, ok := metadata.FromIncomingContext(context)
	if  ok {
		fmt.Println("获取meta失败")
		// 循环打印出来
		for key,value :=range md{
			fmt.Println(key,value)
		}
		// 只取出password来
		fmt.Println(md["password"][0])
	}


	fmt.Println(request.Name)
	return &proto.HelloResponse{
		Reply: "收到客户端的数据:"+ request.Name,
	}, nil
}

func main() {
	g := grpc.NewServer()
	s := HelloServer{}
	proto.RegisterHelloServer(g, &s)
	lis, error := net.Listen("tcp", "0.0.0.0:50052")
	if error != nil {
		panic("启动服务异常")
	}
	g.Serve(lis)

}

1.4 客户端

package main

import (
	"context"
	"fmt"
	"go_test_learn/meta_proto/proto"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"google.golang.org/grpc/metadata"
)

func main() {

	conn, err := grpc.Dial("127.0.0.1:50052", grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		panic("连接服务异常")
	}
	defer conn.Close()
	client := proto.NewHelloClient(conn)
	request := proto.HelloRequest{Name: "lqz",}

	// 1 方式一:创建md对象
	//md := metadata.Pairs("name", "lqz","password","123")
	// 1 方式二:创建md对象
	md := metadata.New(map[string]string{"name": "lqznb", "password": "456"})

	// 2 新建一个有 metadata 的 context
	ctx := metadata.NewOutgoingContext(context.Background(), md)
	//3 发送
	res, err := client.Hello(ctx, &request)
	if err != nil {
		panic("调用方法异常")
	}

	fmt.Println(res.Reply)
}

二 grpc 拦截器interceptor

在 gRPC 调用过程中,我们可以拦截 RPC 的执行,在 RPC 服务执行前或执行后运行一些自定义逻辑,这在某些场景下很有用,例如身份验证、日志等,我们可以在 RPC 服务执行前检查调用方的身份信息,若未通过验证,则拒绝执行,也可以在执行前后记录下详细的请求响应信息到日志。这种拦截机制与 Gin 中的中间件技术类似,在 gRPC 中被称为 拦截器,它是 gRPC 核心扩展机制之一

2.1 服务端拦截器grpc.UnaryInterceptor(interceptor)

interceptor是自定义的拦截器函数,追踪函数的参数可知,interceptor是一个:

type UnaryServerInterceptor func(ctx context.Context, req interface{}, info *UnaryServerInfo, handler UnaryHandler) (resp interface{}, err error)

参数含义:

ctx context.Context:请求上下文
req interface{}:RPC 方法的请求参数
info *UnaryServerInfo:RPC 方法的所有信息
handler UnaryHandler:RPC 方法真正执行的逻辑

案例

//拦截器
interceptor :=  func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error){
		fmt.Println("接收到了一个新的请求")
		ctime:=time.Now()
		res,err := handler(ctx, req)
		fmt.Println("请求已完成")
		fmt.Println("耗时为:",time.Since(ctime))
		return res, err
}
opt := grpc.UnaryInterceptor(interceptor)
g := grpc.NewServer(opt)
//...

2.2 客户端拦截器

interceptor是自定义的拦截器函数,追踪函数的参数可知,interceptor是一个:

type UnaryClientInterceptor func(ctx context.Context, method string, req, reply interface{}, cc *ClientConn, invoker UnaryInvoker, opts ...CallOption) error

案例

	// 创建客户端拦截器
interceptor := func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error{
		start := time.Now()
		fmt.Println("客户端拦截器")
		err := invoker(ctx, method, req, reply, cc, opts...)
		fmt.Printf("耗时:%s\n", time.Since(start))
		return err
	}
opt := grpc.WithUnaryInterceptor(interceptor)
conn, err := grpc.Dial("127.0.0.1:50052", opt,grpc.WithTransportCredentials(insecure.NewCredentials()))

2.3 开源拦截器

https://github.com/grpc-ecosystem/go-grpc-middleware

image-20220521221658299

案例(服务端)

	// 使用第三方拦截器,使用了grpc_auth和grpc_recovery
	myAuthFunction := func(ctx context.Context) (context.Context, error) {
		fmt.Println("走了认证")
		return ctx, nil
	}
	opt := grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
		grpc_auth.UnaryServerInterceptor(myAuthFunction),
		grpc_recovery.UnaryServerInterceptor(),
	))
	g := grpc.NewServer(opt)
	// 使用拦截器结束

三 通过metadata+拦截器实现认证

3.1 自定义

服务端

package main

import (
   "context"
   "fmt"
   "go_test_learn/interpret_proto/proto"
   "google.golang.org/grpc"
   "google.golang.org/grpc/codes"
   "google.golang.org/grpc/metadata"
   "google.golang.org/grpc/status"
   "net"
)

type HelloServer struct {
}

func (s *HelloServer) Hello(context context.Context, request *proto.HelloRequest) (*proto.HelloResponse, error) {
   fmt.Println(request.Name)
   return &proto.HelloResponse{
      Reply: "收到客户端的数据:" + request.Name,
   }, nil
}

func main() {
   //服务端拦截器
   interceptor :=  func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error){
      fmt.Println("进行用户名密码认证")
      // 取出MD
      md, ok := metadata.FromIncomingContext(ctx)
      if  !ok { // 没有取出meta,返回错误-->这个错误的rpc的错误,状态码也是rpc的状态码
         return resp,status.Error(codes.Unauthenticated,"没有携带认证信息")
      }
      // 携带meta,取出name和password
      // 取出name来
      var (
         name string
         password string
      )
      if names,ok:=md["name"];ok{
         name=names[0]
      }
      // 取出password来
      if passwords,ok:=md["password"];ok{
         password=passwords[0]
      }
      fmt.Println(name,password)
      if name=="lqz" && password=="123"{
         res,err := handler(ctx, req)
         return res, err
      }
      return resp, status.Error(codes.Unauthenticated,"用户名或密码错误")
   }
   opt := grpc.UnaryInterceptor(interceptor)
   g := grpc.NewServer(opt)
   // 使用拦截器结束
   s := HelloServer{}
   proto.RegisterHelloServer(g, &s)
   lis, error := net.Listen("tcp", "0.0.0.0:50052")
   if error != nil {
      panic("启动服务异常")
   }
   g.Serve(lis)

}

客户端

package main

import (
	"context"
	"fmt"
	"go_test_learn/interpret_proto/proto"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"google.golang.org/grpc/metadata"
	"time"
)

func main() {
	// 创建客户端拦截器
	interceptor := func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error{
		start := time.Now()
		fmt.Println("在客户端拦截器中加入用户名密码")
		md := metadata.New(map[string]string{"name": "lqz", "password": "123"})
		ctx = metadata.NewOutgoingContext(context.Background(), md)
		err := invoker(ctx, method, req, reply, cc, opts...)
		fmt.Printf("耗时:%s\n", time.Since(start))
		return err
	}
	opt := grpc.WithUnaryInterceptor(interceptor)
	conn, err := grpc.Dial("127.0.0.1:50052", opt,grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		panic("连接服务异常")
	}
	defer conn.Close()
	client := proto.NewHelloClient(conn)
	request := proto.HelloRequest{Name: "lqz",}
	res, err := client.Hello(context.Background(), &request)
	if err != nil {
		fmt.Println(err)
		panic("调用方法异常")
	}

	fmt.Println(res.Reply)
}

proto

syntax = "proto3";
option go_package = ".;proto";


service Hello{
  rpc Hello(HelloRequest) returns(HelloResponse);
}

message HelloRequest {
  string name = 1;
}

message HelloResponse {
  string reply = 1;
}

3.2 WithPerRPCCredentials

服务端(代码不变)

package main

import (
	"context"
	"fmt"
	"go_test_learn/interpret_proto/proto"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/status"
	"net"
)

type HelloServer struct {
}

func (s *HelloServer) Hello(context context.Context, request *proto.HelloRequest) (*proto.HelloResponse, error) {
	fmt.Println(request.Name)
	return &proto.HelloResponse{
		Reply: "收到客户端的数据:" + request.Name,
	}, nil
}

func main() {
	//服务端拦截器
	interceptor :=  func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error){
		fmt.Println("进行用户名密码认证")
		// 取出MD
		md, ok := metadata.FromIncomingContext(ctx)
		if  !ok { // 没有取出meta,返回错误-->这个错误的rpc的错误,状态码也是rpc的状态码
			return resp,status.Error(codes.Unauthenticated,"没有携带认证信息")
		}
		// 携带meta,取出name和password
		// 取出name来
		var (
			name string
			password string
		)
		if names,ok:=md["name"];ok{
			name=names[0]
		}
		// 取出password来
		if passwords,ok:=md["password"];ok{
			password=passwords[0]
		}
		fmt.Println(name,password)
		if name=="lqz" && password=="123"{
			res,err := handler(ctx, req)
			return res, err
		}
		return resp, status.Error(codes.Unauthenticated,"用户名或密码错误")
	}
	opt := grpc.UnaryInterceptor(interceptor)
	g := grpc.NewServer(opt)
	// 使用拦截器结束
	s := HelloServer{}
	proto.RegisterHelloServer(g, &s)
	lis, error := net.Listen("tcp", "0.0.0.0:50052")
	if error != nil {
		panic("启动服务异常")
	}
	g.Serve(lis)

}

客户端(使用内置的)

package main

import (
	"context"
	"fmt"
	"go_test_learn/interpret_proto/proto"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

// 1 第一步,定义结构体,实现GetRequestMetadata和RequireTransportSecurity方法
type CommonCredential struct {
}

func (c CommonCredential) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
	return map[string]string{
		"name":     "lqz1",
		"password": "123",
	}, nil
}
func (c CommonCredential) RequireTransportSecurity() bool {
	//是否需要基于 TLS 认证进行安全传输
	return false
}
func main() {
	// 使用内置的WithPerRPCCredentials
	// 1 第一步,定义结构体,实现GetRequestMetadata和RequireTransportSecurity方法
	//2 第二步,生成DialOption对象
	opt := grpc.WithPerRPCCredentials(CommonCredential{})

	conn, err := grpc.Dial("127.0.0.1:50052", grpc.WithTransportCredentials(insecure.NewCredentials()),opt)
	if err != nil {
		fmt.Println(err)
		panic("连接服务异常")
	}
	defer conn.Close()
	client := proto.NewHelloClient(conn)
	request := proto.HelloRequest{Name: "lqz"}
	res, err := client.Hello(context.Background(), &request)
	if err != nil {
		fmt.Println(err)
		panic("调用方法异常")
	}

	fmt.Println(res.Reply)
}

四 验证器

我们使用第三方:https://github.com/envoyproxy/protoc-gen-validate

4.1 linux/mac安装

// 执行命令
go get -d github.com/envoyproxy/protoc-gen-validate
make build

//执行make build之前需要先切换到protoc-gen-validate路径下;因为make build执行的就是这个路径下的Makefile;一定要确保在对应的路径下,这样make build才不会出错

/*
mac位置在:Users/liuqingzheng/go/pkg/mod/github.com/envoyproxy/protoc-gen-validate@v0.6.7
// 权限问题,cp到go路径下
cp -r protoc-gen-validate@v0.6.7 /Users/liuqingzheng/go/validate
// export GO111MODULE=on  开启go mod模式
*/

4.2 win安装

下载exe,将exe文件拷贝到 go的根目录的bin目录下

0.6.7版本exe

最新版本查找

下载完成,放到gopath的bin路径下,加入环境变量

4.3 代码

proto--hello.proto

syntax = "proto3";
import "validate.proto";

option go_package = "./;proto";



service Hello{
  rpc Hello(Person) returns(Person);
}


message Person {
  uint64 id    = 1 [(validate.rules).uint64.gt    = 999];

  string email = 2 [(validate.rules).string.email = true];

  string mobile  = 3 [(validate.rules).string = {
    pattern:   "^1[3-9][0-9]{9}$",
  }];

}


proto--validate.proto

https://github.com/envoyproxy/protoc-gen-validate/blob/main/validate/validate.proto

执行命令

protoc --go_out=. --validate_out="lang=go:." hello.proto
protoc --go-grpc_out=. --go-grpc_opt=require_unimplemented_servers=false   --validate_out="lang=go:." hello.proto

服务端

package main

import (
	"awesomeProject/valdiate_proto/proto"
	"context"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"net"
)

type HelloServer struct {
}

func (s *HelloServer) Hello(context context.Context, request *proto.Person) (*proto.Person, error) {
	fmt.Println(request.Id)
	err:=request.Validate()

	if err != nil {
		panic(err)
	}else {

		return &proto.Person{
			Id:1000,
			Email:" 3@qq.com",
			Mobile: "18953675221",
		}, nil
	}



}

type Validator interface {
	Validate() error
}
func main() {
	//服务端拦截器
	interceptor :=  func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error){
		fmt.Println("进行数据校验")
		if r,ok:=req.(Validator);ok{
			if err:=r.Validate();err!=nil{
				return resp, status.Error(codes.Unauthenticated,err.Error())
			}
		}
		return handler(ctx,req)


	}
	opt := grpc.UnaryInterceptor(interceptor)
	g := grpc.NewServer(opt)

	// 使用拦截器结束
	s := HelloServer{}
	proto.RegisterHelloServer(g, &s)
	lis, error := net.Listen("tcp", "0.0.0.0:50052")
	if error != nil {
		panic("启动服务异常")
	}
	g.Serve(lis)

}

//func main() {
//	per:=new(proto.Person)
//	err:=per.Validate()
//	fmt.Println(err)
//}

客户端

package main

import (
	"context"
	"fmt"
	"awesomeProject/valdiate_proto/proto"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"

)



func main() {

	conn, err := grpc.Dial("127.0.0.1:50052", grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		fmt.Println(err)
		panic("连接服务异常")
	}
	defer conn.Close()
	client := proto.NewHelloClient(conn)
	request := proto.Person{
		Id:1000,
		Email:" 3@qq.com",
		Mobile: "1895636255144",
	}
	res, err := client.Hello(context.Background(), &request)
	if err != nil {
		fmt.Println(err)
		panic("调用方法异常")
	}

	fmt.Println(res.Mobile)
}

五 grpc 状态码

gRPC提供的:https://github.com/grpc/grpc/blob/master/doc/statuscodes.md

image-20220522010411111

六 grpc 错误

6.1 服务端

status.Error(codes.Unauthenticated,"没有携带认证信息")

status.New(codes.Unauthenticated,"没有携带认证信息").Err()

status.Newf(codes.Unauthenticated,"没有携带认证信息%s","lqz").Err()

6.2 客户端

s,ok:=status.FromError(err)
if !ok{
		fmt.Println(s.Message())
		fmt.Println(s.Code())
}

七 grpc超时机制

7.1 客户端使用ctx

ctx,_:=context.WithTimeout(context.Background(),time.Second*3)
res, err := client.Hello(ctx, &request)
fmt.Println(err)
s,ok:=status.FromError(err)
if !ok{
		fmt.Println("ok")
		fmt.Println(s.Message())
		fmt.Println(s.Code())
}
fmt.Println(s.Message())
fmt.Println(s.Code())

7.2 服务端睡5s

func (s *HelloServer) Hello(context context.Context, request *proto.HelloRequest) (*proto.HelloResponse, error) {
	fmt.Println(request.Name)
	fmt.Println("睡5s")
	time.Sleep(5*time.Second)

	return &proto.HelloResponse{
		Reply: "收到客户端的数据:" + request.Name,
	}, nil
}

标签:11,进阶,err,proto,gRPC,fmt,grpc,context,Println
来源: https://www.cnblogs.com/liuqingzheng/p/16296783.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有