【橘子分布式】gRPC(番外篇-拦截器)

一、简介

我们之前其实已经完成了关于grpc的一些基础用法,实际上还有一些比较相对进阶的使用方式。比如:

  • 拦截器:包括客户端和服务端的拦截器,进而在每一端都可以划分为流式的拦截器和非流式的拦截器。和以前我们在spring web中的拦截器思路是一样的,都可以在请求来之前做一些统一的处理,进而减少代码量,做一些鉴权 ,数据校验 ,限流等等,和业务解耦。
    gRPC的拦截器
    1. 一元请求的 拦截器
      客户端 【请求 响应】
      服务端 【请求 响应】
    2. 流式请求的 拦截器 (Stream Tracer)
      客户端 【请求 响应】
      服务端 【请求 响应】
  • 客户端重试:grpc的客户端还可以发起重试请求,当我们有一些异常并非代码异常的时候,可以通过重试来避免问题。
  • NameResolver :当用于微服务的时候,需要注册中心对服务名的解析等等。
  • 负载均衡:包括(pick-first , 轮训)等轮训方式。
  • 可以在其他微服务框架中整合,比如dubbo中,spring cloud中,用protobuf来序列化数据,用grpc来发起rpc(比如可以替代open fegin)等场合。

下面我们就来从拦截器功能开始学习一下grpc。

二、项目构建

我们为进阶篇搭建一个新的工程。结构还是客户端,服务端,api模块。
其中api模块作为公共内容被其他模块引入做公共的声明使用。

api模块: rpc-grpc-adv-api
服务端模块:rpc-grpc-adv-server
客户端模块: rpc-grpc-adv-client

1、api模块

<dependencies>
    <dependency>
        <groupId>io.grpc</groupId>
        <artifactId>grpc-netty-shaded</artifactId>
        <version>1.51.0</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>io.grpc</groupId>
        <artifactId>grpc-protobuf</artifactId>
        <version>1.51.0</version>
    </dependency>
    <dependency>
        <groupId>io.grpc</groupId>
        <artifactId>grpc-stub</artifactId>
        <version>1.51.0</version>
    </dependency>
    <dependency> <!-- necessary for Java 9+ -->
        <groupId>org.apache.tomcat</groupId>
        <artifactId>annotations-api</artifactId>
        <version>6.0.53</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

<build>
    <extensions>
        <extension>
            <groupId>kr.motd.maven</groupId>
            <artifactId>os-maven-plugin</artifactId>
            <version>1.7.1</version>
        </extension>
    </extensions>
    <plugins>
        <plugin>
            <groupId>org.xolstice.maven.plugins</groupId>
            <artifactId>protobuf-maven-plugin</artifactId>
            <version>0.6.1</version>
            <configuration>
                <protocArtifact>com.google.protobuf:protoc:3.21.7:exe:${os.detected.classifier}</protocArtifact>
                <pluginId>grpc-java</pluginId>
                <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.52.1:exe:${os.detected.classifier}</pluginArtifact>
                <outputDirectory>${basedir}/src/main/java</outputDirectory>
                <clearOutputDirectory>false</clearOutputDirectory>
            </configuration>
            <executions>
                <execution>
                    <goals>
                        <goal>compile</goal>
                        <goal>compile-custom</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

在main目录下创建proto目录,下建立Hello.proto文件,声明内容为。

syntax = "proto3";

package com.levi;

option java_multiple_files = false;
option java_package = "com.levi";
option java_outer_classname = "HelloProto";


message HelloRequest{
  string name = 1;
}

message HelloRespnose{
  string result = 1;
}

service HelloService{
  // 普通方法
  rpc hello(HelloRequest) returns (HelloRespnose);
  // 双端流方法
  rpc hello1(stream HelloRequest) returns (stream HelloRespnose);
}

然后通过编译器编译生成对应的message类HelloProto.java和service类HelloServiceGrpc.java。
然后该模块将会被其他模块引用,使用这些定义的类。

2、server模块

引入api模块。

<dependencies>
    <dependency>
        <groupId>com.levi</groupId>
        <artifactId>rpc-grpc-adv-api</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
</dependencies>

服务端业务代码为:

@Slf4j
public class HelloServiceImpl extends HelloServiceGrpc.HelloServiceImplBase {

    @Override
    public void hello(HelloProto.HelloRequest request, StreamObserver<HelloProto.HelloRespnose> responseObserver) {
        String name = request.getName();
        System.out.println("接收到客户端的参数name = " + name);
        responseObserver.onNext(HelloProto.HelloRespnose.newBuilder().setResult("this is server result").build());
        responseObserver.onCompleted();
    }
}

服务端启动代码为:

package com.levi;

import com.levi.service.HelloServiceImpl;
import io.grpc.Server;
import io.grpc.ServerBuilder;

import java.io.IOException;

public class GrpcServer {
    public static void main(String[] args) throws InterruptedException, IOException {
        ServerBuilder<?> serverBuilder = ServerBuilder.forPort(9000);
        serverBuilder.addService(new HelloServiceImpl());
        Server server = serverBuilder.build();

        server.start();
        server.awaitTermination();
    }
}

3、client模块

引入api模块。

<dependencies>
    <dependency>
        <groupId>com.levi</groupId>
        <artifactId>rpc-grpc-adv-api</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
</dependencies>

ok,至此就搭建完了项目结构。

三、拦截器

1、一元拦截器

其中一元拦截器就是在我们以前的一元通信模式使用的。也就是非流式的通信模式下。
而一元拦截器也分为两种:客户端拦截器和服务端拦截器
每一种下面又能分为两种
1.简单模式:只能拦截请求,不能拦截响应。
2.复杂模式:可以拦截请求和响应两种。
下面我们先来研究客户端拦截器。

1.1、客户端拦截器

我们说客户端拦截器又分为简单模式和复杂模式。

1.1.1、简单客户端拦截器

我们来开发客户端的代码,首先我们来编写一个简单拦截器。

package com.levi.interceptor;

import io.grpc.*;
import lombok.extern.slf4j.Slf4j;

/**
* 自定义客户端拦截器,需要实现grpc提供的拦截器接口ClientInterceptor
* 该拦截器在客户端发起请求时被调用,
* 可以在该拦截器中对请求进行处理,比如添加请求头、修改请求参数等
*/
@Slf4j
public class CustomClientInterceptor implements ClientInterceptor {
@Override
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
    log.debug("模拟业务处理,这是一个拦截启动的处理 ,统一的做了一些操作 ....");
    /*
     * 拦截器在客户端发起stub的rpc调用之前被调用。处理完之后往下传,把本次调用的一些信息继续往下传
     * 把调用交给grpc,所以需要传递下去调用方法的元信息和一些选项
     * 其实就是拦截器方法的MethodDescriptor<ReqT, RespT> method, CallOptions callOptions
     * 然后往下传是用来发起调用的,底层基于netty,所以需要传递Channel next(这是netty调用的基础连接)
     * 所以需要返回一个ClientCall,封装元信息,然后交给grpc,用来发起调用
     */
    return next.newCall(method, callOptions);
}

我们定义好拦截器之后就要整合在客户端调用的构建上。

package com.levi;

import com.levi.interceptor.CustomClientInterceptor;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;

import java.util.List;

public class GrpcClient {
    public static void main(String[] args) {
        ManagedChannel managedChannel = ManagedChannelBuilder
                .forAddress("localhost", 9000)
                // .intercept(new CustomClientInterceptor())
                // 可以传递多个拦截器,按照传递顺序执行拦截器
                .intercept(List.of(new CustomClientInterceptor()))
                .usePlaintext()
                .build();

        try {
            HelloServiceGrpc.HelloServiceBlockingStub helloServiceBlockingStub = HelloServiceGrpc.newBlockingStub(managedChannel);
            HelloProto.HelloRequest helloRequest = HelloProto.HelloRequest.newBuilder()
                    .setName("levi")
                    .build();
            HelloProto.HelloRespnose helloRespnose = helloServiceBlockingStub.hello(helloRequest);
            System.out.println("接收到的服务端响应为: " + helloRespnose.getResult());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            managedChannel.shutdown();
        }
    }
}

启动服务端,启动客户端,日志显示正常输出没毛病。
但是此时我们也看出来这种简单模式存在几个问题。

# 客户端简单拦截器的问题
1. 只能拦截请求,不能拦截响应。我们只能在请求的时候发起拦截,但是接收响应的时候无法拦截,也就是类似spring mvc的时候没有后置的拦截能力。
2. 即使拦截了请求操作,但是就这个请求拦截上,这个业务粒度也是过于宽泛,不精准。无法在请求的各个阶段发起拦截(1. 开始阶段 2. 设置消息数量 3.发送数据阶段 4.半连接阶段。),其实我们上面的代码可以看出来我们的拦截器是在往下传递ClientCall给grpc,也就是这个调用最后是ClientCall完成的。这里的各个阶段拦截其实就是在ClientCall的各个方法里面插入一些拦截操纵,其实就是在发起请求前,在ClientCall构建的各个阶段拦截一下(这个的底层应该是netty那些阶段性的事件感知实现的。)
装饰者模式增强了一下。

在这里插入图片描述

1.1.2、复杂客户端拦截器

我们前面操作的简单客户端请求拦截器粒度比较大,无法实现对请求过程的更加细力度的监听和管理。所以我们需要一个更加强大的拦截器。我们说白了就是对原来能正常请求中间加一些增强方法,其实就是装饰者模式,包装一下原始类型。在原始类型的基础上加了一堆方法分别在各个阶段生效,从而来增强原始能力。但是真正的rpc调用实现还是原始类型发起的。

请求拦截

于是我们来写一下代码。

/*
   这个类型增强原始类型 适用于控制 拦截 请求发送各个环节
 */
@Slf4j
class CustomForwardingClientClass<ReqT, RespT> extends ClientInterceptors.CheckedForwardingClientCall<ReqT, RespT> {


    /**
     * 构造器模式,需要实现构造函数,传入原始类型,进行增强类型的包装
     */
    protected CustomForwardingClientClass(ClientCall<ReqT, RespT> delegate) {
        super(delegate);
    }

    /**
     * 开始调用,目的 看一个这个RPC请求是不是可以被发起。比如加一些鉴权等功能来判断是不是可以调用,如果不可以直接
     * 返回responseListener.onClose(Status.INTERNAL, new Metadata());
     * 否则就发起请求delegate().start(responseListener, headers);
     */
    protected void checkedStart(Listener<RespT> responseListener, Metadata headers) throws Exception {
        log.debug("发送请求数据之前的检查.....");
        //真正的去发起grpc的请求
        // 是否真正发送grpc的请求,取决这个start方法的调用,delegate()就是原始类型,可以通过构造函数来看到
        // delegate()就是原始类型那个之前简单调用的ClientCall,这就是装饰器模式
        delegate().start(responseListener, headers);
    }
}

我们看到这就是一个增强的包装类,他是对原始的简单拦截器的那个ClientCall的包装。我们看到它在后续的动作之前增强了一个检查的实现。
然后你钥匙要继续就一定要delegate().start才会往下走。否则没启动ClientCall会报错。
ok,我们已经完成了增强ClientCall的开发,现在要把原来的拦截器方法里面的简单ClientCall替换为增强ClientCall。
我们来修改拦截器代码。

/**
 * 自定义客户端拦截器,需要实现grpc提供的拦截器接口ClientInterceptor
 * 该拦截器在客户端发起请求时被调用,
 * 可以在该拦截器中对请求进行处理,比如添加请求头、修改请求参数等
 */
@Slf4j
public class CustomClientInterceptor implements ClientInterceptor {
    @Override
    public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
        log.debug("模拟业务处理,这是一个拦截启动的处理 ,统一的做了一些操作 ....");
        /*
         * 拦截器在客户端发起stub的rpc调用之前被调用。处理完之后往下传,把本次调用的一些信息继续往下传
         * 把调用交给grpc,所以需要传递下去调用方法的元信息和一些选项
         * 其实就是拦截器方法的MethodDescriptor<ReqT, RespT> method, CallOptions callOptions
         * 然后往下传是用来发起调用的,底层基于netty,所以需要传递Channel next(这是netty调用的基础连接)
         * 所以需要返回一个ClientCall,封装元信息,然后交给grpc,用来发起调用
         */
        // return next.newCall(method, callOptions);
        /*
         *  如果我们需要用复杂客户端拦截器 ,就需要对原始的ClientCall进行包装
         *  那么这个时候,就不能返回原始ClientCall对象,
         *  应该返回 包装的ClientCall ---> CustomForwardingClientClass
         */
        return new CustomForwardingClientClass<>(next.newCall(method, callOptions));
    }
}

于是,启动服务端代码,客户端代码观察执行结果。
在这里插入图片描述
没有问题。
此外这个增强拦截还有更加细粒度的方法增强,我们来实现一下。

package com.levi.interceptor;

import io.grpc.*;
import lombok.extern.slf4j.Slf4j;

import javax.annotation.Nullable;

/**
 * 自定义客户端拦截器,需要实现grpc提供的拦截器接口ClientInterceptor
 * 该拦截器在客户端发起请求时被调用,
 * 可以在该拦截器中对请求进行处理,比如添加请求头、修改请求参数等
 */
@Slf4j
public class CustomClientInterceptor implements ClientInterceptor {
    @Override
    public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
        log.debug("模拟业务处理,这是一个拦截启动的处理 ,统一的做了一些操作 ....");
        /*
         * 拦截器在客户端发起stub的rpc调用之前被调用。处理完之后往下传,把本次调用的一些信息继续往下传
         * 把调用交给grpc,所以需要传递下去调用方法的元信息和一些选项
         * 其实就是拦截器方法的MethodDescriptor<ReqT, RespT> method, CallOptions callOptions
         * 然后往下传是用来发起调用的,底层基于netty,所以需要传递Channel next(这是netty调用的基础连接)
         * 所以需要返回一个ClientCall,封装元信息,然后交给grpc,用来发起调用
         */
        // return next.newCall(method, callOptions);
        /*
         *  如果我们需要用复杂客户端拦截器 ,就需要对原始的ClientCall进行包装
         *  那么这个时候,就不能返回原始ClientCall对象,
         *  应该返回 包装的ClientCall ---> CustomForwardingClientClass
         */
        return new CustomForwardingClientClass<>(next.newCall(method, callOptions));
    }
}

/*
   这个类型增强原始类型 适用于控制 拦截 请求发送各个环节
 */
@Slf4j
class CustomForwardingClientClass<ReqT, RespT> extends ClientInterceptors.CheckedForwardingClientCall<ReqT, RespT> {

    /**
     * 构造器模式,需要实现构造函数,传入原始类型,进行增强类型的包装
     */
    protected CustomForwardingClientClass(ClientCall<ReqT, RespT> delegate) {
        super(delegate);
    }

    /**
     * 开始调用,目的 看一个这个RPC请求是不是可以被发起。比如加一些鉴权等功能来判断是不是可以调用,如果不可以直接
     * 返回responseListener.onClose(Status.INTERNAL, new Metadata());
     * 否则就发起请求delegate().start(responseListener, headers);
     */
    protected void checkedStart(Listener<RespT> responseListener, Metadata headers) throws Exception {
        log.debug("发送请求数据之前的检查.....");
        //真正的去发起grpc的请求
        // 是否真正发送grpc的请求,取决这个start方法的调用,delegate()就是原始类型,可以通过构造函数来看到
        // delegate()就是原始类型那个之前简单调用的ClientCall,这就是装饰器模式
        delegate().start(responseListener, headers);
    }

    // 真正开始发送消息,netty的发送消息的方法,outBoundBuffer
    @Override
    public void sendMessage(ReqT message) {
        log.info("发送请求数据: {}", message);
        super.sendMessage(message);
    }

    // 指定发送消息的数量,类似批量发送
    @Override
    public void request(int numMessages) {
        log.info("指定发送消息的数量: {}", numMessages);
        super.request(numMessages);
    }

    // 取消请求的时候回调触发
    @Override
    public void cancel(@Nullable String message, @Nullable Throwable cause) {
        log.info("取消请求: {}", message);
        super.cancel(message, cause);
    }

    // 链接半关闭的时候回调触发,请求消息无法发送,但是可以接受响应的消息
    @Override
    public void halfClose() {
        log.info("链接半关闭");
        super.halfClose();
    }

    // 消息发送是否启用压缩
    @Override
    public void setMessageCompression(boolean enabled) {
        log.info("消息发送是否启用压缩: {}", enabled);
        super.setMessageCompression(enabled);
    }

    // 是否可以发送消息,这个在流式里面会调用,一元的不会
    @Override
    public boolean isReady() {
        log.info("是否可以发送消息: {}", super.isReady());
        return super.isReady();
    }
}

运行程序结果为:
在这里插入图片描述
至此我们看到我们在客户端请求的各个阶段都进行了监听回调。这就是客户端的请求增强了。

响应拦截

前面我们完成的是对于请求的拦截,其实我们可以在客户端这里对服务端响应的拦截。我们可以拦截响应数据,这个能力可以让我们在不同的客户端定制自己的拦截需求。服务端不管你的需求,都返回,你不同的客户端可能有不同的要求,自己去做拦截定制。
我们先来看一下我们之前的那个请求增强。

@Slf4j
class CustomForwardingClientClass<ReqT, RespT> extends ClientInterceptors.CheckedForwardingClientCall<ReqT, RespT> {
    protected CustomForwardingClientClass(ClientCall<ReqT, RespT> delegate) {
        super(delegate);
    }
    protected void checkedStart(Listener<RespT> responseListener, Metadata headers) throws Exception {
        log.debug("发送请求数据之前的检查.....");
        delegate().start(responseListener, headers);
    }
}

你有请求拦截才有响应拦截,而且响应拦截我们一般都是通过监听器来实现的,因为客户端你也不知道你啥时候响应,所以就需要监听回调的形式来监听。我们看到在checkedStart这个方法这里。他的参数列表里面有一个responseListener,响应监听器。其实就是这个东西,我们需要重新实现他,然后传进去,他就会在checkedStart调用的时候传递给grpc,grpc就根据你的实现来拦截了。现在他是一个responseListener,啥也没有,你要想拦截功能还是要增强包装。所以我们来实现一下。

/*
   用于监听响应,并对响应进行拦截,其中响应头回来onHeaders被调用是服务端的
   responseObserver.onNext这个调用触发的。
   而服务端调用responseObserver.onCompleted()才会回调onMessage这个。
   对应的其实就是netty的write和flush,responseObserver.onCompleted()
   才会真的flush,把数据写回来。可以在服务端做修改测试一下。
 */
@Slf4j
class CustomCallListener<RespT> extends ForwardingClientCallListener.SimpleForwardingClientCallListener<RespT> {
	// 构造器包装原始的Listener,下面的回调实现包装增强。
    protected CustomCallListener(ClientCall.Listener<RespT> delegate) {
        super(delegate);
    }

    @Override
    public void onHeaders(Metadata headers) {
        log.info("响应头信息 回来了......");
        super.onHeaders(headers);
    }

    @Override
    public void onMessage(RespT message) {
        log.info("响应的数据 回来了.....{} ", message);
        super.onMessage(message);
    }
	// 这个在流式里面会调用,一元的不会,可以不实现
    @Override
    public void onReady() {
        super.onReady();
    }

    
    //这个在流式里面会调用,一元的不会,可以不实现
    @Override
    public void onClose(Status status, Metadata trailers) {
        super.onClose(status, trailers);
    }
}

通过构造函数执行包装,然后再包装里面增强。此时我们只需要替代delegate().start(responseListener, headers);中的参数responseListener为我们自己定义的就好了。

@Slf4j
class CustomForwardingClientClass<ReqT, RespT> extends ClientInterceptors.CheckedForwardingClientCall<ReqT, RespT> {

    /**
     * 构造器模式,需要实现构造函数,传入原始类型,进行增强类型的包装
     */
    protected CustomForwardingClientClass(ClientCall<ReqT, RespT> delegate) {
        super(delegate);
    }

    /**
     * 开始调用,目的 看一个这个RPC请求是不是可以被发起。比如加一些鉴权等功能来判断是不是可以调用,如果不可以直接
     * 返回responseListener.onClose(Status.INTERNAL, new Metadata());
     * 否则就发起请求delegate().start(responseListener, headers);
     */
    protected void checkedStart(Listener<RespT> responseListener, Metadata headers) throws Exception {
        log.debug("发送请求数据之前的检查.....");
        //真正的去发起grpc的请求
        // 是否真正发送grpc的请求,取决这个start方法的调用,delegate()就是原始类型,可以通过构造函数来看到
        // delegate()就是原始类型那个之前简单调用的ClientCall,这就是装饰器模式
        // delegate().start(responseListener, headers);
        delegate().start(new CustomCallListener<>(responseListener), headers);// 传入增强响应拦截
    }

   ...... 省略其余代码
}

执行没有问题。
在这里插入图片描述

1.2、服务端拦截器

1.2.1、服务端简单拦截器

对应客户端那边的拦截器,服务端这里其实是对应的,该有的都有。我们来看下服务端的简单拦截器。

/**
 * 自定义服务端拦截器
 */
@Slf4j
public class CustomServerInterceptor implements ServerInterceptor {

    @Override
    public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {
        //在服务器端 拦截请求操作的功能 写在这个方法中
        log.debug("服务器端拦截器生效.....");
        // 返回req请求监听器,用于监听服务端req请求的事件,reqTListener就是原始的拦截监听器
        ServerCall.Listener<ReqT> reqTListener = next.startCall(call, headers);
        return reqTListener;
    }
}

这里的概念我们都可以在客户端那里找到对应的。我们看到这个方法返回了一个ServerCall.Listener 类型的,而且泛型是req,可见是对于请求的监听器。作为服务端,他是被动连接的,所以她的拦截方式就是监听,什么时候来我什么时候拦截,他不知道你啥时候来,就只能监听着。而next.startCall(call, headers);返回的就是一个具有原始能力的拦截器,没有封装增强的。我们把这个拦截器整合到服务端发布代码中使其生效。

package com.levi;

import com.levi.interceptor.CustomServerInterceptor;
import com.levi.service.HelloServiceImpl;
import io.grpc.Server;
import io.grpc.ServerBuilder;

import java.io.IOException;

public class GrpcServer {
    public static void main(String[] args) throws InterruptedException, IOException {
        ServerBuilder<?> serverBuilder = ServerBuilder.forPort(9000);
        serverBuilder.addService(new HelloServiceImpl());
        // 注册自定义拦截器
        serverBuilder.intercept(new CustomServerInterceptor());
        Server server = serverBuilder.build();

        server.start();
        server.awaitTermination();
    }
}

我们把自定义的拦截器注册进去之后启动服务端和客户端看一下。没有问题。
在这里插入图片描述
同样的服务端的简单拦截器也存在像客户端那边的问题,
拦截请求发送过来的数据,无法处理响应的数据。
拦截力度过于宽泛
所以我么需要复杂拦截器,增强原始的拦截器,达到更加细力度的控制拦截。一切都和当初我们在客户端做的一样,重新定义一个增强的。

1.2.2、服务端复杂拦截器

拦截请求,拦截的是客户端过来的消息

/**
 * 自定义服务端拦截器
 */
@Slf4j
public class CustomServerInterceptor implements ServerInterceptor {

    @Override
    public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {
        //在服务器端 拦截请求操作的功能 写在这个方法中
        log.debug("服务器端拦截器生效.....");
        // 返回req请求监听器,用于监听服务端req请求的事件,reqTListener就是原始的拦截监听器
        // ServerCall.Listener<ReqT> reqTListener = next.startCall(call, headers);
        // 包装器设计模式,封装原始的监听器,增强原始监听器的功能,实际的核心调用还是原始的在做
        // 只是加了一些额外的增强的方法
        return new CustomServerCallListener<>(next.startCall(call, headers));
    }
}

/**
 * 复杂服务端拦截器,用于监听服务端req请求的事件,reqTListener就是原始的拦截监听器
 * 对于reqTListener的事件,我们可以在事件触发时,做一些自定义的操作,
 * 本质是对于原始监听器的一个包装增强,包装器模式
 */
@Slf4j
class CustomServerCallListener<ReqT> extends ForwardingServerCallListener.SimpleForwardingServerCallListener<ReqT> {
    protected CustomServerCallListener(ServerCall.Listener<ReqT> delegate) {
        super(delegate);
    }

    @Override
    //准备接受请求数据
    public void onReady() {
        log.debug("onRead Method Invoke,准备好接收客户端数据....");
        super.onReady();
    }

    @Override
    public void onMessage(ReqT message) {
        log.debug("接受到了客户端请求提交的数据,客户端的请求数据是:  {} ", message);
        super.onMessage(message);
    }

    @Override
    public void onHalfClose() {
        log.debug("监听到了 半连接触发这个操作...");
        super.onHalfClose();
    }

    @Override
    public void onComplete() {
        log.debug("服务端 调用onCompleted()触发...");
        super.onComplete();
    }

    @Override
    public void onCancel() {
        log.debug("出现异常后 会调用这个方法... 可以在这里做一些关闭资源的操作");
        super.onCancel();
    }
}

在经历了客户端的开发之后,我们这里其实就很好理解了。调用没有问题。
在这里插入图片描述

拦截响应,拦截的是服务端发给客户端的响应

我们能拦截请求,自然也就能拦截响应。我们先来看一下什么是服务端的响应,其实就是服务端回写给客户端的操作。也就是服务端调用客户端的操作,我们上面拦截请求其实是客户端发给服务端,也即是服务端的监听ServerCall.Listener,服务端的监听器做包装增强。
现在你要增强服务端对客户端的调用其实就是ServerCall(这里对应我们客户端那里的ClientCall)。所以我们要对ServerCall做包装。就是你谁干啥就增强啥就实现啥就行。

/**
 * 目的:通过自定义的ServerCall 包装原始的ServerCall 增加对于响应拦截的功能
 */
@Slf4j
class CustomServerCall<ReqT, RespT> extends ForwardingServerCall.SimpleForwardingServerCall<ReqT, RespT> {

	// 这里包装的是ServerCall
    protected CustomServerCall(ServerCall<ReqT, RespT> delegate) {
        super(delegate);
    }

    @Override
    //指定发送消息的数量 【响应消息】
    public void request(int numMessages) {
        log.debug("response 指定消息的数量 【request】");
        super.request(numMessages);
    }

    @Override
    //设置响应头
    public void sendHeaders(Metadata headers) {
        log.debug("response 设置响应头 【sendHeaders】");
        super.sendHeaders(headers);
    }

    @Override
    //响应数据
    public void sendMessage(RespT message) {
        log.debug("response 响应数据  【send Message 】 {} ", message);
        super.sendMessage(message);
    }

    @Override
    //关闭连接
    public void close(Status status, Metadata trailers) {
        log.debug("response 关闭连接 【close】");
        super.close(status, trailers);
    }
}

然后我们再把这个包装增强整合到拦截器里面,交给grpc的体系中才能生效,在interceptCall中进行整合,我们不需要改动服务端发布那里的代码,那里可以直接通过CustomServerInterceptor来处理我们这里整合到的两个拦截器,以下为完整代码。

package com.levi.interceptor;

import io.grpc.*;
import lombok.extern.slf4j.Slf4j;

/**
 * 自定义服务端拦截器
 */
@Slf4j
public class CustomServerInterceptor implements ServerInterceptor {

    @Override
    public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {
        //在服务器端 拦截请求操作的功能 写在这个方法中
        log.debug("服务器端拦截器生效.....");

        //包装ServerCall 处理服务端响应拦截
        CustomServerCall<ReqT,RespT> reqTRespTCustomServerCall = new CustomServerCall<>(call);
        // 包装Listener   处理服务端请求拦截
        CustomServerCallListener<ReqT> reqTCustomServerCallListener = new CustomServerCallListener<>(next.startCall(reqTRespTCustomServerCall, headers));
        return reqTCustomServerCallListener;
    }
}

/**
 * 复杂服务端拦截器,用于监听服务端req请求的事件,reqTListener就是原始的拦截监听器
 * 对于reqTListener的事件,我们可以在事件触发时,做一些自定义的操作,
 * 本质是对于原始监听器的一个包装增强,包装器模式
 */
@Slf4j
class CustomServerCallListener<ReqT> extends ForwardingServerCallListener.SimpleForwardingServerCallListener<ReqT> {
    protected CustomServerCallListener(ServerCall.Listener<ReqT> delegate) {
        super(delegate);
    }

    @Override
    //准备接受请求数据
    public void onReady() {
        log.debug("onRead Method Invoke,准备好接收客户端数据....");
        super.onReady();
    }

    @Override
    public void onMessage(ReqT message) {
        log.debug("接受到了客户端请求提交的数据,客户端的请求数据是:  {} ", message);
        super.onMessage(message);
    }

    @Override
    public void onHalfClose() {
        log.debug("监听到了 半连接触发这个操作...");
        super.onHalfClose();
    }

    @Override
    public void onComplete() {
        log.debug("服务端 调用onCompleted()触发...");
        super.onComplete();
    }

    @Override
    public void onCancel() {
        log.debug("出现异常后 会调用这个方法... 可以在这里做一些关闭资源的操作");
        super.onCancel();
    }
}

/**
 * 通过自定义的ServerCall 包装原始的ServerCall 增加对于响应拦截的功能
 */
@Slf4j
class CustomServerCall<ReqT, RespT> extends ForwardingServerCall.SimpleForwardingServerCall<ReqT, RespT> {

    protected CustomServerCall(ServerCall<ReqT, RespT> delegate) {
        super(delegate);
    }

    @Override
    //指定发送消息的数量 【响应消息】
    public void request(int numMessages) {
        log.debug("response 指定消息的数量 【request】");
        super.request(numMessages);
    }

    @Override
    //设置响应头
    public void sendHeaders(Metadata headers) {
        log.debug("response 设置响应头 【sendHeaders】");
        super.sendHeaders(headers);
    }

    @Override
    //响应数据
    public void sendMessage(RespT message) {
        log.debug("response 响应数据  【send Message 】 {} ", message);
        super.sendMessage(message);
    }

    @Override
    //关闭连接
    public void close(Status status, Metadata trailers) {
        log.debug("response 关闭连接 【close】");
        super.close(status, trailers);
    }
}

而且我们看到增强类都是要实现构造的,因为要传进去原始类,进行封装,调用核心方法还是走super,走那个原始的操作。你的增强的操作可以加在这些新的方法里面。这些增强方法,你可以酌情看你要的业务方法,需要的就实现,不需要就可以不覆盖实现。其实他就是服务端的响应的各个阶段不同的触发。
我们运行代码没有问题,各个阶段都被触发了。
在这里插入图片描述
对于你要是想只拦截响应,不拦截请求可以这么做。

public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {
        //在服务器端 拦截请求操作的功能 写在这个方法中
        log.debug("服务器端拦截器生效.....");

        //包装ServerCall 处理服务端响应拦截
        CustomServerCall<ReqT,RespT> reqTRespTCustomServerCall = new CustomServerCall<>(call);
        // 包装Listener   处理服务端请求拦截
        CustomServerCallListener<ReqT> reqTCustomServerCallListener =
                new CustomServerCallListener<>(next.startCall(reqTRespTCustomServerCall, headers));
        // return reqTCustomServerCallListener;
        /**
         * 只拦截响应,我们就不需要包装Listener,也就是返回原始的Listener即可。原始的Listener我们是通过
         * next.startCall(reqTRespTCustomServerCall, headers)获取到的。所以继续用next.startCall不操作包装的
         * Listener即可,但是我们要包装响应也就是serverCall,所以返回reqTRespTCustomServerCall。包在原始Listener中
         * 你要是包装请求,那就是需要包装的Listener,不需要就直接next.startCall返回startCall即可。
         */
        return next.startCall(reqTRespTCustomServerCall, headers);
    }

明白请求是包装的listener,响应是servercall,需要哪个就加强哪个就行,不需要增强拦截就用原始的就行。

四、总结

这就是grpc中比较常见的一元拦截器的使用,他是对于一元rpc的拦截。在各个拦截方法中我们可以定义一些自己的业务方法。进而灵活使用拦截器。而且你要是在某个点拦截之后不想继续往下走,那你就不要调用每个拦截方法的super,不要做后续的调用,直接断开链路即可。而且至于拦截请求还是响应就看你包装啥就完了,他不是耦合在一起的。
后面我们再来分析监听流也就是流式和双向调用的拦截器。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值