spring cloud 2.x版本 Gateway自定义过滤器教程

前言

本文采用Spring cloud本文为2.1.8RELEASE,version=Greenwich.SR3 java

本文基于前两篇文章eureka-server、eureka-client、eureka-ribbon、eureka-feign和spring-gataway的实现。 参考react

概术

Spring Cloud Gateway内部已经提供很是多的过滤器filter,Hystrix Gateway Filter、Prefix PathGateway Filter等。感兴趣的小伙伴能够直接阅读Spring Cloud Gateway官网相关文档或直接阅读源码。可是不少状况下自带的过滤器并不能知足咱们的需求,因此自定义过滤器就显得的很是重要。本文主要介绍全局过滤器(Global Filter)和局部过滤器(Gateway Filter)。git

Gateway Filter

自定义过滤器须要实现GatewayFilter和Ordered。其中GatewayFilter主要是用来实现自定义的具体逻辑,Ordered中的getOrder()方法是来给过滤器设定优先级别的,值越大优先级别越低。github

1.1 建立Filter

package spring.cloud.demo.spring.gateway.filter;

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.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

public class MyGatewayFilter implements GatewayFilter, Ordered {

    private static final Log log = LogFactory.getLog(MyGatewayFilter.class);

    private static final String TIME = "Time";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        exchange.getAttributes().put(TIME, System.currentTimeMillis());
        return chain.filter(exchange).then(
                Mono.fromRunnable(() -> {
                    Long start = exchange.getAttribute(TIME);
                    if (start != null) {
                        log.info("exchange request uri:" + exchange.getRequest().getURI() + ", Time:" + (System.currentTimeMillis() - start) + "ms");
                    }
                })
        );
    }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }
}
复制代码

咱们在请求过来到达的时候,往ServerWebExchange中放了一个属性TIME,属性的值为当前时间的毫秒数,而后请求结束后,又会将请求的时间数取出来来和当前时间数作差,获得耗时时间数。web

如何区分“pre”和“post”?spring

  • pre就是chain.filter(exchange)部分.
  • post就是then()部分.

1.2 将Filter添加到Chain

package spring.cloud.demo.spring.gateway.config;

import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import spring.cloud.demo.spring.gateway.filter.MyGatewayFilter;

@Configuration
public class RoutesConfig {

    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder routeLocatorBuilder){
        return routeLocatorBuilder.routes().route(r -> r.path("/ribbon/**")
                .filters(f -> f.stripPrefix(1)
                        .filter(new MyGatewayFilter()) //增长自定义filter
                        .addRequestHeader("X-Response-Default-Foo", "Default-Bar"))
                .uri("lb://EUREKA-RIBBON")
                .order(0)
                .id("ribbon-route")
        ).build();
    }
}
复制代码

1.2 启动相关服务

启动eureka-server、eureka-client、eureka-ribbon、spring-gateway相关服务,访问http://localhost:8100/ribbon/sayHello地址,页面显示结果以下:apache

这时我打开控制台能够看到日志输出为:

2.1 建立GlobalFilter

package spring.cloud.demo.spring.gateway.filter;

import org.apache.commons.lang.StringUtils;
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;


/** * 全局过滤器 * 校验token */
public class MyGlobalFilter implements GlobalFilter, Ordered {

    private static final String TOKEN = "token";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String parm = exchange.getRequest().getQueryParams().getFirst(TOKEN);
        if (StringUtils.isBlank(parm)) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 1;
    }
}
复制代码

2.2 添加Bean

将MyGlobalFilter添加到Bean中app

package spring.cloud.demo.spring.gateway.filter;

import org.apache.commons.lang.StringUtils;
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;


/** * 全局过滤器 * 校验token */
public class MyGlobalFilter implements GlobalFilter, Ordered {

    private static final String TOKEN = "token";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String parm = exchange.getRequest().getQueryParams().getFirst(TOKEN);
        if (StringUtils.isBlank(parm)) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 1;
    }
}
复制代码

这里只是简单模拟,若是感兴趣的小伙伴能够本身尝试将全部参数取出来并解析(能够利用反射来实现)。分布式

2.3 启动服务

重启动服务,访问http://localhost:8100/ribbon/sayHello,显示以下: ide

我能够看到显示访问是不生效的,咱们在请求中加入token=xxx,显示以下:
这是看到正常返回。日志输出以下:

2019-10-21 16:20:00.478  INFO 15322 --- [ctor-http-nio-2] c.netflix.config.ChainedDynamicProperty  : Flipping property: EUREKA-RIBBON.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
2019-10-21 16:20:00.480  INFO 15322 --- [ctor-http-nio-2] c.n.l.DynamicServerListLoadBalancer      : DynamicServerListLoadBalancer for client EUREKA-RIBBON initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=EUREKA-RIBBON,current list of Servers=[eureka1.server.com:8901],Load balancer stats=Zone stats: {defaultzone=[Zone:defaultzone;	Instance count:1;	Active connections count: 0;	Circuit breaker tripped count: 0;	Active connections per server: 0.0;]
},Server stats: [[Server:eureka1.server.com:8901;	Zone:defaultZone;	Total Requests:0;	Successive connection failure:0;	Total blackout seconds:0;	Last connection made:Thu Jan 01 08:00:00 CST 1970;	First connection made: Thu Jan 01 08:00:00 CST 1970;	Active Connections:0;	total failure count in last (1000) msecs:0;	average resp time:0.0;	90 percentile resp time:0.0;	95 percentile resp time:0.0;	min resp time:0.0;	max resp time:0.0;	stddev resp time:0.0]
]}ServerList:org.springframework.cloud.netflix.ribbon.eureka.DomainExtractingServerList@40585976
2019-10-21 16:20:01.293  INFO 15322 --- [ctor-http-nio-8] s.c.d.s.gateway.filter.MyGatewayFilter   : exchange request uri:http://localhost:8100/sayHello?token=xxx, Time:23ms
2019-10-21 16:20:01.467  INFO 15322 --- [erListUpdater-0] c.netflix.config.ChainedDynamicProperty  : Flipping property: EUREKA-RIBBON.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
复制代码

引用官网原文:The GlobalFilter interface has the same signature as GatewayFilter. These are special filters that are conditionally applied to all routes. (This interface and usage are subject to change in future milestones). 说明GlobalFilter在将来的版本中会又一些变化。

总结

至此,自定义filter的两种方式就简单的实现完成了。一样的方式能够在feign作测试。

彩蛋

在前一篇文章中,配置文件中有这样一段配置:

filters:
 - StripPrefix=1
 - AddResponseHeader=X-Response-Default-Foo, Default-Bar
复制代码

StripPrefix和AddResponseHeader这两个配置其实是两个Filter的过滤器工厂(GatewayFilterFactory),接下来将介绍过滤器工厂的使用,相对来讲这种方式更加灵活。

1.1 建立自定义过滤工厂

package spring.cloud.demo.spring.gateway.factory;

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 MyGatewayFilterFactory extends AbstractGatewayFilterFactory<MyGatewayFilterFactory.Config> {

    private static final Log log = LogFactory.getLog(MyGatewayFilterFactory.class);

    private static final String PARAMS = "myParams";

    private static final String START_TIME = "startTime";

    public MyGatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList(PARAMS);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return ((exchange, chain) -> {
            exchange.getAttributes().put(START_TIME, System.currentTimeMillis());
            return chain.filter(exchange).then(
                    Mono.fromRunnable(() -> {
                        Long startTime = exchange.getAttribute(START_TIME);
                        if (startTime == null) {
                            return;
                        }
                        StringBuilder sb = new StringBuilder();
                        sb.append("exchange request uri:" + exchange.getRequest().getURI() + ",");
                        sb.append("Time:" + (System.currentTimeMillis() - startTime) + "ms.");
                        if (config.isMyParams()) {
                            sb.append("params:" + exchange.getRequest().getQueryParams());
                        }
                        log.info(sb.toString());
                    })
            );
        });
    }

    /** * 配置参数类 */
    public static class Config {

        private boolean myParams;

        public boolean isMyParams() {
            return myParams;
        }

        public void setMyParams(boolean myParams) {
            this.myParams = myParams;
        }
    }
}
复制代码

注意:当咱们继承AbstractGatewayFilterFactory的时候,要把自定义的Config类传给父类,不然会报错。

1.2 添加自定义工厂Bean

package spring.cloud.demo.spring.gateway.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import spring.cloud.demo.spring.gateway.factory.MyGatewayFilterFactory;

@Configuration
public class FilterFactory {

    @Bean
    public MyGatewayFilterFactory myGatewayFilterFactory() {
        return new MyGatewayFilterFactory();
    }
}
复制代码

1.3 修改application.yml

server:
 port: 8100
spring:
 application:
 name: spring-gateway
 cloud:
 gateway:
 discovery:
 locator:
 enabled: true # 开启经过服务中心的自动根据 serviceId 建立路由的功能
 default-filters:
 - My=true
 routes:
 - id: ribbon-route
 uri: lb://EUREKA-RIBBON
 order: 0
 predicates:
 - Path=/ribbon/**
 filters:
 - StripPrefix=1 #去掉前缀,具体实现参考StripPrefixGatewayFilterFactory
 - AddResponseHeader=X-Response-Default-Foo, Default-Bar
 - id: feign-route
 uri: lb://EUREKA-FEIGN
 order: 0
 predicates:
 - Path=/feign/**
 filters:
 - StripPrefix=1
 - AddResponseHeader=X-Response-Default-Foo, Default-Bar


eureka:
 instance:
 hostname: eureka1.server.com
 lease-renewal-interval-in-seconds: 5
 lease-expiration-duration-in-seconds: 10
 client:
 service-url:
 defaultZone: http://eureka1.server.com:8701/eureka/,http://eureka2.server.com:8702/eureka/,http://eureka3.server.com:8703/eureka/
复制代码

default-filters:- My=true 主要增长了这个配置。

1.4 启动服务

访问http://localhost:8100/ribbon/sayHello?token=xxx,显示若是下:

日志输出结果:

2019-10-21 17:40:20.191  INFO 18059 --- [ctor-http-nio-2] c.netflix.config.ChainedDynamicProperty  : Flipping property: EUREKA-RIBBON.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
2019-10-21 17:40:20.192  INFO 18059 --- [ctor-http-nio-2] c.n.l.DynamicServerListLoadBalancer      : DynamicServerListLoadBalancer for client EUREKA-RIBBON initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=EUREKA-RIBBON,current list of Servers=[eureka1.server.com:8901],Load balancer stats=Zone stats: {defaultzone=[Zone:defaultzone;	Instance count:1;	Active connections count: 0;	Circuit breaker tripped count: 0;	Active connections per server: 0.0;]
},Server stats: [[Server:eureka1.server.com:8901;	Zone:defaultZone;	Total Requests:0;	Successive connection failure:0;	Total blackout seconds:0;	Last connection made:Thu Jan 01 08:00:00 CST 1970;	First connection made: Thu Jan 01 08:00:00 CST 1970;	Active Connections:0;	total failure count in last (1000) msecs:0;	average resp time:0.0;	90 percentile resp time:0.0;	95 percentile resp time:0.0;	min resp time:0.0;	max resp time:0.0;	stddev resp time:0.0]
]}ServerList:org.springframework.cloud.netflix.ribbon.eureka.DomainExtractingServerList@46c172ce
2019-10-21 17:40:20.583  INFO 18059 --- [ctor-http-nio-7] s.c.d.s.g.f.MyGatewayFilterFactory       : exchange request uri:http://localhost:8100/ribbon/sayHello?token=xxx,Time:582ms.params:{token=[xxx]}
2019-10-21 17:40:21.181  INFO 18059 --- [erListUpdater-0] c.netflix.config.ChainedDynamicProperty  : Flipping property: EUREKA-RIBBON.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
复制代码

总结

过滤器工厂的顶级的接口是GatewayFilterFactory,咱们能够直接继承它们的两个抽象类AbstractGatewayFilterFactory 和 AbstractNameValueGatewayFilterFactory来简化开发。区别在于AbstractGatewayFilterFactory是接受一个参数,AbstractNameValueGatewayFilterFactory是接收两个参数,例如:- AddResponseHeader=X-Response-Default-Foo, Default-Bar

结语

本文介绍了GatewayFilter、GlobalFilter和GatewayFilterFactory的简单使用方式,相信小伙伴们对Spring Cloud Gateway有了简单的了解。

代码地址

gitHub地址


《Srping Cloud 2.X小白教程》目录

  • 写做不易,转载请注明出处,喜欢的小伙伴能够关注公众号查看更多喜欢的文章。
  • 联系方式:4272231@163.com
相关文章
相关标签/搜索