spring cloud gateway之filter篇
转载请标明出处:
https://www.fangzhipeng.com
本文出自
在上一篇文章详细的介绍了gateway的predict,predict决定了请求由哪一个路由处理,在路由处理之前,需要经过“pre”类型的过滤器处理,处理返回响应之后,可以由“post”类型的过滤器处理。
filter的作用和生命周期
由filter工作流程点,可以知道filter有着非常重要的作用,在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,在“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等。首先需要弄清一点为什么需要网关这一层,这就不得不说下filter的作用了。
作用
当我们有很多个服务时,比如下图中的user-service、goods-service、sales-service等服务,客户端请求各个服务的api时,每个服务都需要做相同的事情,比如鉴权、限流、日志输出等。
对于这样重复的工作,有没有办法做的更好,答案是肯定的。在微服务的上一层加一个全局的权限控制、限流、日志输出的api gatewat服务,然后再将请求转发到具体的业务服务层。这个api gateway服务就是起到一个服务边界的作用,外接的请求访问系统,必须先通过网关层。
生命周期
spring cloud gateway同zuul类似,有“pre”和“post”两种方式的filter。客户端的请求先经过“pre”类型的filter,然后将请求转发到具体的业务服务,比如上图中的user-service,收到业务服务的响应之后,再经过“post”类型的filter处理,最后返回响应到客户端。
与zuul不同的是,filter除了分为“pre”和“post”两种方式的filter外,在spring cloud gateway中,filter从作用范围可分为另外两种,一种是针对于单个路由的gateway filter,它在配置文件中的写法同predict类似;另外一种是针对于所有路由的global gateway filer。现在从作用范围划分的维度来讲解这两种filter。
gateway filter
过滤器允许以某种方式修改传入的http请求或传出的http响应。过滤器可以限定作用在某些特定请求路径上。 spring cloud gateway包含许多内置的gatewayfilter工厂。
gatewayfilter工厂同上一篇介绍的predicate工厂类似,都是在配置文件application.yml中配置,遵循了约定大于配置的思想,只需要在配置文件配置gatewayfilter factory的名称,而不需要写全部的类名,比如addrequestheadergatewayfilterfactory只需要在配置文件中写addrequestheader,而不是全部类名。在配置文件中配置的gatewayfilter factory最终都会相应的过滤器工厂类处理。
spring cloud gateway 内置的过滤器工厂一览表如下:
现在挑几个常见的过滤器工厂来讲解,每一个过滤器工厂在官方文档都给出了详细的使用案例,如果不清楚的还可以在org.springframework.cloud.gateway.filter.factory看每一个过滤器工厂的源码。
addrequestheader gatewayfilter factory
创建工程,引入相关的依赖,包括spring boot 版本2.0.5,spring cloud版本finchley,gateway依赖如下:
<dependency> <groupid>org.springframework.cloud</groupid> <artifactid>spring-cloud-starter-gateway</artifactid> </dependency>
在工程的配置文件中,加入以下的配置:
server: port: 8081 spring: profiles: active: add_request_header_route --- spring: cloud: gateway: routes: - id: add_request_header_route uri: http://httpbin.org:80/get filters: - addrequestheader=x-request-foo, bar predicates: - after=2017-01-20t17:42:47.789-07:00[america/denver] profiles: add_request_header_route
在上述的配置中,工程的启动端口为8081,配置文件为add_request_header_route,在add_request_header_route配置中,配置了roter的id为add_request_header_route,路由地址为http://httpbin.org:80/get,该router有afterpredictfactory,有一个filter为addrequestheadergatewayfilterfactory(约定写成addrequestheader),addrequestheader过滤器工厂会在请求头加上一对请求头,名称为x-request-foo,值为bar。为了验证addrequestheadergatewayfilterfactory是怎么样工作的,查看它的源码,addrequestheadergatewayfilterfactory的源码如下:
public class addrequestheadergatewayfilterfactory extends abstractnamevaluegatewayfilterfactory { @override public gatewayfilter apply(namevalueconfig config) { return (exchange, chain) -> { serverhttprequest request = exchange.getrequest().mutate() .header(config.getname(), config.getvalue()) .build(); return chain.filter(exchange.mutate().request(request).build()); }; } }
由上面的代码可知,根据旧的serverhttprequest创建新的 serverhttprequest ,在新的serverhttprequest加了一个请求头,然后创建新的 serverwebexchange ,提交过滤器链继续过滤。
启动工程,通过curl命令来模拟请求:
curl localhost:8081
最终显示了从 http://httpbin.org:80/get得到了请求,响应如下:
{ "args": {}, "headers": { "accept": "*/*", "connection": "close", "forwarded": "proto=http;host=\"localhost:8081\";for=\"0:0:0:0:0:0:0:1:56248\"", "host": "httpbin.org", "user-agent": "curl/7.58.0", "x-forwarded-host": "localhost:8081", "x-request-foo": "bar" }, "origin": "0:0:0:0:0:0:0:1, 210.22.21.66", "url": "http://localhost:8081/get" }
可以上面的响应可知,确实在请求头中加入了x-request-foo这样的一个请求头,在配置文件中配置的addrequestheader过滤器工厂生效。
跟addrequestheader过滤器工厂类似的还有addresponseheader过滤器工厂,在此就不再重复。
rewritepath gatewayfilter factory
在nginx服务启中有一个非常强大的功能就是重写路径,spring cloud gateway默认也提供了这样的功能,这个功能是zuul没有的。在配置文件中加上以下的配置:
spring: profiles: active: rewritepath_route --- spring: cloud: gateway: routes: - id: rewritepath_route uri: https://blog.csdn.net predicates: - path=/foo/** filters: - rewritepath=/foo/(?<segment>.*), /$\{segment} profiles: rewritepath_route
上面的配置中,所有的/foo/**开始的路径都会命中配置的router,并执行过滤器的逻辑,在本案例中配置了rewritepath过滤器工厂,此工厂将/foo/(?
自定义过滤器
spring cloud gateway内置了19种强大的过滤器工厂,能够满足很多场景的需求,那么能不能自定义自己的过滤器呢,当然是可以的。在spring cloud gateway中,过滤器需要实现gatewayfilter和ordered2个接口。写一个requesttimefilter,代码如下:
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 0; } }
在上面的代码中,ordered中的int getorder()方法是来给过滤器设定优先级别的,值越大则优先级越低。还有有一个filteri(exchange,chain)方法,在该方法中,先记录了请求的开始时间,并保存在serverwebexchange中,此处是一个“pre”类型的过滤器,然后再chain.filter的内部类中的run()方法中相当于"post"过滤器,在此处打印了请求所消耗的时间。然后将该过滤器注册到router中,代码如下:
@bean public routelocator customerroutelocator(routelocatorbuilder builder) { // @formatter:off return builder.routes() .route(r -> r.path("/customer/**") .filters(f -> f.filter(new requesttimefilter()) .addresponseheader("x-response-default-foo", "default-bar")) .uri("http://httpbin.org:80/get") .order(0) .id("customer_filter_router") ) .build(); // @formatter:on }
重启程序,通过curl命令模拟请求:
curl localhost:8081/customer/123
在程序的控制台输出一下的请求信息的日志:
2018-11-16 15:02:20.177 info 20488 --- [ctor-http-nio-3] o.s.cloud.gateway.filter.gatewayfilter : /customer/123: 152ms
自定义过滤器工厂
在上面的自定义过滤器中,有没有办法自定义过滤器工厂类呢?这样就可以在配置文件中配置过滤器了。现在需要实现一个过滤器工厂,在打印时间的时候,可以设置参数来决定是否打印请参数。查看gatewayfilterfactory的源码,可以发现gatewayfilterfactory的层级如下:
过滤器工厂的*接口是gatewayfilterfactory,我们可以直接继承它的两个抽象类来简化开发abstractgatewayfilterfactory和abstractnamevaluegatewayfilterfactory,这两个抽象类的区别就是前者接收一个参数(像stripprefix和我们创建的这种),后者接收两个参数(像addresponseheader)。
过滤器工厂的*接口是gatewayfilterfactory,有2个两个较接近具体实现的抽象类,分别为abstractgatewayfilterfactory和abstractnamevaluegatewayfilterfactory,这2个类前者接收一个参数,比如它的实现类redirecttogatewayfilterfactory;后者接收2个参数,比如它的实现类addrequestheadergatewayfilterfactory类。现在需要将请求的日志打印出来,需要使用一个参数,这时可以参照redirecttogatewayfilterfactory的写法。
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
需要注意的是,在类的构造器中一定要调用下父类的构造器把config类型传过去,否则会报classcastexception
最后,需要在工程的启动文件application类中,向srping ioc容器注册requesttimegatewayfilterfactory类的bean。
@bean public requesttimegatewayfilterfactory elapsedgatewayfilterfactory() { return new requesttimegatewayfilterfactory(); }
然后可以在配置文件中配置如下:
spring: profiles: active: elapse_route --- 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
启动工程,在浏览器*问localhost:8081?name=forezp,可以在控制台上看到,日志输出了请求消耗的时间和请求参数。
global filter
spring cloud gateway根据作用范围划分为gatewayfilter和globalfilter,二者区别如下:
gatewayfilter : 需要通过spring.cloud.routes.filters 配置在具体路由下,只作用在当前路由上或通过spring.cloud.default-filters配置在全局,作用在所有路由上
globalfilter : 全局过滤器,不需要在配置文件中配置,作用在所有的路由上,最终通过gatewayfilteradapter包装成gatewayfilterchain可识别的过滤器,它为请求业务以及路由的uri转换为真实业务服务的请求地址的核心过滤器,不需要配置,系统初始化时加载,并作用在每个路由上。
spring cloud gateway框架内置的globalfilter如下:
上图中每一个globalfilter都作用在每一个router上,能够满足大多数的需求。但是如果遇到业务上的定制,可能需要编写满足自己需求的globalfilter。在下面的案例中将讲述如何编写自己globalfilter,该globalfilter会校验请求中是否包含了请求参数“token”,如何不包含请求参数“token”则不转发路由,否则执行正常的逻辑。代码如下:
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().getqueryparams().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(); }
启动工程,使用curl命令请求:
curl localhost:8081/customer/123
可以看到请没有被转发,请求被终止,并在控制台打印了如下日志:
2018-11-16 15:30:13.543 info 19372 --- [ctor-http-nio-2] gateway.tokenfilter : token is empty...
上面的日志显示了请求进入了没有传“token”的逻辑。
总结
本篇文章讲述了spring cloud gateway中的过滤器,包括gatewayfilter和globalfilter。从官方文档的内置过滤器讲起,然后讲解自定义gatewayfilter、gatewayfilterfactory以及自定义的globalfilter。有很多内置的过滤器并没有讲述到,比如限流过滤器,这个我觉得是比较重要和大家关注的过滤器,将在之后的文章讲述。
参考资料
https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.1.0.m1/single/spring-cloud-gateway.html
https://www.jianshu.com/p/eb3a67291050
https://blog.csdn.net/qq_36236890/article/details/80822051
https://windmt.com/2018/05/08/spring-cloud-14-spring-cloud-gateway-filter
源码下载
https://github.com/forezp/springcloudlearning/tree/master/sc-f-gateway-predicate
扫一扫,支持下作者吧
(转载本站文章请注明作者和出处 )
上一篇: 百度搜索2018年度排行榜出炉
推荐阅读
-
Spring之IOC篇 博客分类: Spring IOCSpringBeanXML设计模式
-
spring-boot-2.0.3不一样系列之源码篇 - run方法(四)之prepareContext,绝对有值得你看的地方
-
spring cloud gateway请求跨域问题解决方案
-
跟我学SpringCloud | 第六篇:Spring Cloud Config Github配置中心
-
Spring Cloud Gateway中异常处理
-
负载均衡之Spring Cloud Ribbon
-
Spring Boot2.0深度实践之核心技术篇|Spring Boot2.0视频教程下载 Spring Boot2.0
-
Spring Boot2.0深度实践之核心技术篇|Spring Boot2.0视频教程下载 Spring Boot2.0
-
Spring Cloud Gateway全局通用异常处理的实现
-
Spring-Cloud-GateWay