• 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();
    }
    
  • 相关阅读:
    学习ASP.NET MVC3(5) Controller
    关于测试
    [JAVA SE] Java反射机制
    Windows 8 的软件开发架构
    Servlet生命周期与工作原理
    展望未来,总结过去10年的程序员生涯,给程序员小弟弟小妹妹们的一些总结性忠告(转载)
    JAVA小游戏代码(剪刀石头布)
    [JAVA SE] JSP中pageEncoding和charset区别,中文乱码解决方案
    我是工程师,不是编译器
    自己对三层架构理论的理解
  • 原文地址:https://www.cnblogs.com/chenglc/p/13139407.html
Copyright © 2020-2023  润新知