前言

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

本文基于前两篇文章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; @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地址


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


  • 写作不易,转载请注明出处,喜欢的小伙伴可以关注公众号查看更多喜欢的文章。
  • 联系方式:4272231@163.com

spring cloud 2.x版本 Gateway自定义过滤器教程的更多相关文章

  1. spring cloud 2.x版本 Gateway动态路由教程

    摘要 本文采用的Spring cloud为2.1.8RELEASE,version=Greenwich.SR3 本文基于前面的几篇Spring cloud Gateway文章的实现. 参考 Gatew ...

  2. spring cloud 2.x版本 Gateway路由网关教程

    前言 本文采用Spring cloud本文为2.1.8RELEASE,version=Greenwich.SR3 本文基于前两篇文章eureka-server.eureka-client.eureka ...

  3. spring cloud 2.x版本 Gateway熔断、限流教程

    前言 本文采用Spring cloud本文为2.1.8RELEASE,version=Greenwich.SR3 本文基于前两篇文章eureka-server.eureka-client.eureka ...

  4. spring cloud 2.x版本 Eureka Client服务提供者教程

    本文采用Spring cloud本文为2.1.8RELEASE,version=Greenwich.SR3 1 创建eureka client 1.1 新建Srping boot工程:eureka-c ...

  5. spring cloud 2.x版本 Zuul路由网关教程

    前言 本文采用Spring cloud本文为2.1.8RELEASE,version=Greenwich.SR3 本文基于前两篇文章eureka-server.eureka-client.eureka ...

  6. spring cloud 2.x版本 Ribbon服务发现教程(内含集成Hystrix熔断机制)

    本文采用Spring cloud本文为2.1.8RELEASE,version=Greenwich.SR3 前言 本文基于前两篇文章eureka-server和eureka-client的实现. 参考 ...

  7. spring cloud 2.x版本 Feign服务发现教程(内含集成Hystrix熔断机制)

    前言 本文采用Spring cloud本文为2.1.8RELEASE,version=Greenwich.SR3 本文基于前两篇文章eureka-server和eureka-client的实现. 参考 ...

  8. spring cloud 2.x版本 Hystrix Dashboard断路器教程

    前言 本文采用Spring cloud本文为2.1.8RELEASE,version=Greenwich.SR3 本文基于前两篇文章eureka-server.eureka-client.eureka ...

  9. spring cloud 2.x版本 Config配置中心教程

    前言 本文采用Spring cloud本文为2.1.8RELEASE,version=Greenwich.SR3 本文基于前面的文章eureka-server的实现. 参考 eureka-server ...

随机推荐

  1. svn忽略文件不提交至服务器的方法

    在提交界面,右键加入忽略提交列表.可以实现忽略本地文件不提交,且不删除服务器上的文件.

  2. appium---模拟点击事件

    在做自动化的过程中都会遇到一些无法定位到的地方,或者通过元素怎么都定位不成功的地方,这个时候我们可以使用必杀技,通过坐标定位.具体的怎么操作呢? swipe点击事件 前面安静写过一篇关于swipe的滑 ...

  3. python-基础-def(*agrs,**kwagrs)

    1.*args,返回的数据类型为 tuple,使用方法如下图代码:**kwargs 返回的数据类型为 dict 使用方法如下图代码. def KeyWord_s(arg): print(arg,typ ...

  4. [译]Vulkan教程(03)开发环境

    [译]Vulkan教程(03)开发环境 这是我翻译(https://vulkan-tutorial.com)上的Vulkan教程的第3篇. In this chapter we'll set up y ...

  5. 多线程编程学习七( Fork/Join 框架).

    一.介绍 使用 java8 lambda 表达式大半年了,一直都知道底层使用的是 Fork/Join 框架,今天终于有机会来学学 Fork/Join 框架了. Fork/Join 框架是 Java 7 ...

  6. linux自建https证书

    一.生成单向认证的https证书 建立服务器私钥,生成RSA秘钥. 会有两次要求输入密码, 然后获得了一个server.key文件. 以后使用此文件(通过openssl提供的命令或API)可能经常回要 ...

  7. C++入门到理解阶段二基础篇(8)——C++指针

    1.什么是指针? 为了更加清楚的了解什么是指针?我们首先看下变量和内存的关系,当我们定义了int a=10之后.相当于在内存之中找了块4个字节大小的空间,并且存储10,要想操作这块空间,就通过a这个变 ...

  8. DOM介绍以及使用方法

    DOM的基本讲解 一.DOM (Document Object Model)文档对象模型 1.有属性有方法 var person = { name:'派大星', fav:function(){ } } ...

  9. 一起学Android之Xml与Json解析

    概述 在网络中,数据交互通常是以XML和Json的格式进行,所以对这两种格式的数据进行解析,是Android开发中的必备功能,本文以一个简单的小例子,简述Android开发中Xml和Json解析的常用 ...

  10. python Windows环境下文件路径问题

    转自:http://blog.sina.com.cn/s/blog_5ee7254801013zu7.html 在python程序里面我们经常需要对文件进行操作,Windows下的文件目录路径使用反斜 ...