Zuul 的核心逻辑是由一系列紧密配合工作的 Filter 来实现的,能够在进行 HTTP 请求或响应的时候执行相关操作。
Zuul 内部提供了一个动态读取、编译、运行这些 Filter 的机制。Filter 之间不直接通信,在请求线程中会通过 RequestContext 共享状态,内部使用 ThreadLocal 实现,也可以在 Filter 之间使用 ThreadLocal 收集自己需要的状态、数据
Zuul Filter 的执行逻辑源码在 com.netflix.zuul.http.ZuulServlet 中
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 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:源服务返回结果或异常信息发生后执行,如果需要对返回值信息做处理,需要实现此类 Filtererror:整个生命周期发生异常,都会进入 error Filter,可做全局异常处理。Filter 之间,通过 com.netflix.zuul.context.RequestContext 类进行通信,内部采用 ThreadLocal 保存每个请求的一些信息,包括:请求路由、错误信息、HttpServletRequest、HTTPServletResponse,扩展了 ConcurrentHashMap,目的是为了在处理过程中保存任何形式的信息
整合 spring-boot-starter-actuator 后,查看 idea 控制台 endpoints 栏的 mappings,可以看到多了几个 Actuator 端点
访问 http://localhost:8989/actuator/routes 可以查看当前 zuul server 映射了几个路径、服务
{ "/provider/**": "spring-cloud-provider-service-simple", "/spring-cloud-provider-service-simple/**": "spring-cloud-provider-service-simple" }访问 http://localhost:8989/actuator/routes/details 可以查看具体的映射信息
{ "/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 } }访问 http://localhost:8989/actuator/filters ,返回当前 zuul 的所有 filters
Zuul Filters
如果使用 @EnableZuulServer 注解,将减少 PreDecorationFilter、RibbonRoutingFilter、SimpleHostRoutingFilter
如果要替换到某个原生的 Filter,可以自实现一个和原生 Filter 名称、类型一样的 Filter,并替换。或者禁用掉某个filter,并自实现一个新的。 禁用语法: zuul.{SimpleClassName}.{filterType}.disable=true,如 zuul.SendErrorFilter.error.disable=true
在 Zuul Filter 链体系中,可以把一组业务逻辑细分,然后封装到一个个紧密结合的 Filter,设置处理顺序,组成一组 Filter 链。
在 Zuul 中实现自定义 Filter,继承 ZuulFilter 类即可,ZuulFilter 是一个抽象类,需要实现以下几个方法
String filterType:使用返回值设定 Filter 类型,可以设置为 pre、route、post、errorint filterOrder:使用返回值设置 Filter 执行次序boolean shouldFilter:使用返回值设定该 Filter 是否执行,可以作为开关来使用Object run:Filter 的核心执行逻辑 // 自定义 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 ,查看控制台:
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
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
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
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/add 、http://localhost:8989/provider/add?a=1 、http://localhost:8989/provider/add?a=1&b=1 ,查看控制台
控制台:
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! <<<<<<<<<<<<<<<< 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! <<<<<<<<<<<<<<<< 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! <<<<<<<<<<<<<<<<返回值:
{"status": 500, "message": "参数 a 为空!"} {"status": 500, "message": "参数 b 为空!"} result is : a + b = 2由此验证自定义 Zuul Filter 成功。
作者:laiyy0728 链接:https://www.jianshu.com/p/e8126da2f4fd 来源:简书 简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。