前面学习了zuul的反向代理、负载均衡、fallback回退。这张学习写过滤器filter,做java web开发的对filter都不陌生,那就是客户端(如浏览器)发起请求的时候,都先经过过滤器filter做一些相关的校验或业务判断(如登录、权限等),zuul也同样提供了过滤器功能。只要继承ZuulFilter类即可。
通过前文的介绍,我们对于Zuul的第一印象通常是这样的:它包含了对请求的路由和过滤两个功能,其中路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础;而过滤器功能则负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能的基础。
然而实际上,路由功能在真正运行时,它的路由映射和请求转发都是由几个不同的过滤器完成的。其中,路由映射主要通过pre类型的过滤器完成,它将请求路径与配置的路由规则进行匹配,以找到需要转发的目标地址;而请求转发的部分则是由route类型的过滤器来完成,对pre类型过滤器获得的路由地址进行转发。
所以,过滤器可以说是Zuul实现API网关功能最为核心的部件,每一个进入Zuul的HTTP请求都会经过一系列的过滤器处理链得到请求响应并返回给客户端。
继承ZuulFilter.java,需要实现一些方法
package com.fei.springcloud.filter;
import com.netflix.zuul.ZuulFilter;
public class TestFilter extends ZuulFilter{
@Override
public boolean shouldFilter() {
return false;
}
@Override
public Object run() {
return null;
}
@Override
public String filterType() {
return null;
}
@Override
public int filterOrder() {
return 0;
}
}
它们各自的含义与功能总结如下:
从上图中,我们可以看到,当外部HTTP请求到达API网关服务的时候,首先它会进入第一个阶段pre,在这里它会被pre类型的过滤器进行处理,该类型的过滤器主要目的是在进行请求路由之前做一些前置加工,比如请求的校验等。
在完成了pre类型的过滤器处理之后,请求进入第二个阶段routing,也就是之前说的路由请求转发阶段,请求将会被routing类型过滤器处理,这里的具体处理内容就是将外部请求转发到具体服务实例上去的过程,当服务实例将请求结果都返回之后,routing阶段完成,请求进入第三个阶段post,此时请求将会被post类型的过滤器进行处理,这些过滤器在处理的时候不仅可以获取到请求信息,还能获取到服务实例的返回信息,所以在post类型的过滤器中,我们可以对处理结果进行一些加工或转换等内容。
另外,还有一个特殊的阶段error,该阶段只有在上述三个阶段中发生异常的时候才会触发,但是它的最后流向还是post类型的过滤器,因为它需要通过post过滤器将最终结果返回给请求客户端(实际实现上还有一些差别)
Zuul自带的核心过滤器
在spring-cloud-netflix-core-XXX.jar架包下
如上图所示,在默认启用的过滤器中包含了三种不同生命周期的过滤器,这些过滤器都非常重要,可以帮助我们理解Zuul对外部请求处理的过程,以及帮助我们如何在此基础上扩展过滤器去完成自身系统需要的功能。下面,我们将逐个地对这些过滤器做一些详细的介绍:
ServletDetectionFilter:它的执行顺序为-3,是最先被执行的过滤器。该过滤器总是会被执行,主要用来检测当前请求是通过Spring的DispatcherServlet处理运行,还是通过ZuulServlet来处理运行的。
它的检测结果会以布尔类型保存在当前请求上下文的isDispatcherServletRequest参数中,这样在后续的过滤器中,我们就可以通过RequestUtils.isDispatcherServletRequest()和RequestUtils.isZuulServletRequest()方法判断它以实现做不同的处理。
一般情况下,发送到API网关的外部请求都会被Spring的DispatcherServlet处理,除了通过/zuul/路径访问的请求会绕过DispatcherServlet,被ZuulServlet处理,主要用来应对处理大文件上传的情况。另外,对于ZuulServlet的访问路径/zuul/,我们可以通过zuul.servletPath参数来进行修改。
Servlet30WrapperFilter:它的执行顺序为-2,是第二个执行的过滤器。目前的实现会对所有请求生效,主要为了将原始的HttpServletRequest包装成Servlet30RequestWrapper对象。
FormBodyWrapperFilter:它的执行顺序为-1,是第三个执行的过滤器。
该过滤器仅对两种类请求生效,第一类是Content-Type为application/x-www-form-urlencoded的请求,第二类是Content-Type为multipart/form-data并且是由Spring的DispatcherServlet处理的请求(用到了ServletDetectionFilter的处理结果)。
而该过滤器的主要目的是将符合要求的请求体包装成FormBodyRequestWrapper对象。
DebugFilter:它的执行顺序为1,是第四个执行的过滤器。
该过滤器会根据配置参数zuul.debug.request和请求中的debug参数来决定是否执行过滤器中的操作。而它的具体操作内容则是将当前的请求上下文中的debugRouting和debugRequest参数设置为true。
由于在同一个请求的不同生命周期中,都可以访问到这两个值,所以我们在后续的各个过滤器中可以利用这两值来定义一些debug信息,这样当线上环境出现问题的时候,可以通过请求参数的方式来激活这些debug信息以帮助分析问题。
另外,对于请求参数中的debug参数,我们也可以通过zuul.debug.parameter来进行自定义。
PreDecorationFilter:它的执行顺序为5,是pre阶段最后被执行的过滤器。该过滤器会判断当前请求上下文中是否存在forward.to和serviceId参数,如果都不存在,那么它就会执行具体过滤器的操作(如果有一个存在的话,说明当前请求已经被处理过了,因为这两个信息就是根据当前请求的路由信息加载进来的)。
而它的具体操作内容就是为当前请求做一些预处理,比如:进行路由规则的匹配、在请求上下文中设置该请求的基本信息以及将路由匹配结果等一些设置信息等,这些信息将是后续过滤器进行处理的重要依据,我们可以通过RequestContext.getCurrentContext()来访问这些信息。
另外,我们还可以在该实现中找到一些对HTTP头请求进行处理的逻辑,其中包含了一些耳熟能详的头域,比如:X-Forwarded-Host、X-Forwarded-Port。
另外,对于这些头域的记录是通过zuul.addProxyHeaders参数进行控制的,而这个参数默认值为true,所以Zuul在请求跳转时默认地会为请求增加X-Forwarded-*头域,包括:X-Forwarded-Host、X-Forwarded-Port、X-Forwarded-For、X-Forwarded-Prefix、X-Forwarded-Proto。
我们也可以通过设置zuul.addProxyHeaders=false关闭对这些头域的添加动作。
代码实战,完整代码在github上
写2个pre类型的filter,1个post类型的filter.如果在第一个pre过滤器验证就失败了,则后面的过滤器不需要执行了。
TestPre01Filter.java
package com.fei.springcloud.filter;
import javax.servlet.http.HttpServletRequest;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
/**
* 第一个pre类型的filter,prefilter01=true才能通过
* @author Jfei
*
*/
public class TestPre01Filter extends ZuulFilter{
/**
* 是否应该执行该过滤器,如果是false,则不执行该filter
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* 过滤器类型
* 顺序: pre ->routing -> post ,以上3个顺序出现异常时都可以触发error类型的filter
*/
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
/**
* 同filterType类型中,order值越大,优先级越低
*/
@Override
public int filterOrder() {
return 1;
}
/**
* 执行业务操作,可执行sql,nosql等业务
*/
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String prefilter01 = request.getParameter("prefilter01");
System.out.println("执行pre01Filter .....prefilter01=" + prefilter01 );
//如果用户名和密码都正确,则继续执行下一个filter
if("true".equals(prefilter01) ){
ctx.setSendZuulResponse(true);//会进行路由,也就是会调用api服务提供者
ctx.setResponseStatusCode(200);
ctx.set("isOK",true);//可以把一些值放到ctx中,便于后面的filter获取使用
}else{
ctx.setSendZuulResponse(false);//不需要进行路由,也就是不会调用api服务提供者
ctx.setResponseStatusCode(401);
ctx.set("isOK",false);//可以把一些值放到ctx中,便于后面的filter获取使用
//返回内容给客户端
ctx.setResponseBody("{\"result\":\"pre01Filter auth not correct!\"}");// 返回错误内容
}
return null;
}
}
这是第一个自定义的pre类型filter,说以shuldFilter()为true,也就是必须执行
TestPre02Filter.java
package com.fei.springcloud.filter;
import javax.servlet.http.HttpServletRequest;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
/**
* prefilter02 校验 prefilter02=true才能通过
* @author Jfei
*
*/
public class TestPre02Filter extends ZuulFilter{
/**
* 是否应该执行该过滤器,如果是false,则不执行该filter
*/
@Override
public boolean shouldFilter() {
//上一个filter设置该值
return RequestContext.getCurrentContext().getBoolean("isOK");
}
/**
* 过滤器类型
* 顺序: pre ->routing -> post ,以上3个顺序出现异常时都可以触发error类型的filter
*/
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
/**
* 同filterType类型中,order值越大,优先级越低
*/
@Override
public int filterOrder() {
return 2;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String prefilter02 = request.getParameter("prefilter02");
System.out.println("执行pre02Filter .....prefilter02=" + prefilter02 );
//如果用户名和密码都正确,则继续执行下一个filter
if("true".equals(prefilter02) ){
ctx.setSendZuulResponse(true);//会进行路由,也就是会调用api服务提供者
ctx.setResponseStatusCode(200);
ctx.set("isOK",true);//可以把一些值放到ctx中,便于后面的filter获取使用
}else{
ctx.setSendZuulResponse(false);//不需要进行路由,也就是不会调用api服务提供者
ctx.setResponseStatusCode(401);
ctx.set("isOK",false);//可以把一些值放到ctx中,便于后面的filter获取使用
//返回内容给客户端
ctx.setResponseBody("{\"result\":\"pre02Filter auth not correct!\"}");// 返回错误内容
}
return null;
}
}
package com.fei.springcloud.filter;
import javax.servlet.http.HttpServletRequest;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
/**
*
* post类型的filter,post=true才能通过
*
*/
public class TestPostFilter extends ZuulFilter{
/**
* 是否应该执行该过滤器,如果是false,则不执行该filter
*/
@Override
public boolean shouldFilter() {
//上一个filter设置该值
return RequestContext.getCurrentContext().getBoolean("isOK");
}
/**
* 过滤器类型
* 顺序: pre ->routing -> post ,以上3个顺序出现异常时都可以触发error类型的filter
*/
@Override
public String filterType() {
return FilterConstants.ROUTE_TYPE;
}
/**
* 同filterType类型中,order值越大,优先级越低
*/
@Override
public int filterOrder() {
return 1;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String post = request.getParameter("post");
System.out.println("执行postFilter .....post=" + post );
//如果用户名和密码都正确,则继续执行下一个filter
if("true".equals(post) ){
ctx.setSendZuulResponse(true);//会进行路由,也就是会调用api服务提供者
ctx.setResponseStatusCode(200);
ctx.set("isOK",true);//可以把一些值放到ctx中,便于后面的filter获取使用
}else{
ctx.setSendZuulResponse(false);//不需要进行路由,也就是不会调用api服务提供者
ctx.setResponseStatusCode(401);
ctx.set("isOK",false);//可以把一些值放到ctx中,便于后面的filter获取使用
//返回内容给客户端
ctx.setResponseBody("{\"result\":\"post auth not correct!\"}");// 返回错误内容
}
return null;
}
}
package com.fei.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.Bean;
import com.fei.springcloud.filter.TestPre02Filter;
import com.fei.springcloud.filter.TestPostFilter;
import com.fei.springcloud.filter.TestPre01Filter;
@EnableZuulProxy
@SpringCloudApplication
public class ZuulFilterApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulFilterApplication.class, args);
}
@Bean
public TestPre01Filter testPre01Filter(){
return new TestPre01Filter();
}
@Bean
public TestPre02Filter testPre02Filter(){
return new TestPre02Filter();
}
@Bean
public TestPostFilter testPostFilter(){
return new TestPostFilter();
}
}
http://127.0.0.1:8888/user-api/user/find?prefilter01=true
http://127.0.0.1:8888/user-api/user/find?prefilter01=true&prefilter02=true
对比页面看到的内容和控制台打印的日志,会发现如果pre的校验都通不过的时候,api微服务就没打印了,说明没调用api微服务,post filter也没执行
完整代码