grpc-6-拦截器的使用

拦截器

在 gRPC 中,大类可分为两种 RPC 方法,与拦截器的对应关系是:

  • 普通方法:一元拦截器(grpc.UnaryInterceptor)
  • 流方法:流拦截器(grpc.StreamInterceptor)

一元拦截器

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

grpc.UnaryInterceptor

func UnaryInterceptor(i UnaryServerInterceptor) ServerOption {
    return func(o *options) {
        if o.unaryInt != nil {
            panic("The unary server interceptor was already set and may not be reset.")
        }
        o.unaryInt = i
    }
}

要完成一个拦截器需要实现 UnaryServerInterceptor 方法。形参如下:

  • ctx context.Context:请求上下文
  • req interface{}:RPC 方法的请求参数
  • info *UnaryServerInfo:RPC 方法的所有信息
  • handler UnaryHandler:RPC 方法本身

流式拦截器

type StreamServerInterceptor func(srv interface{}, ss ServerStream, info *StreamServerInfo, handler StreamHandler) error

grpc.StreamInterceptor

 复制代码func StreamInterceptor(i StreamServerInterceptor) ServerOption

如何实现多个拦截器

另外,可以发现 gRPC 本身居然只能设置一个拦截器,难道所有的逻辑都只能写在一起?

关于这一点,你可以放心。采用开源项目 go-grpc-middleware 就可以解决这个问题,本章也会使用它。

import "github.com/grpc-ecosystem/go-grpc-middleware"
myServer := grpc.NewServer(
    grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
        ...
    )),
    grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
       ...
    )),
)

流式拦截器代码

package main

import (
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"github.com/grpc-ecosystem/go-grpc-middleware"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/status"
	pb "grpc/test/src/proto"
	"io"
	"io/ioutil"
	"log"
	"net"
	"runtime/debug"
)
type StreamService struct{}

const PORT = "9002"
func main() {
	cert, err := tls.LoadX509KeyPair("/Users/zhangsan/Documents/GitHub/grpc-01/code/conf/server/server.pem", "/Users/zhangsan/Documents/GitHub/grpc-01/code/conf/server/server.key")
	if err != nil {
		log.Fatalf("credentials.NewServerTLSFromFile err: %v", err)
	}

	certPool := x509.NewCertPool()
	ca, err := ioutil.ReadFile("/Users/zhangsan/Documents/GitHub/grpc-01/code/conf/ca.pem")
	if err != nil {
		log.Fatalf("ioutil.ReadFile err: %v", err)
	}
	if ok := certPool.AppendCertsFromPEM(ca); !ok {
		log.Fatalf("certPool.AppendCertsFromPEM err")
	}
	c := credentials.NewTLS(&tls.Config{
		Certificates: []tls.Certificate{cert},
		ClientAuth:   tls.RequireAndVerifyClientCert,
		ClientCAs:    certPool,
	})

	//拦截器
	opts := []grpc.ServerOption{
		grpc.Creds(c),
		grpc_middleware.WithStreamServerChain(
			RecoveryInterceptor,
			LoggingInterceptor,
		),
	}

	server := grpc.NewServer(opts...)
	pb.RegisterStreamServiceServer(server, &StreamService{})
	lis, err := net.Listen("tcp", ":"+PORT)
	if err != nil {
		log.Fatalf("net.Listen err: %v", err)
	}
	server.Serve(lis)
}

//客户端流rpc
func (s *StreamService) Work(stream pb.StreamService_WorkServer) error {
    panic("panic")
	//设置header信息 sendHeader不可同时用,否则SendHeader会覆盖前一个
	if err := stream.SetHeader(metadata.MD{"cc2":[]string{"dd2"}});nil != err{
		return err
	}
	//设置header信息
	//if err := stream.SendHeader(metadata.MD{"cc":[]string{"dd"}});err != nil{
	//	return err
	//}



	//设置metadata,注意一元和流式的区别
	stream.SetTrailer(metadata.MD{"cc1":[]string{"dd1"}})

	a := stream.Context().Value("a")
	fmt.Println(a)
	for {
		r ,err := stream.Recv()
		if err == io.EOF{
			return stream.SendAndClose(&pb.PublicResponse{
				Resp:                &pb.Item{
					Value:                "client-stream-server",
					Value2:               "client-stream-server-v2",
				} ,
			})
		}
		if err != nil{
			return err
		}
		log.Printf("stream.Recv value: %s,value2: %s", r.Req.Value, r.Req.Value2)
	}
}

//服务端流式
func (s *StreamService) Eat(r *pb.PublicRequest, stream pb.StreamService_EatServer) error {

	return nil
}

func (s *StreamService) Sleep(stream pb.StreamService_SleepServer) error {
	return nil
}


//拦截器实现
func LoggingInterceptor(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
	fmt.Println("srv",srv)
	fmt.Printf("%#v\n",ss)
	fmt.Printf("%#v\n",info)

	err := handler(srv,ss)
	fmt.Println(err)

	return err
}

func RecoveryInterceptor(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) (err error) {
	defer func() {
		if e := recover(); e != nil {
			debug.PrintStack()
			err = status.Errorf(codes.Internal, "Panic err: %v", e)
		}
	}()
	return handler(srv, ss)
}

验证

-> % go run src/server/lJq_server/main.go   
日记的拦截器
srv &{}
&grpc.serverStream{ctx:(*context.valueCtx)(0xc0001ba600), t:(*transport.http2Server)(0xc000082c00), s:(*transport.Stream)(0xc0000c0120), p:(*grpc.parser)(0xc00051f1c0), codec:proto.codec{}, cp:grpc.Compressor(nil), dc:grpc.Decompressor(nil), comp:encoding.Compressor(nil), decomp:encoding.Compressor(nil), maxReceiveMessageSize:4194304, maxSendMessageSize:2147483647, trInfo:(*grpc.traceInfo)(nil), statsHandler:stats.Handler(nil), binlog:(*binarylog.MethodLogger)(nil), serverHeaderBinlogged:false, mu:sync.Mutex{state:0, sema:0x0}}
&grpc.StreamServerInfo{FullMethod:"/proto.StreamService/Work", IsClientStream:true, IsServerStream:false}

panic的拦截器
goroutine 37 [running]:
runtime/debug.Stack(0x16, 0x8, 0x0)
        /usr/local/go/src/runtime/debug/stack.go:24 +0x9d
runtime/debug.PrintStack()
        /usr/local/go/src/runtime/debug/stack.go:16 +0x22
main.RecoveryInterceptor.func1(0xc00052fbf8)
        /Users/zhangsan/Documents/GitHub/grpc-01/code/src/server/lJq_server/main.go:124 +0x57
panic(0x1500080, 0x1684460)
        /usr/local/go/src/runtime/panic.go:969 +0x166
main.(*StreamService).Work(0x1a80f48, 0x169fa40, 0xc000098870, 0x1698260, 0x1a80f48)
        /Users/zhangsan/Documents/GitHub/grpc-01/code/src/server/lJq_server/main.go:63 +0x39
grpc/test/src/proto._StreamService_Work_Handler(0x153ff60, 0x1a80f48, 0x169ce00, 0xc0000f0240, 0xc00052fad0, 0x1)
        /Users/zhangsan/Documents/GitHub/grpc-01/code/src/proto/stream.pb.go:374 +0xad
main.LoggingInterceptor(0x153ff60, 0x1a80f48, 0x169ce00, 0xc0000f0240, 0xc00051f1e0, 0x15f9278, 0x20, 0x20)
        /Users/zhangsan/Documents/GitHub/grpc-01/code/src/server/lJq_server/main.go:115 +0x1f1
github.com/grpc-ecosystem/go-grpc-middleware.ChainStreamServer.func1.1.1(0x153ff60, 0x1a80f48, 0x169ce00, 0xc0000f0240, 0x3, 0xc00004fc38)
        /Users/zhangsan/Documents/GitHub/grpc-01/code/vendor/github.com/grpc-ecosystem/go-grpc-middleware/chain.go:49 +0x5f
main.RecoveryInterceptor(0x153ff60, 0x1a80f48, 0x169ce00, 0xc0000f0240, 0xc00051f1e0, 0xc00051f200, 0x0, 0x0)
        /Users/zhangsan/Documents/GitHub/grpc-01/code/src/server/lJq_server/main.go:128 +0x8b
github.com/grpc-ecosystem/go-grpc-middleware.ChainStreamServer.func1.1.1(0x153ff60, 0x1a80f48, 0x169ce00, 0xc0000f0240, 0xc00004fc68, 0x100e788)
        /Users/zhangsan/Documents/GitHub/grpc-01/code/vendor/github.com/grpc-ecosystem/go-grpc-middleware/chain.go:49 +0x5f
github.com/grpc-ecosystem/go-grpc-middleware.ChainStreamServer.func1(0x153ff60, 0x1a80f48, 0x169ce00, 0xc0000f0240, 0xc00051f1e0, 0x15f9278, 0x1699e60, 0xc0001ba600)
        /Users/zhangsan/Documents/GitHub/grpc-01/code/vendor/github.com/grpc-ecosystem/go-grpc-middleware/chain.go:58 +0xcf
google.golang.org/grpc.(*Server).processStreamingRPC(0xc0001816c0, 0x16a00a0, 0xc000082c00, 0xc0000c0120, 0xc0001bb230, 0x1a4ad20, 0x0, 0x0, 0x0)
        /Users/zhangsan/Documents/GitHub/grpc-01/code/vendor/google.golang.org/grpc/server.go:1540 +0x511
google.golang.org/grpc.(*Server).handleStream(0xc0001816c0, 0x16a00a0, 0xc000082c00, 0xc0000c0120, 0x0)
        /Users/zhangsan/Documents/GitHub/grpc-01/code/vendor/google.golang.org/grpc/server.go:1613 +0xc96
google.golang.org/grpc.(*Server).serveStreams.func1.2(0xc0000b6400, 0xc0001816c0, 0x16a00a0, 0xc000082c00, 0xc0000c0120)
        /Users/zhangsan/Documents/GitHub/grpc-01/code/vendor/google.golang.org/grpc/server.go:934 +0xa1
created by google.golang.org/grpc.(*Server).serveStreams.func1
        /Users/zhangsan/Documents/GitHub/grpc-01/code/vendor/google.golang.org/grpc/server.go:932 +0x204

一元拦截器代码

package main

import (
	"context"
	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	v11 "grpc/test/src/proto"
	"log"
	"net"
	"os"
	"runtime/debug"
)

func main(){
	RunServer(context.Background(),"9001")
}

func RunServer(ctx context.Context, port string) error {
	listen, err := net.Listen("tcp", ":"+port)
	if nil != err {
		return err
	}

	/*
	var (
		opts []grpc.ServerOption
	)

	注册日志,各种时间因素
	opts = append(opts, gRpcServices.RegisterLogInject(logLayout, constant.GRpcLoginInsKey))
	opts = append(opts, grpc.KeepaliveParams(keepalive.ServerParameters{
		MaxConnectionIdle: 15 * time.Second, //client空闲超过该时间,发送一个GOAWAY
		//MaxConnectionAge:      time.Duration(math.MaxInt64), //client最大存活时间
		MaxConnectionAge:      5 * time.Second, //client最大存活时间
		MaxConnectionAgeGrace: 5 * time.Second, //强制关闭连接前缓冲时间,用以完成pending的请求
		Time:                  5 * time.Second, //client空闲该时间侯,发送一个ping
		Timeout:               3 * time.Second, //如果ping该时间内未收到pong,认为连接已断开
	}))
	opts = append(opts, grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{
		MinTime:             3 * time.Second, //client两次ping最小间隔,小于该时间中止连接
		PermitWithoutStream: true,            //即使没有活动的stream,也允许keepalive的ping
	}))

	 */
	//sv = grpc.NewServer(opts...)

	opts := []grpc.ServerOption{
		grpc_middleware.WithUnaryServerChain(
			RecoveryInterceptor,
			LoggingInterceptor,
		),
	}


	server := grpc.NewServer(opts...)
	v11.RegisterSayHelloServiceServer(server, NewSayHelloResponseService())
	c := make(chan os.Signal, 1)
	go func() {
		for range c {
			log.Println("shutting down GRPC server...")
			server.GracefulStop()//平滑关闭服务
			<-ctx.Done()
		}
	}()
	log.Println("start gRPC server...,port " + port)
	return server.Serve(listen)

}


type Services struct {

}

func NewSayHelloResponseService()*Services{
	return &Services{}
}



func(s *Services) SayHello(ctx context.Context,req *v11.SayHelloRequest)(resp *v11.SayHelloResponse,err error){
	return &v11.SayHelloResponse{
		Response:             "resp",
	}, err
}

func LoggingInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
	log.Printf("gRPC method: %s, %v", info.FullMethod, req)
	resp, err := handler(ctx, req)
	log.Printf("gRPC method: %s, %v", info.FullMethod, resp)
	return resp, err
}

func RecoveryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
	defer func() {
		if e := recover(); e != nil {
			debug.PrintStack()
			err = status.Errorf(codes.Internal, "Panic err: %v", e)
		}
	}()
	return handler(ctx, req)
}

验证

-> % go run src/server/simple_server/serve.go 
2021/06/03 09:49:00 start gRPC server...,port 9001
2021/06/03 09:49:10 gRPC method: /proto.SayHelloService/SayHello, request:"gRPC" 
2021/06/03 09:49:10 gRPC method: /proto.SayHelloService/SayHello, response:"resp" 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

a...Z

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值