Spring Cloud 为 HTTP 请求的各个阶段提供了多个过滤器,这些过滤器的执行顺序由各自提供的一个 int 值决定,提供的值越小则优先级越高,默认的过滤器及优先级如下:
自定义过滤器
在默认过滤器的基础上,我们可以实现自己的自定义过滤器,自定义过滤器需要继承 com.netflix.zuul.ZuulFilter 类,并实现相关方法,说明如下:
- filterType:该函数需要返回一个字符串来代表过滤器的类型,而这个类型就是在HTTP请求过程中定义的各个阶段。在Zuul中默认定义了四种不同生命周期的过滤器类型,具体如下:
- pre:可以在请求被路由之前调用
- routing:在路由请求时候被调用
- post:在routing和error过滤器之后被调用
- error:处理请求时发生错误时被调用
- filterOrder:通过int值来定义过滤器的执行顺序,数值越小优先级越高
- shouldFilter:返回一个boolean类型来判断该过滤器是否要执行。我们可以通过此方法来指定过滤器的有效范围。
- run:过滤器的具体逻辑。在该函数中,我们可以实现自定义的过滤逻辑,来确定是否要拦截当前的请求,不对其进行后续的路由,或是在请求路由返回结果之后,对处理结果做一些加工等
Zuul 默认定义了四个不同的过滤器类型,它们覆盖了一个外部HTTP请求到达API网关,直到返回请求结果的全部生命周期。下图源自Zuul的官方WIKI中关于请求生命周期的图解,它描述了一个HTTP请求到达API网关之后,如何在各个不同类型的过滤器之间流转的详细过程如下:
我们可以看到,当外部 HTTP 请求到达 API 网关服务的时候,首先它会进入第一个阶段 pre,在这里它会被pre 类型的过滤器进行处理,该类型的过滤器主要目的是在进行请求路由之前做一些前置加工,比如请求的校验等。
在完成了 pre 类型的过滤器处理之后,请求进入第二个阶段 routing,也就是之前说的路由请求转发阶段,请求将会被 routing 类型过滤器处理,这里的具体处理内容就是将外部请求转发到具体服务实例上去的过程,当服务实例将请求结果都返回之后,routing 阶段完成,请求进入第三个阶段 post,此时请求将会被 post 类型的过滤器进行处理,这些过滤器在处理的时候不仅可以获取到请求信息,还能获取到服务实例的返回信息,所以在 post 类型的过滤器中,我们可以对处理结果进行一些加工或转换等内容。
另外,还有一个特殊的阶段error,该阶段只有在上述三个阶段中发生异常的时候才会触发,但是它的最后流向还是post类型的过滤器,因为它需要通过post过滤器将最终结果返回给请求客户端。
过滤器示例
- 验证过滤器
该过滤器会在 pre 阶段执行,并且其优先级为 1 在包装请求体后执行,其 shouldFilter 返回 true 表示任何情况都执行该过滤器,在 run 方法通过获取 HttpServletRequest 示例获取请求参数并进行验证,在验证失败的时候通过设置 HttpServletResponse 示例和 setSendZuulResponse(false) 来直接返回响应信息,而不进行后续过滤器处理。
package org.lixue.zuul;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class TokenPreZullFilter extends ZuulFilter{
@Override
public String filterType(){
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder(){
return 1;
}
@Override
public boolean shouldFilter(){
return true;
}
@Override
public Object run(){
RequestContext ctx=RequestContext.getCurrentContext();
HttpServletRequest request=ctx.getRequest();
System.out.println(String.format("%s request to %s",request.getMethod(),request.getRequestURL().toString()));
String token=request.getParameter("token");
System.out.println("token:"+token);
if(token==null||!token.equals("success_token")){
//认证失败
System.out.println("token验证失败");
HttpServletResponse response=ctx.getResponse();
response.setCharacterEncoding("utf-8");//设置字符集
response.setContentType("text/html;charset=utf-8");//设置相应格式
response.setStatus(401);
ctx.setSendZuulResponse(false);//不进行路由
try{
response.getWriter().write("token验证失败");//响应体
}catch(IOExceptione){
System.out.println("responseio异常");
e.printStackTrace();
}
ctx.setResponse(response);
return null;
}
System.out.println("token验证成功");
return null;
}
}
- 过滤器配置类
为了让 Spring 容器知道过滤器的存在,需要对该类进行配置,创建过滤器配置类,使用注解 @Configuration 标注类,并使用 @Bean 注解标注返回过滤器的方法。
package org.lixue.zuul;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ZuulFilterConfiguration{
@Bean
public TokenPreZullFilter tokenPreZullFilter(){
return newTokenPreZullFilter();
}
}
- 测试验证
项目依赖一个 eureka-sserver、service-provider 服务,首先启动 eureka-server 和 service-provider 服务,然后启动 spring-cloud-zuul-microservices 服务,访问 http://localhost:9200/hello/speaks?names=123&token=success_token 地址,可以看到能正常返回,如下:
{"123":"Hello World 123 Port=8080"}
修改访问地址,移除 token 参数或者修改参数值 http://localhost:9200/hello/speaks?names=123&token=success_token999 这时会返回 token 验证失败的错误,并且没有执行后续的路由处理。