Spring Cloud 微服务(21) --- Zuul(二)
Zuul Filter 链

Zuul 的核心逻辑是由一系列紧密配合工作的 Filter 来实现的,能够在进行 HTTP 请求或响应的时候执行相关操作。

Zuul Filter

Zuul Filter 的特点

  • Filter 类型:Filter 类型决定了当前的 Filter 在整个 Filter 链中的执行顺序。
  • Filter 执行顺序:同一种类型的 Filter 通过 filterOrder() 来设置执行顺序
  • Filter 执行条件:Filter 执行所需的标准、条件
  • Filter 执行效果:符合某个条件,产生的执行结果

Zuul 内部提供了一个动态读取、编译、运行这些 Filter 的机制。Filter 之间不直接通信,在请求线程中会通过 RequestContext 共享状态,内部使用 ThreadLocal 实现,也可以在 Filter 之间使用 ThreadLocal 收集自己需要的状态、数据

Zuul Filter 的执行逻辑源码在 com.netflix.zuul.http.ZuulServlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
try {
this.init((HttpServletRequest)servletRequest, (HttpServletResponse)servletResponse);
RequestContext context = RequestContext.getCurrentContext(); // 通过 RequestContext 获取共享状态
context.setZuulEngineRan();

try {
this.preRoute(); // 执行请求之前的操作
} catch (ZuulException var13) {
this.error(var13); // 出现错误的操作
this.postRoute();
return;
}

try {
this.route(); // 路由操作
} catch (ZuulException var12) {
this.error(var12);
this.postRoute();
return;
}

try {
this.postRoute(); // 请求操作
} catch (ZuulException var11) {
this.error(var11);
}
} catch (Throwable var14) {
this.error(new ZuulException(var14, 500, "UNHANDLED_EXCEPTION_" + var14.getClass().getName()));
} finally {
RequestContext.getCurrentContext().unset();
}
}

Zuul 生命周期

Zuul 官方文档中,生命周期图。
Zuul Life Cycle

但官方文档的生命周期图不太准确。

  • 在 postRoute 执行之前,即 postFilter 执行之前,如果没有出现过错误,会调用 error 方法,并调用 this.error(new ZuulException) 打印堆栈信息
  • 在 postRoute 执行之前就已经报错,会调用 error 方法,再调用 postRoute,但是之后会直接 return,不会调用 this.error(new ZuulException) 打印堆栈信息

由此可以看出,整个 Filter 调用链的重点可能是 postFilter 也可能是 errorFilter

Zuul Life Cycle

pre、route 出现错误后,进入 error,再进入 post,再返回
pre、route 没有出现错误,进入 post,如果出现错误,再进入 error,再返回

  • pre:在 Zuul 按照规则路由到下级服务之前执行。如果需要对请求进行预处理,如:鉴权、限流等,都需要在此 Filter 实现
  • route:Zuul 路由动作的执行者,是 Http Client、Ribbon 构建和发送原始 HTTP 请求的地方
  • post:源服务返回结果或异常信息发生后执行,如果需要对返回值信息做处理,需要实现此类 Filter
  • error:整个生命周期发生异常,都会进入 error Filter,可做全局异常处理。

Filter 之间,通过 com.netflix.zuul.context.RequestContext 类进行通信,内部采用 ThreadLocal 保存每个请求的一些信息,包括:请求路由、错误信息、HttpServletRequest、HTTPServletResponse,扩展了 ConcurrentHashMap,目的是为了在处理过程中保存任何形式的信息


Zuul 原生 Filter

整合 spring-boot-starter-actuator 后,查看 idea 控制台 endpoints 栏的 mappings,可以看到多了几个 Actuator 端点

routes 端点

访问 http://localhost:8989/actuator/routes 可以查看当前 zuul server 映射了几个路径、服务

1
2
3
4
{
"/provider/**": "spring-cloud-provider-service-simple",
"/spring-cloud-provider-service-simple/**": "spring-cloud-provider-service-simple"
}

访问 http://localhost:8989/actuator/routes/details 可以查看具体的映射信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"/provider/**": {
"id": "spring-cloud-provider-service-simple", // serviceId
"fullPath": "/provider/**", // 映射 path
"location": "spring-cloud-provider-service-simple", // 服务名称,实际上也是 serviceId
"path": "/**", // 实际访问路径
"prefix": "/provider", // 访问前缀
"retryable": false, // 是否开启重试
"customSensitiveHeaders": false, // 是否自定义了敏感 header
"prefixStripped": true // 是否去掉前缀(如果为 false,则实际访问时需要加 前缀,且实际请求的访问路径也会加上前缀)
},
"/spring-cloud-provider-service-simple/**": {
"id": "spring-cloud-provider-service-simple",
"fullPath": "/spring-cloud-provider-service-simple/**",
"location": "spring-cloud-provider-service-simple",
"path": "/**",
"prefix": "/spring-cloud-provider-service-simple",
"retryable": false,
"customSensitiveHeaders": false,
"prefixStripped": true
}
}

filters 端点

访问 http://localhost:8989/actuator/filters ,返回当前 zuul 的所有 filters

Zuul Filters

内置 Filters

名称类型顺序描述
ServletDetectionFilterpre-3通过 Spring Dispatcher 检查请求是否通过
Servlet30WrapperFilterpre-2适配 HttpServletRequest 为 Servlet30RequestWrapper 对象
FormBodyWrapperFilterpre-1解析表单数据,并为下游请求进行重新编码
DebugFilterpre1Debug 路由标识
PreDecorationFilterpre5处理请求上下文供后续使用,设置下游相关头信息
RibbonRoutingFilterroute10使用 Ribbon、Hystrix、嵌入式 HTTP 客户端发送请求
SimpleHostRoutingFilterroute100使用 Apache Httpclient 发送请求
SendForwardFilterroute500使用 Servlet 转发请求
SendResponseFilterpost1000将代理请求的响应写入当前响应
SendErrorFiltererror0如果 RequestContext.getThrowable() 不为空,则转发到 error.path 哦诶之的路径

如果使用 @EnableZuulServer 注解,将减少 PreDecorationFilterRibbonRoutingFilterSimpleHostRoutingFilter

如果要替换到某个原生的 Filter,可以自实现一个和原生 Filter 名称、类型一样的 Filter,并替换。或者禁用掉某个filter,并自实现一个新的。
禁用语法: zuul.{SimpleClassName}.{filterType}.disable=true,如 zuul.SendErrorFilter.error.disable=true


多级业务处理

在 Zuul Filter 链体系中,可以把一组业务逻辑细分,然后封装到一个个紧密结合的 Filter,设置处理顺序,组成一组 Filter 链。

自定义实现 Filter

在 Zuul 中实现自定义 Filter,继承 ZuulFilter 类即可,ZuulFilter 是一个抽象类,需要实现以下几个方法

  • String filterType:使用返回值设定 Filter 类型,可以设置为 prerouteposterror
  • int filterOrder:使用返回值设置 Filter 执行次序
  • boolean shouldFilter:使用返回值设定该 Filter 是否执行,可以作为开关来使用
  • Object run:Filter 的核心执行逻辑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 自定义 ZuulFilter
public class FirstPreFilter extends ZuulFilter {
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}

@Override
public int filterOrder() {
return 0;
}

@Override
public boolean shouldFilter() {
return true;
}

@Override
public Object run() throws ZuulException {
System.out.println("自定义 Filter,类型为 pre!");
return null;
}
}


// 注入 Spring 容器
@Bean
public FirstPreFilter firstPreFilter(){
return new FirstPreFilter();
}

此时访问 http://localhost:8989/provider/get-result ,查看控制台:

1
2
3
4
5
Initializing Servlet 'dispatcherServlet'
Completed initialization in 0 ms
自定义 Filter,类型为 pre!
Flipping property: spring-cloud-provider-service-simple.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
Shutdown hook installed for: NFLoadBalancer-PingTimer-spring-cloud-provider-service-simple

业务处理

使用 SecondFilter 验证是否传入参数 a,ThirdPreFilter 验证是否传入参数 b,在 PostFilter 统一处理返回内容。

SecondPreFilter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class SecondPreFilter extends ZuulFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(SecondPreFilter.class);
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}

@Override
public int filterOrder() {
return 2;
}

@Override
public boolean shouldFilter() {
return true;
}

@Override
public Object run() throws ZuulException {
LOGGER.info(">>>>>>>>>>>>> SecondPreFilter ! <<<<<<<<<<<<<<<<");
// 获取上下文
RequestContext requestContext = RequestContext.getCurrentContext();
// 从上下文获取 request
HttpServletRequest request = requestContext.getRequest();
// 从 request 获取参数 a
String a = request.getParameter("a");
// 如果参数 a 为空
if (StringUtils.isBlank(a)) {
LOGGER.info(">>>>>>>>>>>>>>>> 参数 a 为空! <<<<<<<<<<<<<<<<");
// 禁止路由,禁止访问下游服务
requestContext.setSendZuulResponse(false);
// 设置 responseBody,供 postFilter 使用
requestContext.setResponseBody("{\"status\": 500, \"message\": \"参数 a 为空!\"}");
// 用于下游 Filter 判断是否执行
requestContext.set("logic-is-success", false);
// Filter 结束
return null;
}
requestContext.set("logic-is-success", true);
return null;
}
}

ThirdPreFilter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class ThirdPreFilter extends ZuulFilter {

private static final Logger LOGGER = LoggerFactory.getLogger(ThirdPreFilter.class);

@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}

@Override
public int filterOrder() {
return 3;
}

@Override
public boolean shouldFilter() {
RequestContext context = RequestContext.getCurrentContext();
// 获取上下文中的 logic-is-success 中的值,用于判断当前 filter 是否执行
return (boolean) context.get("logic-is-success");
}

@Override
public Object run() throws ZuulException {
LOGGER.info(">>>>>>>>>>>>> ThirdPreFilter ! <<<<<<<<<<<<<<<<");
// 获取上下文
RequestContext requestContext = RequestContext.getCurrentContext();
// 从上下文获取 request
HttpServletRequest request = requestContext.getRequest();
// 从 request 获取参数 a
String a = request.getParameter("b");
// 如果参数 a 为空
if (StringUtils.isBlank(a)) {
LOGGER.info(">>>>>>>>>>>>>>>> 参数 b 为空! <<<<<<<<<<<<<<<<");
// 禁止路由,禁止访问下游服务
requestContext.setSendZuulResponse(false);
// 设置 responseBody,供 postFilter 使用
requestContext.setResponseBody("{\"status\": 500, \"message\": \"参数 b 为空!\"}");
// 用于下游 Filter 判断是否执行
requestContext.set("logic-is-success", false);
// Filter 结束
return null;
}
requestContext.set("logic-is-success", true);
return null;
}
}

PostFilter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

public class PostFilter extends ZuulFilter {

private static final Logger LOGGER = LoggerFactory.getLogger(PostFilter.class);

@Override
public String filterType() {
return FilterConstants.POST_TYPE;
}

@Override
public int filterOrder() {
return 0;
}

@Override
public boolean shouldFilter() {
return true;
}

@Override
public Object run() throws ZuulException {
LOGGER.info(">>>>>>>>>>>>>>>>>>> Post Filter! <<<<<<<<<<<<<<<<");
RequestContext context = RequestContext.getCurrentContext();
// 处理返回中文乱码
context.getResponse().setCharacterEncoding("UTF-8");
// 获取上下文保存的 responseBody
String responseBody = context.getResponseBody();
// 如果 responseBody 不为空,则证明流程中有异常发生
if (StringUtils.isNotBlank(responseBody)) {
// 设置返回状态码
context.setResponseStatusCode(500);
// 替换响应报文
context.setResponseBody(responseBody);
}
return null;
}
}

访问 http://localhost:8989/provider/addhttp://localhost:8989/provider/add?a=1http://localhost:8989/provider/add?a=1&b=1 ,查看控制台

控制台:

1
2
3
4
2019-02-18 14:09:44.890  INFO 5800 --- [nio-8989-exec-7] c.l.g.z.s.filter.FirstPreFilter          : >>>>>>>>>>>>>>>>> 自定义 Filter,类型为 pre! <<<<<<<<<<<<<<<<<<
2019-02-18 14:09:44.890 INFO 5800 --- [nio-8989-exec-7] c.l.g.z.s.filter.SecondPreFilter : >>>>>>>>>>>>> SecondPreFilter ! <<<<<<<<<<<<<<<<
2019-02-18 14:09:44.890 INFO 5800 --- [nio-8989-exec-7] c.l.g.z.s.filter.SecondPreFilter : >>>>>>>>>>>>>>>> 参数 a 为空! <<<<<<<<<<<<<<<<
2019-02-18 14:09:44.890 INFO 5800 --- [nio-8989-exec-7] c.l.g.z.s.filter.PostFilter : >>>>>>>>>>>>>>>>>>> Post Filter! <<<<<<<<<<<<<<<<

1
2
3
4
5
2019-02-18 14:10:13.004  INFO 5800 --- [nio-8989-exec-5] c.l.g.z.s.filter.FirstPreFilter          : >>>>>>>>>>>>>>>>> 自定义 Filter,类型为 pre! <<<<<<<<<<<<<<<<<<
2019-02-18 14:10:13.004 INFO 5800 --- [nio-8989-exec-5] c.l.g.z.s.filter.SecondPreFilter : >>>>>>>>>>>>> SecondPreFilter ! <<<<<<<<<<<<<<<<
2019-02-18 14:10:13.004 INFO 5800 --- [nio-8989-exec-5] c.l.g.z.s.filter.ThirdPreFilter : >>>>>>>>>>>>> ThirdPreFilter ! <<<<<<<<<<<<<<<<
2019-02-18 14:10:13.004 INFO 5800 --- [nio-8989-exec-5] c.l.g.z.s.filter.ThirdPreFilter : >>>>>>>>>>>>>>>> 参数 b 为空! <<<<<<<<<<<<<<<<
2019-02-18 14:10:13.005 INFO 5800 --- [nio-8989-exec-5] c.l.g.z.s.filter.PostFilter : >>>>>>>>>>>>>>>>>>> Post Filter! <<<<<<<<<<<<<<<<
1
2
3
4
2019-02-18 14:10:28.488  INFO 5800 --- [nio-8989-exec-9] c.l.g.z.s.filter.FirstPreFilter          : >>>>>>>>>>>>>>>>> 自定义 Filter,类型为 pre! <<<<<<<<<<<<<<<<<<
2019-02-18 14:10:28.488 INFO 5800 --- [nio-8989-exec-9] c.l.g.z.s.filter.SecondPreFilter : >>>>>>>>>>>>> SecondPreFilter ! <<<<<<<<<<<<<<<<
2019-02-18 14:10:28.488 INFO 5800 --- [nio-8989-exec-9] c.l.g.z.s.filter.ThirdPreFilter : >>>>>>>>>>>>> ThirdPreFilter ! <<<<<<<<<<<<<<<<
2019-02-18 14:10:28.500 INFO 5800 --- [nio-8989-exec-9] c.l.g.z.s.filter.PostFilter : >>>>>>>>>>>>>>>>>>> Post Filter! <<<<<<<<<<<<<<<<

返回值:

1
{"status": 500, "message": "参数 a 为空!"}

1
{"status": 500, "message": "参数 b 为空!"}
1
result is : a + b = 2

由此验证自定义 Zuul Filter 成功。


-------------本文结束 感谢您的阅读-------------
0%