Zuul源码解析(一)

说在前面

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

Zuul源码解析(一)_第1张图片

然后我上级让我优化下这类日志打印,把无法解析的参数,把原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;

    @Override
    public void service(ServletRequest servletRequest,ServletResponse servletResponse){
        try {
            // 初始化 RequestContext,该 RequestContext 是贯穿 Zuul 处理请求的生命周期的
          	// 主要是暂存 request,response,异常信息等处理请求过程中中间结果
            init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);;

            try {
                // 处理全部 pre 类型的 Filter
                preRoute();
            } catch (ZuulException e) {
              	// 抛异常时,在在 error() 方法中存储该异常到 RequestContext 上下文中,后续会用到的
                // RequestContext.getCurrentContext().setThrowable(e);
                // 接着再处理所有 error 类型 Filter
                error(e);
                // 接着在执行所有 post 类型的 Filter
                postRoute();
              	// 就这样,一个请求在报错下经过 Zuul 的生命周期就结束了
                return;
            }
            try {
              	// preRoute() 没有异常,则执行 route 类型的 Filter
                route();
            } catch (ZuulException e) {
              	// 同上备注
                error(e);
                postRoute();
                return;
            }
            try {
              	// route() 没有异常,则执行 所有 post 类型 Filter
                postRoute();
            } 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" filters
    public 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 不为空,返回 url
				return this.url;
			}
          	// 否则,返回 serviceId
			return 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 模糊匹配 requestUri
		for (Entry<String, ZuulRoute> entry : getRoutesMap().entrySet()) {
			String pattern = entry.getKey();
          	// 重点在这里,使用 pathMatcher 来匹配一个 ZuulRoute
			if (this.pathMatcher.match(pattern, adjustedPath)) {
              	// 匹配则返回一个 ZuulRoute
				return 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 SimpleRouteLocator
      implements RefreshableRouteLocator {
   // 服务发现 client
   private 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 中的路由和服务发现拉取的路由
   @Override
   protected 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())) {
               	  // 赋值 location
                  staticRoute.setLocation(serviceId);
               }
            }
            // 如果静态服务配置没有配置
            if (!PatternMatchUtils.simpleMatch(ignored, serviceId)
                  && !routesMap.containsKey(key)) {
               // 那么它添加到 routesMap 中去。
               routesMap.put(key, new ZuulRoute(key, serviceId));
            }
         }
      }
      // ...
      // 该方法返回值 map
      LinkedHashMap<String, ZuulRoute> values = new LinkedHashMap<>();
      // 遍历 routesMap
      for (Entry<String, ZuulRoute> entry : routesMap.entrySet()) {
         String path = entry.getKey();
         // ...
         values.put(path, entry.getValue());
      }
      return values;
   }

   // 这个方法一般是监听器 Listener 监听到注册中心更新事件时出发本地路由的更新
   @Override
   public void refresh() {
      doRefresh();
   }

}

CompositeRouteLocator

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

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

public class CompositeRouteLocator implements RefreshableRouteLocator {
  	// 默认情况下,zuul 的自动配置中会注册默认的 DiscoveryClientRouteLocator 和 SimpleRouteLocator 实现 bean
	private 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)
	@Override
	public List<Route> getRoutes() {
      	
		List<Route> route = new ArrayList<>();
      	// 编译每个 RouteLocator,然后调用每个 RouteLocator 的 getRoutes()方法聚合全部 Route
		for (RouteLocator locator : routeLocators) {
          	
			route.addAll(locator.getRoutes());
		}
		return route;
	}

	@Override
	public Route getMatchingRoute(String path) {
		for (RouteLocator locator : routeLocators) {
			Route route = locator.getMatchingRoute(path);
			if (route != null) {
				return route;
			}
		}
		return null;
	}

	@Override
	public 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 监听到注册中心更新事件时会触发本地路由的更新

Zuul源码解析(一)_第2张图片

Zuul源码解析(一)_第3张图片

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

Zuul 默认的 filter

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

Pre 过滤器

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

ServletDetectionFilter(了解即可)
public class ServletDetectionFilter extends ZuulFilter {
    // ... 
    @Override
	public int filterOrder() {
        // 优先级为-3,数字越小,越先执行
		return SERVLET_DETECTION_FILTER_ORDER;
    }
    
	@Override
	public boolean shouldFilter() {
		return true; 
	}

	@Override
	public 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 之后执行

	@Override
	public boolean shouldFilter() {
		return true;


	@Override
	public 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()) {
			// 包装原始 request
			ctx.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 {
    @Override
	public 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
	}
    
    @Override
	public Object run() {
		RequestContext ctx = RequestContext.getCurrentContext();
		final String requestURI = this.urlPathHelper.getPathWithinApplication(ctx.getRequest());
		// 请求 uri 匹配获取 Route
        Route 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,支持服务发现,负载均衡等
                // RibbonRoutingFilter
				ctx.set(SERVICE_ID_KEY, location);
				ctx.setRouteHost(null);
				ctx.addOriginResponseHeader(SERVICE_ID_HEADER, location);
			}
        }
    }
}

Route 过滤器

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

RibbonRoutingFilter

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

public class RibbonRoutingFilter extends ZuulFilter {

	@Override
	public String filterType() {
		return ROUTE_TYPE;
	}

    // order 值为 5,是 Route 类型执行的第一个 Filter
	@Override
	public int filterOrder() {
		return RIBBON_ROUTING_FILTER_ORDER;
	}

	@Override
	public boolean shouldFilter() {
		RequestContext ctx = RequestContext.getCurrentContext();
        // 路由配置中,url 为空,serviceId 不为空时满足条件
		return (ctx.getRouteHost() == null && ctx.get(SERVICE_ID_KEY) != null
				&& ctx.sendZuulResponse());
	}

	@Override
	public 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 {
    @Override
	public String filterType() {
		return ROUTE_TYPE;
	}

    // order 为 100,在 RibbonRoutingFilter 之后
	@Override
	public int filterOrder() {
		return SIMPLE_HOST_ROUTING_FILTER_ORDER;
	}

	@Override
	public boolean shouldFilter() {
        // RouteHost 不为空,
      	// 也就是当路由配置中,serviceid 为空,url 不为空时(在 PreDecorationFilter 中赋值的 RouteHost)
		return RequestContext.getCurrentContext().getRouteHost() != null
				&& RequestContext.getCurrentContext().sendZuulResponse();
	}

	@Override
	public 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 {
    @Override
	public String filterType() {
		return ROUTE_TYPE;
	}

    // order = 500,在 SimpleHostRoutingFilter 之后
    @Override
    public int filterOrder() {
    	return SEND_FORWARD_FILTER_ORDER;
    }
    
    @Override
    public boolean shouldFilter() {
    	RequestContext ctx = RequestContext.getCurrentContext();
        // PreDecorationFilter 中赋值的 FORWARD_TO_KEY 不为空
    	return ctx.containsKey(FORWARD_TO_KEY)
    			&& !ctx.getBoolean(SEND_FORWARD_FILTER_RAN, false);
    }
    
    @Override
    public 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 {
    // ...
    @Override
	public String filterType() {
		return POST_TYPE;
	}

    // order = 1000
	@Override
	public int filterOrder() {
		return SEND_RESPONSE_FILTER_ORDER;
	}

	@Override
	public boolean shouldFilter() {
		RequestContext context = RequestContext.getCurrentContext();
      	// 异常上下文为空,并且转发响应体不为空则执行该过滤器
		return context.getThrowable() == null
				&& (!context.getZuulResponseHeaders().isEmpty()
					|| context.getResponseDataStream() != null
					|| context.getResponseBody() != null);
	}

	@Override
	public 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 {
@Override
	public String filterType() {
		return ERROR_TYPE;
	}

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

	@Override
	public boolean shouldFilter() {
		RequestContext ctx = RequestContext.getCurrentContext();
		// 上下文中的异常信息不为空则执行该过滤器
		return ctx.getThrowable() != null
				&& !ctx.getBoolean(SEND_ERROR_FILTER_RAN, false);
	}

	@Override
	public 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 path
					dispatcher.forward(request, ctx.getResponse());
				}
			}
		}
		catch (Exception ex) {
			ReflectionUtils.rethrowRuntimeException(ex);
		}
		return null;
	}
}

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

你可能感兴趣的:(源码系列,Spring,Cloud,微服务,spring,cloud)