> 文章列表 > Zuul源码解析(一)

Zuul源码解析(一)

Zuul源码解析(一)

说在前面

我们公司有一个线上服务报错通知群,经常报网关服务的一个 EOFException 异常。这个异常报出来好久了,如下图所示,艾特相关的人也不去处理,大概是不重要异常吧,反正看样子是不影响线上核心业务流程。

然后我上级让我优化下这类日志打印,把无法解析的参数,把原url打印出来,另外把这类日志等级调整成 warn,不需要处理的就不要用 error 一直报警了。

在根据打印的日志堆栈信息中,可以看到这些日志主要是 Zuul 默认的 SendErrorFilter 打印的,在思考怎么优化这类日志的同时也冒出很多对 Zuul 的疑问:

  1. 既然这些日志是 Zuul 框架打印的 error 级别的日志,并且 error 已经是最高级别了,我还能调整级别?我又如何我新增打印参数如 url,请求体等信息?
  2. 堆栈链路中的 ZuulServlet.service() 方法是啥?
  3. Zuul 网关请求和 DispatcherServlet 有什么联系?它们之间又是如何协作的?
  4. Zuul 有一堆的 Filter,它们是如何串联起来的?它是如何路由发起请求的?

当时脑子里真的是一堆的疑问,它驱使着我翻看 zuul 源码的好奇心愈发增强。

于是就有了这边关于 Zuul 网关的源码解析。

下面,我将从我当时对 Zuul 的疑问点为切入点,是如何一步步带着问题去阅读源码,把整个流程给弄明白的思路给你们讲清楚。也许,我当时的疑问也正式你的疑问呢,希望可以帮助有需要的人。

Zuul 源码解析

Zuul 的本质就是 Servlet 和 Filter 链。Servlet 是业务入口以及控制整个流程,Filter 链处理整个请求的一些逻辑,包括前置处理,转发路由,后置处理返回响应等等

Zuul 的核心流程

Zuul 的核心流程主要封装在 ZuulServlet 中的 service() 方法中

public class ZuulServlet extends HttpServlet {// 省略了部分代码方法和属性// ...private ZuulRunner zuulRunner;@Overridepublic void service(ServletRequest servletRequest,ServletResponse servletResponse){try {// 初始化 RequestContext,该 RequestContext 是贯穿 Zuul 处理请求的生命周期的// 主要是暂存 request,response,异常信息等处理请求过程中中间结果init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);;try {// 处理全部 pre 类型的 FilterpreRoute();} catch (ZuulException e) {// 抛异常时,在在 error() 方法中存储该异常到 RequestContext 上下文中,后续会用到的// RequestContext.getCurrentContext().setThrowable(e);// 接着再处理所有 error 类型 Filtererror(e);// 接着在执行所有 post 类型的 FilterpostRoute();// 就这样,一个请求在报错下经过 Zuul 的生命周期就结束了return;}try {// preRoute() 没有异常,则执行 route 类型的 Filterroute();} catch (ZuulException e) {// 同上备注error(e);postRoute();return;}try {// route() 没有异常,则执行 所有 post 类型 FilterpostRoute();} catch (ZuulException e) {error(e);return;}} catch (Throwable e) {error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));} finally {RequestContext.getCurrentContext().unset();}}// ...
}

preRoute() 方法中,处理 Filter 的逻辑是委托给 zuulRunner 去完成的,其他两个 error()postRoute() 同理。

	// ZuulServlet#preRoute()void preRoute() throws ZuulException {zuulRunner.preRoute();}

可以理解 ZuulServlet 是入口,是控制执行流程的,类似于 DisPatcherServlet。而干实事的是 ZuulRunner,ZuulRunner 的主要职责是主要有两个:

  1. 初始化 requests 和 responses 到 RequestContext 中
	// ZuulRunner#init()public void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {RequestContext ctx = RequestContext.getCurrentContext();if (bufferRequests) {ctx.setRequest(new HttpServletRequestWrapper(servletRequest));} else {ctx.setRequest(servletRequest);}ctx.setResponse(new HttpServletResponseWrapper(servletResponse));}

2)封装 preRoute(), route(), postRoute()error() 的 FilterProcessor 调用

	// 如 ZuulRunner#postRoute()public void postRoute() throws ZuulException {// 看 FilterProcessor(拦截器处理器)名字就知道这是应用了典型的拦截器模式FilterProcessor.getInstance().postRoute();}

再看看 FilterProcessor 的处理逻辑

public class FilterProcessor {//...// 执行所有 "post" filters. public void postRoute() throws ZuulException {try {// runFilters 方法只有一个参数// 它里面主要做的就是通过查找所有 post 类型的 FIlter,然后根据每个 ZuulFilter 的 Order 进行排序// 排完序之后,执行 shouldFilter(),满足条件的话,在执行 Filter 的实际逻辑方法 run()runFilters("post");} catch (ZuulException e) {// 其中一个 Filter 出错则抛出异常throw e;} catch (Throwable e) {throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_POST_FILTER_" + e.getClass().getName());}}// 执行所有 "error" filters. public void error() {try {runFilters("error");} catch (Throwable e) {// 其中一个 Filter 出错则抛出异常// 注意这里,在 FilterProcessor 执行所有 error 类型的 Filter 时,只会打印该异常,不会再抛出。// 文章开头中的日志截图中,其中有一条就是在这里打印的。logger.error(e.getMessage(), e);}}// 执行所有 "route" filters. public void route() throws ZuulException {try {runFilters("route");} catch (ZuulException e) {// 其中一个 Filter 出错则抛出异常throw e;} catch (Throwable e) {throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_ROUTE_FILTER_" + e.getClass().getName());}}// 执行所有 "pre" filterspublic void preRoute() throws ZuulException {try {runFilters("pre");} catch (ZuulException e) {// 其中一个 Filter 出错则抛出异常throw e;} catch (Throwable e) {throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_PRE_FILTER_" + e.getClass().getName());}}// ...
}

如上源代码备注所示,流程已经很清晰了。说实话,由于自己当时对 Zuul 的基础知识不够了解,当时看到这个流程就在想,Zuul 有哪些默认的 Filter 呢,发起服务调用是在哪里,route() 方法吗,意思是发送服务调用也是在 Filter 中去完成?

RouteLocator 路由定位器的职责。

在讲 Zuul 默认的 Filter 之前,我觉得有必要先了解 ZuulProperties,RouteLocator 等基础设施类。

ZuulProperties

@ConfigurationProperties("zuul")
public class ZuulProperties {private String prefix = "";private Boolean retryable = false;/*** Map of route names to properties.*/private Map<String, ZuulRoute> routes = new LinkedHashMap<>();// ...public static class ZuulRoute {private String id;/*** The path (pattern) for the route, e.g. /foo/**.*/private String path;// 可以理解是服务名称private String serviceId;// 路由转发 url 全路径,它和 serviceId 是二选一的。// 我们服务中没有使用它private String url;// ...// 先看着,后面会调用public String getLocation() {if (StringUtils.hasText(this.url)) {// url 不为空,返回 urlreturn this.url;}// 否则,返回 serviceIdreturn this.serviceId;}}
}

也许上面配置类中属性看的有点抽象的话,不妨看看如下我目前所在公司的路由配置实例:

#路由规则配置
zuul.routes.usercenter.path = /usercenter/**
zuul.routes.usercenter.serviceId = USERCENTER
zuul.routes.platform.path = /platform/**
zuul.routes.platform.serviceId = PLATFORM
zuul.routes.paycenter.path = /paycenter/**
zuul.routes.paycenter.serviceId = paycenter
...

使用者怎么理解呢?比如一个常规请求:https://网关域名/usercenter/user/detail/1

Zuul 就会取 /usercenter/user/detail/1 去匹配如上配置中的 path 属性,发现 /usercenter/** 符合该路径的匹配规则,就找到对应的 serviceId = USERCENTER,那么就会路由到 USERCENTER 这个服务的 /user/detail/1 接口。

RouteLocator

// 路由定位器
// 怎么理解呢,主要是做什么的呢,看接口就很直观了
public interface RouteLocator {Collection<String> getIgnoredPaths();// 获取所有的路由配置// 路由定位器和其他组件的交互,是最终把定位的 Routes 以 list 的方式提供出去。后面源码中很多地方都会调用// 如上面的配置例子中:zuul.routes.usercenter.path = /usercenter/**//				    zuul.routes.usercenter.serviceId = USERCENTER// 					这就组成一个 Route 实体List<Route> getRoutes();// 根据请求 path,比如如上例子中的 /usercenter/user/detail/1,匹配到一个 Route// Route 这个类与 ZuulProperties 的内部类 ZuulRoute 非常的相似,都是描述路由的一个类// 可以理解 ZuulRoute 的配置用的实体路由类, Route 是业务中使用的路由类Route getMatchingRoute(String path);}

RouteLocator 有两个实现类,分别是 SimpleRouteLocator 和 DiscoveryClientRouteLocator

SimpleRouteLocator

SimpleRouteLocator(简单路由定位器) 我认为是最核心的一个路由定位器类。它主要负责管理 ZuulProperties 配置的 Route。

这里我也是重点关注它的 getRoutes()getMatchingRoute(String path) 两个方法。因为我觉得了解了这两个方法的实现,对后续阅读 理解 zuul 的核源码非常有必要的。

// 该类的重要属性如下:
public class SimpleRouteLocator implements RouteLocator, Ordered {// ...// 配置文件中的路由信息private ZuulProperties properties;private PathMatcher pathMatcher = new AntPathMatcher();// 可以理解是 ZuulProperties 的一个路径模板 path -> ZuulRoute 的 map 缓存// 目的是避免重复在 ZuulProperties 中计算得到 Route 列表private AtomicReference<Map<String, ZuulRoute>> routes = new AtomicReference<>();// ...
}
getRoutes()

获取所有的路由

@Override
public List<Route> getRoutes() {List<Route> values = new ArrayList<>();// 这几行代码很简单,没啥好说的。我们主要关注下 getRoutesMap() 方法for (Entry<String, ZuulRoute> entry : getRoutesMap().entrySet()) {ZuulRoute route = entry.getValue();String path = route.getPath();values.add(getRoute(route, path));}return values;
}

getRoutesMap() 方法:

// 这个方法可以理解就是初始化上面的 map 缓存(路径模板path -> ZuulRoute)
protected Map<String, ZuulRoute> getRoutesMap() {// 为空,则初始化if (this.routes.get() == null) {// 重点关注下 locateRoutes() 方法this.routes.set(locateRoutes());}return this.routes.get();
}

locateRoutes() 方法:

// 该方法是 protected ,一般情况下要自定义 RouteLocator 时,会重写该方法,比如我需要从数据库中加载 Route
protected Map<String, ZuulRoute> locateRoutes() {LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<String, ZuulRoute>();// 看这里,就是遍历了 ZuulProperties 中的 routes 属性for (ZuulRoute route : this.properties.getRoutes().values()) {routesMap.put(route.getPath(), route);}return routesMap;
}
getMatchingRoute()

根据请求 url(requestUri)匹配 Route

@Override
public Route getMatchingRoute(final String path) {// 直接调用下方这个方法return getSimpleMatchingRoute(path);}protected Route getSimpleMatchingRoute(final String path) {// ...// This is called for the initialization done in getRoutesMap()getRoutesMap();// ...String adjustedPath = adjustPath(path);// 根据 requestUri 返回一个 ZuulRoute// 具体实现,往下看ZuulRoute route = getZuulRoute(adjustedPath);// 前面说到业务中使用的都是 Route 实体,并不是 ZuulRoute// 调用 getRoute() 方法就是把 ZuulRoute 转换加工成 Route 返回return getRoute(route, adjustedPath);
}protected ZuulRoute getZuulRoute(String adjustedPath) {if (!matchesIgnoredPatterns(adjustedPath)) {// getRoutesMap() 获取前面缓存好的 route 配置 Map// 遍历,根据模板 path 模糊匹配 requestUrifor (Entry<String, ZuulRoute> entry : getRoutesMap().entrySet()) {String pattern = entry.getKey();// 重点在这里,使用 pathMatcher 来匹配一个 ZuulRouteif (this.pathMatcher.match(pattern, adjustedPath)) {// 匹配则返回一个 ZuulRoutereturn entry.getValue();}}}return null;
}// 刷新 routes 缓存时调用的,咋暂时不管
protected void doRefresh() {this.routes.set(locateRoutes());
}

DiscoveryClientRouteLocator

我习惯叫这个 RouteLocator 为服务发现路由定位器。DiscoveryClientRouteLocator 继承自 SimpleRouteLocator,它的 getRoutes() 方法中,除了获取 ZuulProperties 配置的 zuulRoute 外,还包括从注册中心拉取的动态路由配置。

怎么理解它呢?比如:你部署上线一个新应用服务名称为 ordercenter,然后你不再需要在网关服务中新增 apollo 配置,你就可以向网关发起请求,类似 https://网关域名/ordercenter/order/detail/1001 的方式,网关 Zuul 也会根据你的意愿转发请求到新服务 ordercenter。因为 DiscoveryClientRouteLocator 它会动态拉取新的服务路由配置,动态刷新本地路由集合。

接下,我们来看它是如何实现的。.

public class DiscoveryClientRouteLocator extends SimpleRouteLocatorimplements RefreshableRouteLocator {// 服务发现 clientprivate DiscoveryClient discovery;private ZuulProperties properties;private ServiceRouteMapper serviceRouteMapper;public void addRoute(String path, String location) {this.properties.getRoutes().put(path, new ZuulRoute(path, location));refresh();}// ...// 返回 ZuulProperties 中的路由和服务发现拉取的路由@Overrideprotected LinkedHashMap<String, ZuulRoute> locateRoutes() {LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<String, ZuulRoute>();// 把 ZuulProperties 中的路由配置加进来routesMap.putAll(super.locateRoutes());if (this.discovery != null) {// 静态服务 map,也就相当于遍历 ZuulProperties 中的路由配置,把他添加到 staticServices map 中去Map<String, ZuulRoute> staticServices = new LinkedHashMap<String, ZuulRoute>();for (ZuulRoute route : routesMap.values()) {String serviceId = route.getServiceId();if (serviceId == null) {serviceId = route.getId();}if (serviceId != null) {staticServices.put(serviceId, route);}}/*** 添加服务发现中的路由*/// 通过 DiscoveryClient 获取所有的服务名称(比如 usercenter,ordercenter 等)List<String> services = this.discovery.getServices();// ...// 遍历获取到的所有的服务名称for (String serviceId : services) {// staticServices 静态服务配置是否已经配置了if (staticServices.containsKey(serviceId)&& staticServices.get(serviceId).getUrl() == null) {ZuulRoute staticRoute = staticServices.get(serviceId);if (!StringUtils.hasText(staticRoute.getLocation())) {// 赋值 locationstaticRoute.setLocation(serviceId);}}// 如果静态服务配置没有配置if (!PatternMatchUtils.simpleMatch(ignored, serviceId)&& !routesMap.containsKey(key)) {// 那么它添加到 routesMap 中去。routesMap.put(key, new ZuulRoute(key, serviceId));}}}// ...// 该方法返回值 mapLinkedHashMap<String, ZuulRoute> values = new LinkedHashMap<>();// 遍历 routesMapfor (Entry<String, ZuulRoute> entry : routesMap.entrySet()) {String path = entry.getKey();// ...values.put(path, entry.getValue());}return values;}// 这个方法一般是监听器 Listener 监听到注册中心更新事件时出发本地路由的更新@Overridepublic void refresh() {doRefresh();}}

CompositeRouteLocator

这有点类似于 WebMvcConfigurerComposite,CompositeRouteLocator 就是一个集成了所有 RouteLocator 实现的一个组合类

所以在后面源码中,出现 RouteLocator,都是 CompositeRouteLocator 的实现。

public class CompositeRouteLocator implements RefreshableRouteLocator {// 默认情况下,zuul 的自动配置中会注册默认的 DiscoveryClientRouteLocator 和 SimpleRouteLocator 实现 beanprivate final Collection<? extends RouteLocator> routeLocators;private ArrayList<RouteLocator> rl;public CompositeRouteLocator(Collection<? extends RouteLocator> routeLocators) {Assert.notNull(routeLocators, "'routeLocators' must not be null");rl = new ArrayList<>(routeLocators);AnnotationAwareOrderComparator.sort(rl);this.routeLocators = rl;}// 这个方法组合了所有 RouteLocator 的实现(包括 DiscoveryClientRouteLocator 和 SimpleRouteLocator)@Overridepublic List<Route> getRoutes() {List<Route> route = new ArrayList<>();// 编译每个 RouteLocator,然后调用每个 RouteLocator 的 getRoutes()方法聚合全部 Routefor (RouteLocator locator : routeLocators) {route.addAll(locator.getRoutes());}return route;}@Overridepublic Route getMatchingRoute(String path) {for (RouteLocator locator : routeLocators) {Route route = locator.getMatchingRoute(path);if (route != null) {return route;}}return null;}@Overridepublic void refresh() {for (RouteLocator locator : routeLocators) {if (locator instanceof RefreshableRouteLocator) {((RefreshableRouteLocator) locator).refresh();}}}
}

CompositeRouteLocator#getRoutes() 方法这里其实我存在一个疑问的,我抛出来给你们:

SimpleRouteLocator#getRoutes() 包含了 ZuulProperties 的路由,而 DiscoveryClientRouteLocator 继承了 SimpleRouteLocator,它的 getRoutes() 包含了 ZuulProperties 的路由,然后 CompositeRouteLocator#getRoutes() 方法把这两个实现获取的 Route 集合通过 addAll() 的的形式再组合成一个新的集合,那这样的 ZuulProperties 的配置路由不就重复了么,尽管是不影响路由匹配,你们觉得呢?

RefreshableRouteLocator

当路由发生改变时,route locator 可以刷新本地缓存的一个接口,目前 DiscoveryClientRouteLocator 实现了该接口。

该接口就一个方法:

public interface RefreshableRouteLocator extends RouteLocator {void refresh();}

当 Listener 监听到注册中心更新事件时会触发本地路由的更新

https://qncdn.wanshifu.com/d12824966d80c37c7b8e1570c160b83f

还不知道这行代码的 this.zuulHandlerMapping.setDirty(true); 先往下看,后面就知道了。

Zuul 默认的 filter

再回到前面埋的坑,Zuul 有哪些默认的 Filter 呢,发送服务调用也是在 Filter 中去完成么,答案:是的

Pre 过滤器

Pre 类型的过滤器主要是在路由转发请求前的前置处理,比如对 request 的包装,初始化真正转发时需要的一些参数,匹配 Route 等等

ServletDetectionFilter(了解即可)
public class ServletDetectionFilter extends ZuulFilter {// ... @Overridepublic int filterOrder() {// 优先级为-3,数字越小,越先执行return SERVLET_DETECTION_FILTER_ORDER;}@Overridepublic boolean shouldFilter() {return true; }@Overridepublic Object run() {RequestContext ctx = RequestContext.getCurrentContext();// 阅读源码过程中,存在过这样一个疑问:执行到这里时,request 设置到RequestContext 中了么? // ZuulRunner#init() 中已经赋值了HttpServletRequest request = ctx.getRequest();if (!(request instanceof HttpServletRequestWrapper) && isDispatcherServletRequest(request)) {// 如果是 DispatcherServlet 过来的,则存储该标识// 一般的网关请求都是 DispatcherServlet 进来的,我后面会说到。ctx.set(IS_DISPATCHER_SERVLET_REQUEST_KEY, true);} else {ctx.set(IS_DISPATCHER_SERVLET_REQUEST_KEY, false);}return null;}	 	// ... 
}

检测当前请求是通过 Spring 的 DispatcherServlet 处理运行的,还是通过 ZuulServlet 来处理运行的。它的检测结果会以布尔类型保存在当前请求上下文的 isDispatcherServletRequest 参数中,这样后续的过滤器中,我们就可以通过 RequestUtils.isDispatcherServletRequest() 和 ~方法来判断请求处理的源头,以实现后续不同的处理机制

Servlet30WrapperFilter(了解即可)

优先级 -2,ServletDetectionFilter 之后执行

	@Overridepublic boolean shouldFilter() {return true;@Overridepublic Object run() {RequestContext ctx = RequestContext.getCurrentContext();HttpServletRequest request = ctx.getRequest();if (request instanceof HttpServletRequestWrapper) {request = (HttpServletRequest) ReflectionUtils.getField(this.requestField,request);ctx.setRequest(new Servlet30RequestWrapper(request));}// 是否是 DispatcherServlet 过来的else if (RequestUtils.isDispatcherServletRequest()) {// 包装原始 requestctx.setRequest(new Servlet30RequestWrapper(request));}return null;}

讲到这里,觉得有必要说下 Zuul 的 com.netflix.zuul.http.HttpServletRequestWrapper

HttpServletRequestWrapper 是对原始 HttpServletRequest 的一个包装。我们知道 HttpServletRequest 的 getInputStream() 方法是不可重复读的。

所以 Zuul 就对它做了一个包装的增强,使得 getInputStream() 可以重复读。原理也很简单,就是本地缓存。

而 Servlet30RequestWrapper 也是继承了 com.netflix.zuul.http.HttpServletRequestWrapper 的,那就相当于在这个过滤器中存储到 RequestContext 中的 request 是可以重复读取 getInputStream() 的。

FormBodyWrapperFilter(了解即可)

它的执行顺序为 -1,是第三个执行的过滤器。该过滤器仅对两类请求生效,第一类是 Context-Type 为 application/x-www-form-urlencoded 的请求,第二类是 Context-Type 为 multipart/form-data 并且是由 Spring 的 DispatcherServlet 处理的请求(用到了ServletDetectionFilter 的处理结果)。而该过滤器的主要目的是将符合要求的请求体包装成 FormBodyRequestWrapper 对象。

PS: FormBodyRequestWrapper 是继承了 Servlet30RequestWrapper。

DebugFilter(了解即可)

它的执行顺序为 1,是第四个执行的过滤器。该过滤器会根据配置参数 zuul.debug.request 和请求中的 debug 参数来决定是否执行过滤器中的操作。而它的具体操作内容是将当前请求上下文中的 debugRouting 和 debugRequest 参数设置为 true。由于在同一个请求的不同生命周期都可以访问到这二个值,所以我们在后续的各个过滤器中可以利用这二个值来定义一些 debug 信息,这样当线上环境出现问题的时候,可以通过参数的方式来激活这些 debug 信息以帮助分析问题,另外,对于请求参数中的 debug 参数,我们可以通过zuul.debug.parameter 来进行自定义。

PreDecorationFilter(较为重要)

执行顺序是 5,是 pre 阶段最后被执行的过滤器,主要是给当前请求上下文中赋值 forward.toserviceId 参数,这两个参数在后面转发和发起调用具体服务时会用到。

public class PreDecorationFilter extends ZuulFilter {@Overridepublic boolean shouldFilter() {RequestContext ctx = RequestContext.getCurrentContext();return !ctx.containsKey(FORWARD_TO_KEY) // a filter has already forwarded&& !ctx.containsKey(SERVICE_ID_KEY); // a filter has already determined serviceId}@Overridepublic Object run() {RequestContext ctx = RequestContext.getCurrentContext();final String requestURI = this.urlPathHelper.getPathWithinApplication(ctx.getRequest());// 请求 uri 匹配获取 RouteRoute route = this.routeLocator.getMatchingRoute(requestURI);if (route != null) {// 还记得吗,前面 ZuulProperties 有介绍过这个方法。// 所以这里获取的 location 是 Route 配置中的 url or serviceId。String location = route.getLocation();// locationy 以 http 或 https 开头if (location.startsWith(HTTP_SCHEME+":") || location.startsWith(HTTPS_SCHEME+":")) {// 存储在ctx 上下文中,SimpleHostRoutingFilter 会使用使用 该routeHost 发起http 请求完成路由转发ctx.setRouteHost(getUrl(location));ctx.addOriginResponseHeader(SERVICE_HEADER, location);}// 如果 location 是以 "forward:" 开头,存储对应的属性,SendForwardFilter 会使用到else if (location.startsWith(FORWARD_LOCATION_PREFIX)) {ctx.set(FORWARD_TO_KEY,StringUtils.cleanPath(location.substring(FORWARD_LOCATION_PREFIX.length()) + route.getPath()));ctx.setRouteHost(null);return null;}else {// 这个分支,是我们公司主要跑的这种情况,set serviceId,支持服务发现,负载均衡等// RibbonRoutingFilterctx.set(SERVICE_ID_KEY, location);ctx.setRouteHost(null);ctx.addOriginResponseHeader(SERVICE_ID_HEADER, location);}}}
}

Route 过滤器

这类型就是路由过滤去,比如请求转发服务。

RibbonRoutingFilter

该过滤器只对请求上下文中存在 serviceId 参数的请求进行处理,即只对通过 serviceId 配置路由规则的请求生效

public class RibbonRoutingFilter extends ZuulFilter {@Overridepublic String filterType() {return ROUTE_TYPE;}// order 值为 5,是 Route 类型执行的第一个 Filter@Overridepublic int filterOrder() {return RIBBON_ROUTING_FILTER_ORDER;}@Overridepublic boolean shouldFilter() {RequestContext ctx = RequestContext.getCurrentContext();// 路由配置中,url 为空,serviceId 不为空时满足条件return (ctx.getRouteHost() == null && ctx.get(SERVICE_ID_KEY) != null&& ctx.sendZuulResponse());}@Overridepublic Object run() {RequestContext context = RequestContext.getCurrentContext();this.helper.addIgnoredHeaders();try {// 封装 ribbon 上下文RibbonCommandContext commandContext = buildCommandContext(context);// 包装请求,发起向服务名称为 serviceId的请求调用,完成网关的转发功能ClientHttpResponse response = forward(commandContext);// 暂存请求结果到 context 中setResponse(response);return response;}catch (ZuulException ex) {throw new ZuulRuntimeException(ex);}catch (Exception ex) {throw new ZuulRuntimeException(ex);}}}
SimpleHostRoutingFilter

该过滤器只对请求上下文存在 routeHost 参数的请求进行处理,即只对通过 url 配置路由规则的请求生效

public class SimpleHostRoutingFilter extends ZuulFilter {@Overridepublic String filterType() {return ROUTE_TYPE;}// order 为 100,在 RibbonRoutingFilter 之后@Overridepublic int filterOrder() {return SIMPLE_HOST_ROUTING_FILTER_ORDER;}@Overridepublic boolean shouldFilter() {// RouteHost 不为空,// 也就是当路由配置中,serviceid 为空,url 不为空时(在 PreDecorationFilter 中赋值的 RouteHost)return RequestContext.getCurrentContext().getRouteHost() != null&& RequestContext.getCurrentContext().sendZuulResponse();}@Overridepublic Object run() {RequestContext context = RequestContext.getCurrentContext();HttpServletRequest request = context.getRequest();// ...String uri = this.helper.buildZuulRequestURI(request);this.helper.addIgnoredHeaders();try {// 通过 HttpClient 发起实际请求,完成转发CloseableHttpResponse response = forward(this.httpClient, verb, uri, request,headers, params, requestEntity);setResponse(response);}catch (Exception ex) {throw new ZuulRuntimeException(ex);}return null;}
}
SendForwardFilter

该过滤器只对请求上下文中存在的 forward.do 参数进行处理请求,即用来处理路由规则中的 forward 本地跳转装配

public class SendForwardFilter extends ZuulFilter {@Overridepublic String filterType() {return ROUTE_TYPE;}// order = 500,在 SimpleHostRoutingFilter 之后@Overridepublic int filterOrder() {return SEND_FORWARD_FILTER_ORDER;}@Overridepublic boolean shouldFilter() {RequestContext ctx = RequestContext.getCurrentContext();// PreDecorationFilter 中赋值的 FORWARD_TO_KEY 不为空return ctx.containsKey(FORWARD_TO_KEY)&& !ctx.getBoolean(SEND_FORWARD_FILTER_RAN, false);}@Overridepublic Object run() {try {RequestContext ctx = RequestContext.getCurrentContext();String path = (String) ctx.get(FORWARD_TO_KEY);// RequestDispatcher 服务端转发RequestDispatcher dispatcher = ctx.getRequest().getRequestDispatcher(path);if (dispatcher != null) {ctx.set(SEND_FORWARD_FILTER_RAN, true);if (!ctx.getResponse().isCommitted()) {dispatcher.forward(ctx.getRequest(), ctx.getResponse());ctx.getResponse().flushBuffer();}}}catch (Exception ex) {ReflectionUtils.rethrowRuntimeException(ex);}return null;}
}

post 过滤器

SendResponseFilter

当请求完成之后,在该过滤器中返回响应到客户端。

public class SendResponseFilter extends ZuulFilter {// ...@Overridepublic String filterType() {return POST_TYPE;}// order = 1000@Overridepublic int filterOrder() {return SEND_RESPONSE_FILTER_ORDER;}@Overridepublic boolean shouldFilter() {RequestContext context = RequestContext.getCurrentContext();// 异常上下文为空,并且转发响应体不为空则执行该过滤器return context.getThrowable() == null&& (!context.getZuulResponseHeaders().isEmpty()|| context.getResponseDataStream() != null|| context.getResponseBody() != null);}@Overridepublic Object run() {try {// 添加响应请求头addResponseHeaders();// 返回响应writeResponse();}catch (Exception ex) {ReflectionUtils.rethrowRuntimeException(ex);}return null;}// ...
}

error 过滤器

SendErrorFilter

pre Filterroute Filterpost Filter 在执行过程中,任何一个 Filter 发生异常,则都会进入该过滤器,该过滤器中,主要是转发到错误页面或者默认的 /error rest 接口中完成响应给客户端。我们可以通过配置禁掉该默认错误 Filter,通过自定义 Error Filter 来处理异常响应。

public class SendErrorFilter extends ZuulFilter {
@Overridepublic String filterType() {return ERROR_TYPE;}@Overridepublic int filterOrder() {return SEND_ERROR_FILTER_ORDER;}@Overridepublic boolean shouldFilter() {RequestContext ctx = RequestContext.getCurrentContext();// 上下文中的异常信息不为空则执行该过滤器return ctx.getThrowable() != null&& !ctx.getBoolean(SEND_ERROR_FILTER_RAN, false);}@Overridepublic Object run() {try {RequestContext ctx = RequestContext.getCurrentContext();ZuulException exception = findZuulException(ctx.getThrowable());HttpServletRequest request = ctx.getRequest();// ...RequestDispatcher dispatcher = request.getRequestDispatcher(this.errorPath);if (dispatcher != null) {ctx.set(SEND_ERROR_FILTER_RAN, true);if (!ctx.getResponse().isCommitted()) {ctx.setResponseStatusCode(exception.nStatusCode);// 转发到 error pathdispatcher.forward(request, ctx.getResponse());}}}catch (Exception ex) {ReflectionUtils.rethrowRuntimeException(ex);}return null;}
}

由于全文篇幅太长,我把 Zuul 的源码解析分成了两篇文章,感兴趣可前往 Zuul源码解析(二)