欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

gRPC-拦截器简单使用

程序员文章站 2022-05-04 13:06:02
概述 gRPC作为通用RPC框架,内置了拦截器功能。包括服务器端的拦截器和客户端拦截器,使用上大同小异。主要作用是在rpc调用的前后进行额外处理。 从客户端角度讲,可以在请求发起前,截取到请求参数并修改;也可以修改服务器的响应参数。 示例 以下写一个简单的示例来描述具体的功能实现。以Go语言为例,其 ......

概述

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

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

示例

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

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

接口描述文件

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方法在建立连接时设置。流拦截器与一元拦截器功能大致相同,具体应用可参考库源码或相关文档。