SpringCloud Gateway 中自定义过滤器和过滤器工厂介绍

2021-10-14 0 By admin

SpringCloud Gateway 中虽然提供了众多的路由的过滤器和过滤器工厂,但难免因为业务需求,我们需要自定义特殊的路由过滤器和过滤器工厂,这里介绍过滤器和过滤器工厂定义过程。

一、自定义过滤器 Filter

Spring Cloud Gateway内置了19种强大的过滤器工厂,能够满足很多场景的需求,另外用户也可以自定义过滤器。
在spring Cloud Gateway中,过滤器需要实现 GatewayFilter 和 Ordered2 个接口。

1.1、自定义 RequestTimeFilter 过滤器

public class RequestTimeFilter implements GatewayFilter, Ordered {
  private static final Log log = LogFactory.getLog(GatewayFilter.class);
  private static final String REQUEST_TIME_BEGIN = "requestTimeBegin";
  @Override
  public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    exchange.getAttributes().put(REQUEST_TIME_BEGIN, System.currentTimeMillis());
    return chain.filter(exchange).then(
      Mono.fromRunnable(() -> {
        Long startTime = exchange.getAttribute(REQUEST_TIME_BEGIN);
        if (startTime != null) {
          log.info(exchange.getRequest().getURI().getRawPath() + ": " + (System.currentTimeMillis() - startTime) + "ms");
        }
      })
    );
  }
  @Override
  public int getOrder() {
    return 0;
  }
}
  1. Ordered中的int getOrder()方法是来给过滤器设定优先级别的,值越大则优先级越低。
  2. filter(exchange,chain)方法,先记录了请求的开始时间,并保存在 ServerWebExchange中,此处是一个“pre”类型的过滤器,然后再chain.filter的内部类中的run()方法中相当于”post”过滤器,在此处打印了请求所消耗的时间。

1.2、将过滤器注册到 Router 路由中

@Bean
public RouteLocator customerRouteLocator(RouteLocatorBuilder builder) {
  // @formatter:off
  return builder.routes()
    .route(r -> r.path("/customer/**")
      .filters(f -> f.filter(new RequestTimeFilter())
          .addResponseHeader("X-Response-Default-Foo", "Default-Bar"))
      .uri("http://httpbin.org:80/get")
      .order(0)
      .id("customer_filter_router")
    )
    .build();
  // @formatter:on
}

重启程序,通过curl命令模拟请求:
curl localhost:8081/customer/123
在程序的控制台输出一下的请求信息的日志:
2018-11-16 15:02:20.177 INFO 20488 --- [ctor-http-nio-3] o.s.cloud.gateway.filter.GatewayFilter : /customer/123: 152ms

二、自定义过滤器工厂 FilterFactory

在上面的自定义过滤器中,有没有办法自定义过滤器工厂类呢? 这样就可以在配置文件中配置过滤器了。
现在需要实现一个过滤器工厂,在打印时间的时候,可以设置参数来决定是否打印请参数。
查看 GatewayFilterFactory 的源码,可以发现GatewayFilterfactory的层级如下:

Gateway Filter Factory Structure
Gateway Filter Factory Structure

过滤器工厂的顶级接口是 GatewayFilterFactory,我们可以直接继承它的两个抽象类来简化开发 AbstractGatewayFilterFactory 和 AbstractNameValueGatewayFilterFactory。

  1. 前者接收一个参数,比如它的实现类 RedirectToGatewayFilterFactory。
  2. 后者接收两个参数,比如它的实现类AddRequestHeaderGatewayFilterFactory类。

现在需要将请求的日志打印出来,需要使用一个参数,这时可以参照RedirectToGatewayFilterFactory的写法。

2.1、RequestTimeGatewayFilterFactory 过滤器工厂方法

public class RequestTimeGatewayFilterFactory extends AbstractGatewayFilterFactory<RequestTimeGatewayFilterFactory.Config> {
  private static final Log log = LogFactory.getLog(GatewayFilter.class);
  private static final String REQUEST_TIME_BEGIN = "requestTimeBegin";
  private static final String KEY = "withParams";
  @Override
  public List<String> shortcutFieldOrder() {
    return Arrays.asList(KEY);
  }
  public RequestTimeGatewayFilterFactory() {
    super(Config.class);
  }
  @Override
  public GatewayFilter apply(Config config) {
  return (exchange, chain) -> {
    exchange.getAttributes().put(REQUEST_TIME_BEGIN, System.currentTimeMillis());
    return chain.filter(exchange).then(
      Mono.fromRunnable(() -> {
      Long startTime = exchange.getAttribute(REQUEST_TIME_BEGIN);
      if (startTime != null) {
        StringBuilder sb = new StringBuilder(exchange.getRequest().getURI().getRawPath())
          .append(": ")
          .append(System.currentTimeMillis() - startTime)
          .append("ms");
        if (config.isWithParams()) {
        sb.append(" params:").append(exchange.getRequest().getQueryParams());
        }
        log.info(sb.toString());
      }
      })
    );
  };
  }
  public static class Config {
  private boolean withParams;
  public boolean isWithParams() {
    return withParams;
  }
  public void setWithParams(boolean withParams) {
    this.withParams = withParams;
  }
  }
}

在上面的代码中 apply(Config config)方法内创建了一个GatewayFilter的匿名类,具体的实现逻辑跟之前一样,只不过加了是否打印请求参数的逻辑,而这个逻辑的开关是config.isWithParams()。
静态内部类 Config 就是为了接收那个boolean类型的参数服务的,里边的变量名可以随意写,但是要重写List shortcutFieldOrder()这个方法。
需要注意的是,在类的构造器中一定要调用下父类的构造器把Config类型传过去,否则会报ClassCastException。

2.2、将工厂方法注入到Ioc

最后,需要在工程的启动文件Application类中,向Spring Ioc容器注册 RequestTimeGatewayFilterFactory 类的Bean。

@Bean
public RequestTimeGatewayFilterFactory elapsedGatewayFilterFactory() {
  return new RequestTimeGatewayFilterFactory();
}

2.3、在配置文件中使用

spring:
  cloud:
  gateway:
  routes:
  - id: elapse_route
  uri: http://httpbin.org:80/get
  filters:
  - RequestTime=false
  predicates:
  - After=2017-01-20T17:42:47.789-07:00[America/Denver]

启动工程,在浏览器上访问localhost:8081?name=forezp,可以在控制台上看到,日志输出了请求消耗的时间和请求参数。