Spring Cloud Gateway 4 自定义Filter

Spring Cloud Gateway 自定义Filter

Spring Cloud Gateway 的Filter分为GatewayFilter和GlobalFilter两种,二者区别如下

  • GatewayFilter : 需要通过spring.cloud.routes.filters 配置在具体路由下,只作用在当前路由上或通过spring.cloud.default-filters配置在全局,作用在所有路由上
  • GlobalFilter : 全局过滤器,不需要在配置文件中配置,作用在所有的路由上,最终通过GatewayFilterAdapter包装成GatewayFilterChain可识别的过滤器,它为请求业务以及路由的URI转换为真实业务服务的请求地址的核心过滤器,不需要配置,系统初始化时加载,并作用在每个路由上。

Gateway Filter

Gateway filter 是从 Web Filter中复制来的,相当于一个Filter过滤器,可以对访问的URL进行过滤,进行横切面处理,应用场景包括超时、安全等。

Global Filter

Spring Cloud Gateway 定义了Global filter的接口,让我们可以自定义实现自己的Glabl Filter,Glabl Filter是一个全局的Filter,作用于所有的路由。

自定义GatewayFilter

创建一个自定的filter,功能:打印请求的耗时

/**
 * 自定义GatewayFilter
 * 打印一条请求下的耗时
 *  
 * @author chengluchao
 */
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 Ordered.LOWEST_PRECEDENCE;
    }
}

代码解析:

  • Ordered中的int getOrder()方法是来给过滤器设定优先级别的,值越大则优先级越低。
  • 还有有一个filterI(exchange,chain)方法,在该方法中,先记录了请求的开始时间,并保存在ServerWebExchange中,此处是一个“pre”类型的过滤器,然后再chain.filter的内部类中的run()方法中相当于"post"过滤器,在此处打印了请求所消耗的时间。

然后将该过滤器注册到router中,代码如下:

@Bean
public RouteLocator customerRouteLocator(RouteLocatorBuilder builder) {
    return builder.routes()
            .route(r -> r.path("/demo/**")
                    .filters(f -> f.filter(new RequestTimeFilter()))
                    .uri("http://httpbin.org:80/get")
                    .order(0)
                    .id("customer_filter_router")
            )
            .build();
}

执行程序,会打印出方法的耗时

自定义过滤器工厂

过滤器工厂的顶级接口是GatewayFilterFactory,我们可以直接继承它的两个抽象类来简化开发AbstractGatewayFilterFactory和AbstractNameValueGatewayFilterFactory,这两个抽象类的区别就是前者接收一个参数(像StripPrefix和我们创建的这种),后者接收两个参数(像AddResponseHeader)。

image

做一个可配置是否开启的打印请求耗时的过滤器

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import reactor.core.publisher.Mono;

import java.util.Arrays;
import java.util.List;


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

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

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

配置项:

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]
  profiles: elapse_route

现在可以通过配置来决定是否开启打印时间的日志

自定义Global Filter

该GlobalFilter会校验请求中是否包含了“token”,如何不包含请求参数“token”则不转发路由,否则执行正常的逻辑。代码如下:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

public class TokenFilter implements GlobalFilter, Ordered {

    Logger logger= LoggerFactory.getLogger( TokenFilter.class );
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String token = exchange.getRequest().getHeaders().getFirst("token");
        if (token == null || token.isEmpty()) {
            logger.info( "token is empty..." );
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return -100;
    }
}

在上面的TokenFilter需要实现GlobalFilter和Ordered接口,这和实现GatewayFilter很类似。然后根据ServerWebExchange获取ServerHttpRequest,然后根据ServerHttpRequest中是否含有参数token,如果没有则完成请求,终止转发,否则执行正常的逻辑。

然后需要将TokenFilter在工程的启动类中注入到Spring Ioc容器中,代码如下:

@Bean
public TokenFilter tokenFilter(){
        return new TokenFilter();
}
posted @ 2020-06-16 09:56  路迢迢  阅读(8325)  评论(0编辑  收藏  举报