一辈子也不容错过学习的微服务网关与用户身份识别,Zuul过滤器
Zuul过滤器
Spring Cloud Zuul除了可以实现请求的路由功能外,还有一个重要的功能就是过滤器。Zuul可以通过定义过滤器来实现请求的拦截和过滤,而它本身的大部分功能也是通过过滤器实现的。
Zuul网关的过滤器类型
Zuul中定义了4种标准过滤器类型,分别说明如下:
1.pre类型的过滤器
此类型为请求路由之前调用的过滤器,可利用此类过滤器来实现身份验证、记录调试信息等。
2.route类型的过滤器
此类型为发送请求到上游服务的过滤器,比如使用ApacheHttpClient或Netflix Ribbon请求上游服务。
3.post类型的过滤器
此类型为上游服务返回之后调用的过滤器,可用来为响应添加HTTP响应头、收集统计信息和指标、将响应回复给客户端。
4.error类型的过滤器
此类型为在其他阶段发生错误时执行的过滤器。
除了默认的过滤器类型外,Zuul还允许我们创建自定义的过滤器类型,例如可以定制一种echo类型的过滤器,直接在Zuul中生成响应,而不将请求转发到上游的服务。
Zuul的请求处理流程如下:
(1)当外部请求到达Zuul网关时,首先会进入pre处理阶段,在这个阶段请求将被pre类型的过滤器处理,以完成再请求路由的前置过滤处理,比如请求的校验等。在完成pre类型的过滤处理之后,请求进入第二个阶段:route路由请求转发阶段。
(2)在route路由请求转发阶段,请求将被route类型的过滤器处理,route类型的过滤器将外部请求转发到上游的服务。当服务实例的结果返回之后,route阶段完成,请求进入第三个阶段:post处理阶段。
(3)在post处理阶段,请求将被post类型的过滤器处理,post类型的过滤器在处理的时候不仅可以获取请求信息,还能获取服务实例的返回信息,所以post阶段可以对处理结果进行一些加工或转换等。
(4)还有一个特殊的阶段error,在该阶段请求将被error类型的过滤器处理,在上述3个阶段发生异常时才会触发,但是error过滤器也能将最终结果返回给请求客户端。
Zuul的请求处理流程如图6-3所示。
图6-3 Zuul的请求处理流程
Zuul提供了一个动态读取、编译和运行过滤器的框架。过滤器不直接相互通信,而是通过RequestContext共享状态,RequestContext(请求上下文)实例对每个请求都是唯一的。
实战:用户的黑名单过滤
Zuul提供了一个过滤器ZuulFilter抽象基类,可以作为自定义过滤器的父类。定制一个过滤器需要实现的父类方法有4个,具体如下。
1.filterType方法
返回自定义过滤器的类型,以常量的形式定义在FilterConstants类中,具体代码如下:
package org.springframework.cloud.netflix.zuul.filters.support;
...
/**
*@author Spencer Gibb
*/
public class FilterConstants {
...
/**
*异常过滤
*/
public static final String ERROR_TYPE = "error";
/**
*后置过滤
*/
public static final String POST_TYPE = "post";
/**
*前置过滤
*/
public static final String PRE_TYPE = "pre";
/**
*路由过滤
*/
public static final String ROUTE_TYPE = "route";
...
}
2.filterOrder方法
返回过滤器顺序,值越小优先级越高。
3.shouldFilter方法
返回过滤器是否生效的boolean值,返回true代表生效,返回false代表不生效。比如,在请求处理过程中,需要根据请求中是否携带某个参数来判断是否需要过滤时,可以用shouldFilter方法对请求进行参数判断,并返回一个相应的boolean值。
如果直接返回true,那么该过滤器总是生效。
4.run方法
过滤器的处理逻辑。在该函数中,可以进行当前的请求拦截和参数定制,也可以进行后续的路由定制,同时可以进行返回结果的定制,等等。
下面是根据请求参数username进行用户黑名单过滤的例子,如果username的参数值在黑名单中,就对请求进行拦截。具体的代码如下:
package com.crazymaker.springcloud.cloud.center.zuul.filter;
//省略import
/**
*演示过滤器:黑名单过滤
*/
@Slf4j
@Component
public class DemoFilter extends ZuulFilter
{
/**
*示例所使用的黑名单:实际使用场景,需要从数据库或者其他来源获取
*/
static List<String> blackList = Arrays.asList("foo", "bar", "test");
/**过滤的执行类型*/
@Override
public String filterType()
{
//pre:路由之前
//routing:路由之时
//post:路由之后
//error:发送错误调用
return "pre";
} /**
*过滤的执行次序
*/
@Override
public int filterOrder()
{
return 0;
}
/**
*这里是判断逻辑—是否要执行过滤,true为跳过
*/
@Override
public boolean shouldFilter()
{
/***获取上下文*/
RequestContext ctx = RequestContext.getCurrentContext();
/***如果请求已经被其他的过滤器终止,本过滤器就不做处理*/
if (!ctx.sendZuulResponse())
{
return false;
}
/**
*获取请求
*/
HttpServletRequest request = ctx.getRequest();
/**
*返回true表示需要执行过滤器的run方法
*/
if (request.getRequestURI().startsWith("/ZuulFilter/demo"))
{
return true;
}
/**
*返回false表示需要跳过此过滤器,不执行run方法
*/
return false;
}
/**
*过滤器的具体逻辑
*通过请求中的用户名称参数判断是否在黑名单中
*/
@Override
public Object run()
{
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
/**
*对用户名称进行判断
*如果用户名称在黑名单中,就不再转发给后端的服务提供者
*/
String username = request.getParameter("username");
if (username != null && blackList.contains(username))
{
log.info(username + " is forbidden:" +
request.getRequestURL().toString());
/**
*终止后续的访问流程
*/
ctx.setSendZuulResponse(false);
try
{
ctx.getResponse().setContentType("text/html;charset=utf-8");
对不起
您已经进入黑名单 ctx.getResponse().getWriter().write("对不起,您已经进入黑名单");
} catch (Exception e)
{
e.printStackTrace();
}
return null;
}
return null;
}
}
在上面的代码中,
RequestContext.setSendZuulResponse(Boolean)方法在请求上下文中设置了标志位sendZuulResponse的值为false,表示不需要后续处理。上下文setSendZuulResponse标志位的值通过RequestContext.sendZuulResponse()方法获取。
Zuul内置的几乎所有过滤器都会对该标志位进行判断,如果其值为false,那么将不用对请求进行过滤处理。以非常重要的route类型RibbonRoutingFilter为例来看其shouldFilter方法的源码,具体代码如下:
package org.springframework.cloud.netflix.zuul.filters.route;
...
public class RibbonRoutingFilter extends ZuulFilter {
...
@Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
return (ctx.getRouteHost() == null && ctx.get(SERVICE_ID_KEY) != null
&& ctx.sendZuulResponse());
}
...
}
以上过滤器RibbonRoutingFilter的作用是通过结合Ribbon和Hystrix来向服务提供者实例发起请求,并将请求结果返回。它的判断条件中就有sendZuulResponse的标志位判断的部分,如果该值为false,就不再发起请求。