• gRPC-拦截器简单使用


    概述

    gRPC作为通用RPC框架,内置了拦截器功能。包括服务器端的拦截器和客户端拦截器,使用上大同小异。主要作用是在rpc调用的前后进行额外处理。

    从客户端角度讲,可以在请求发起前,截取到请求参数并修改;也可以修改服务器的响应参数。

    示例

    以下写一个简单的示例来描述具体的功能实现。以Go语言为例,其它语言的gRPC库应该也有类似功能,具体请参考文档。

    为使示例简单,简化了对错误的处理。并且只展示了部分代码,完整项目请参考GitHub仓库pnnh/suji-go

    接口描述文件

    syntax = "proto3";
    
    package suji;
    
    service Suji {
        rpc Say(SayRequest) returns (SayReply) {}
    }
    
    message SayRequest {
        string msg = 1;
    }
    
    message SayReply {
        string msg = 1;
    }
    
    

    最初实现

    服务器main方法

    func main() {
    	lis, err := net.Listen("tcp", "0.0.0.0:1301")
    	if err != nil {
    		log.Fatalln("监听出错", err)
    		return
    	}
    
    	grpcServer := grpc.NewServer()
    	suji.RegisterSujiServer(grpcServer, &server.SujiServer{})
    
    	if err = grpcServer.Serve(lis); err != nil {
    		log.Fatalln("服务停止", err)
    	}
    }
    

    客户端main方法

    func main() {
    	addr := "127.0.0.1:1301"
    	c := client.LinkSujiServer(addr)
        
        rep := client.Say(c, msg)
    	log.Println("收到:", rep.Msg)
    }
    

    这里通过LinkSujiServer方法来连接至gRPC服务器,调用了Say接口,并打印了服务器返回值。

    LinkSujiServer方法如下

    func LinkSujiServer(target string) suji.SujiClient {
    	conn, err := grpc.DialContext(context.Background(), target, grpc.WithInsecure())
    	if err != nil {
    		log.Fatalln("链接至服务出错", err, target)
    	}
    	return suji.NewSujiClient(conn)
    }
    

    Say接口客户端调用方式如下:

    
    func Say(client suji.SujiClient, msg string) *suji.SayReply {
    	request := &suji.SayRequest{Msg: msg}
    
    	reply, err := client.Say(context.Background(), request)
    	if err != nil {
    		log.Fatalln("调用出错", err)
    	}
    	return reply
    }
    
    

    Say接口服务端实现如下,将收到的内容原样返回给调用者:

    func (s *SujiServer) Say(ctx context.Context, req *suji.SayRequest) (*suji.SayReply, error) {
    	log.Println("收到:", req.Msg)
    
    	reply := &suji.SayReply{Msg: req.Msg}
    
    	return reply, nil
    }
    

    运行这段代码,将分别打印以下结果

    客户端:

    2019/08/15 18:19:59 发送: 你好
    2019/08/15 18:19:59 收到: 你好
    

    服务器:

    2019/08/15 18:19:59 收到: 你好
    2019/08/15 18:19:59 回复: 你好
    

    拦截器实现

    原本很简单的接口调用,现在我们通过gRPC客户端拦截器给这段对话加点料。

    我们将通过拦截器,截取并篡改客户端发送给服务器的内容,然后把服务器返回的内容也篡改掉。这一切是悄悄在拦截器中进行的,调用的发起方和接收方并不知晓。

    定义拦截器方法

    func callInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn,
    	invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
    
    	if reqParam, ok := req.(*suji.SayRequest); ok {
    		newMsg := strings.Replace(reqParam.Msg, "喜欢", "讨厌", 1)
    		req = &suji.SayRequest{Msg: newMsg}
    	}
    
    	err := invoker(ctx, method, req, reply, cc, opts...)
    	if err != nil {
    		log.Println("接口调用出错", method, err)
    		return err
    	}
    
    	if replyParam, ok := reply.(*suji.SayReply); ok {
    		newMsg := strings.Replace(replyParam.Msg, "讨厌", "喜欢", 1)
    		replyParam.Msg = newMsg
    	}
    
    	return nil
    }
    

    方法稍后解释,这里先修改连接服务器的方法,加入拦截器选项:

    
    func LinkSujiServer(target string) suji.SujiClient {
    	conn, err := grpc.DialContext(context.Background(), target, grpc.WithInsecure(),
    		grpc.WithUnaryInterceptor(callInterceptor))
    	if err != nil {
    		log.Fatalln("链接至服务出错", err, target)
    	}
    	return suji.NewSujiClient(conn)
    }
    

    注意新增的grpc.WithUnaryInterceptor(callInterceptor)这一行。

    gRPC运行时将会为我们定义的callInterceptor传入几个有用的参数。其中method是调用接口的路径,req和reply分别为对应接口的请求和输出参数。而invoker参数是一个方法,用于执行原本的RPC请求,如果调用这个方法,则RPC请求就不会发到服务器。

    在这里,我们通过判断请求和响应类型,并对参数进行篡改。同时为了使示例更有趣,简单修改了下main函数代码。

    客户端main方法

    func main() {
    	addr := "127.0.0.1:1301"
    
    	c := client.LinkSujiServer(addr)
    
    	msg := "我喜欢你"
    	log.Println("发送:", msg)
    	rep := client.Say(c, msg)
    
    	log.Println("收到:", rep.Msg)
    
    	if strings.Contains(rep.Msg, "喜欢") {
    		log.Println("内心:", "好开心啊")
    	}
    }
    

    服务器Say方法

    func (s *SujiServer) Say(ctx context.Context, req *suji.SayRequest) (*suji.SayReply, error) {
    	log.Println("收到:", req.Msg)
    
    	reply := &suji.SayReply{}
    	if strings.Contains(req.Msg, "讨厌") {
    		reply.Msg = "我也讨厌你"
    	}
    	log.Println("回复:", reply.Msg)
    	log.Println("内心:", "沙雕")
    
    	return reply, nil
    }
    

    来看下输出感受下双方的内心吧:

    客户端输出:

    2019/08/15 19:07:14 发送: 我喜欢你
    2019/08/15 19:07:14 收到: 我也喜欢你
    2019/08/15 19:07:14 内心: 好开心啊
    

    服务器输出:

    2019/08/15 19:07:14 收到: 我讨厌你
    2019/08/15 19:07:14 回复: 我也讨厌你
    2019/08/15 19:07:14 内心: 沙雕
    

    最后

    gRPC除了一元拦截器以外也提供了流拦截器设置方法,通过grpc.WithStreamInterceptor方法在建立连接时设置。流拦截器与一元拦截器功能大致相同,具体应用可参考库源码或相关文档。

  • 相关阅读:
    webpack打包时候去掉console.log配置
    nodejs 同时create多条数据到接口中
    element ui 对话框eldialog关闭事件
    elementui 禁用radio
    git 更改远程地址
    js 快速排序
    vue $set $nextTick()
    js includes()
    人工智能导论 第四章答案 (部分)
    大促来临,你的数据库系统准备好了吗?
  • 原文地址:https://www.cnblogs.com/coloc/p/11360071.html
Copyright © 2020-2023  润新知