文档章节

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

毛毛向前冲V5
 毛毛向前冲V5
发布于 10/21 17:59
字数 2038
阅读 16
收藏 0

前言

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

[toc]

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

概术

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

Gateway Filter

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

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,属性的值为当前时间的毫秒数,然后请求结束后,又会将请求的时间数取出来来和当前时间数做差,得到耗时时间数。

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

  • 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;

/**
 * @auther: fujie.feng
 * @DateT: 2019-10-12
 */
@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地址,页面显示结果如下:

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

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中

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,显示如下: 我可以看到显示访问是不生效的,我们在请求中加如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地址


<center><font color=red>《Srping Cloud 2.X小白教程》目录</font></center>


本文由博客一文多发平台 OpenWrite 发布!

© 著作权归作者所有

毛毛向前冲V5
粉丝 0
博文 18
码字总数 23921
作品 0
私信 提问
Spring Cloud Gateway VS Zuul 比较,怎么选择?

Spring Cloud Gateway 是 Spring Cloud Finchley 版推出来的新组件,用来代替服务网关:Zuul。 那 Spring Cloud Gateway 和 Zuul 都有哪些区别呢,咱们来比较一下。 1、开源组织 Spring Clo...

Java技术栈
05/10
980
0
微服务网关Zuul迁移到Spring Cloud Gateway

背景 在之前的文章中,我们介绍过微服务网关Spring Cloud Netflix Zuul,前段时间有两篇文章专门介绍了Spring Cloud的全新项目Spring Cloud Gateway,以及其中的过滤器工厂。本文将会介绍将微...

aoho
2018/09/24
0
0
spring cloud gateway之filter篇

版权声明:本文为博主原创文章,欢迎转载,转载请注明作者、原文超链接 ,博主地址:http://blog.csdn.net/forezp。 https://blog.csdn.net/forezp/article/details/85057268 转载请标明出处...

方志朋
2018/12/17
0
0
Spring Cloud Greenwich.SR3 发布

Spring Cloud Greenwich.SR3 已发布,可在 Maven Central 中获取。更新情况如下: 新的负载均衡器实现 Spring Cloud Greenwich.M3 是第一个包含阻塞和非阻塞负载均衡器客户端实现的版本,作为...

xplanet
09/16
3.1K
3
微服务下使用网关 Spring Cloud Gateway

Spring Cloud Gateway 工作原理 客户端向 Spring Cloud Gateway 发出请求,如果请求与网关程序定义的路由匹配,则将其发送到网关 Web 处理程序,此处理程序运行特定的请求过滤器链。 过滤器之...

Anoyi
2018/06/27
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Java自学指南五、编码工具

工欲善其事,必先利其器... 想要高效率编码输出和快速处理文本,需要 熟悉编程语言的语法 盲打 至少需要熟练使用一种文本编辑器 至少熟练熟练使用一种 IDE (集成开发环境) 现实中,看到过二指...

ConstXiong
42分钟前
6
0
Java 集合框架

早在 Java 2 中之前,Java 就提供了特设类。比如:Dictionary, Vector, Stack, 和 Properties 这些类用来存储和操作对象组。 虽然这些类都非常有用,但是它们缺少一个核心的,统一的主题。由...

天子剑毅
57分钟前
7
0
Oracle 回滚段

--查询数据文件 select t.TABLESPACE_NAME, --表空间名 t.FILE_NAME, --文件名 t.AUTOEXTENSIBLE, --是否自动扩展 t.BYTES / 1024 / 1024 as tsize, --表空间初始大小 t.MAXBYTE...

max佩恩
今天
6
0
在Serverless Kubernetes(ASK)集群中使用Nginx Ingress

ASK: Alibaba Cloud Serverless Kubernetes 导读 不同于阿里云ACK集群默认通过nginx-ingress-controller提供ingress能力,在ASK(Serverless Kubernetes)集群中默认基于SLB七层转发提供ingre...

阿里云官方博客
今天
5
0
Android换肤原理和Android-Skin-Loader框架解析

https://blog.csdn.net/stven_king/article/details/78648095 https://www.jianshu.com/p/b0253de8ac04 https://blog.csdn.net/weixin_38261570/article/details/82079540......

shzwork
今天
7
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部