Source code interpretation of Spring Cloud microservice gateway Zuul filter chain implementation

1. The loading process of Zuul filter

The Filter of the Zuul gateway needs to be initialized and loaded into the Spring container before it can play a role in the request:

zuul-filter-config.jpg

In the last article: "What does @EnableZuulProxy or @EnableZuulServer do in Spring Cloud Microservice Gateway Zuul's annotations?" ZuulServerAutoConfiguration, there is an internal configuration class ZuulFilterConfigurationthat is the entry point for Filter initialization in Zuul:

@Configuration
protected static class ZuulFilterConfiguration {
   @Autowired
   private Map<String, ZuulFilter> filters;
   @Bean
   public ZuulFilterInitializer zuulFilterInitializer(
         CounterFactory counterFactory, TracerFactory tracerFactory) {
      FilterLoader filterLoader = FilterLoader.getInstance();
      FilterRegistry filterRegistry = FilterRegistry.instance();
      return new ZuulFilterInitializer(this.filters, counterFactory, tracerFactory, filterLoader, filterRegistry);
   }
}
复制代码

ZuulFilterInitializer

public class ZuulFilterInitializer {

   private static final Log log = LogFactory.getLog(ZuulFilterInitializer.class);

   private final Map<String, ZuulFilter> filters;
   private final CounterFactory counterFactory;
   private final TracerFactory tracerFactory;
   private final FilterLoader filterLoader;
   private final FilterRegistry filterRegistry;

   public ZuulFilterInitializer(Map<String, ZuulFilter> filters,
                         CounterFactory counterFactory,
                         TracerFactory tracerFactory,
                         FilterLoader filterLoader,
                         FilterRegistry filterRegistry) {
      this.filters = filters;
      this.counterFactory = counterFactory;
      this.tracerFactory = tracerFactory;
      this.filterLoader = filterLoader;
      this.filterRegistry = filterRegistry;
   }

   @PostConstruct
   public void contextInitialized() {
      log.info("Starting filter initializer");

      TracerFactory.initialize(tracerFactory);
      CounterFactory.initialize(counterFactory);
      // 将ZuulFilter放到FilterRegistry中
      for (Map.Entry<String, ZuulFilter> entry : this.filters.entrySet()) {
         filterRegistry.put(entry.getKey(), entry.getValue());
      }
   }

   @PreDestroy
   public void contextDestroyed() {
      log.info("Stopping filter initializer");
      for (Map.Entry<String, ZuulFilter> entry : this.filters.entrySet()) {
         filterRegistry.remove(entry.getKey());
      }
      clearLoaderCache();

      TracerFactory.initialize(null);
      CounterFactory.initialize(null);
   }

   private void clearLoaderCache() {
      Field field = ReflectionUtils.findField(FilterLoader.class, "hashFiltersByType");
      ReflectionUtils.makeAccessible(field);
      @SuppressWarnings("rawtypes")
      Map cache = (Map) ReflectionUtils.getField(field, filterLoader);
      cache.clear();
   }

}
复制代码

It can be seen from the source code that FilterLoaderboth and FilterRegistryare singleton objects, and then everything in the Spring container ZuulFilteris handed over FilterRegistryto manage, just like Spring's singleton pool. FilterRegistryUsed internally ConcurrentHashMapto hold these singletons ZuulFilter.

When the request arrives ZuulServlet, it will check ZuulRunnerwhether it is initialized. If it is not initialized, execute the init method initialization ZuulRunner; if it has been initialized, enter the service method to execute the logic of the filter chain.

ZuulRunnerIt is mainly RequestContextthe initialization of the pair, putting the request context into the statement cycle, and the obtained FilterProcessorsingleton object, which can be said to be the executor of the Zuul filter chain. Finally, FilterProcessorget FilterLoaderthe corresponding Filter and initialize it in.

2. Realization of filter chain

Zuul Filter is mainly divided into pre-filter (pre), route filter (route) and post-filter (post).

zuul-filter-time-line.jpg

同类型的过滤器组成一个过滤连,可以通过指定过滤器的执行顺序。它们的执行顺序在FilterLoaderFilterProcessor中被定义和执行。FilterProcessor会通过FilterLoader获取同一个类型的过滤器集合,然后遍历这些过滤器按照排序执行。

public class ZuulServlet extends HttpServlet {

    private static final long serialVersionUID = -3374242278843351500L;
    private ZuulRunner zuulRunner;


    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);

        String bufferReqsStr = config.getInitParameter("buffer-requests");
        boolean bufferReqs = bufferReqsStr != null && bufferReqsStr.equals("true") ? true : false;

        zuulRunner = new ZuulRunner(bufferReqs);
    }

    @Override
    public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
        try {
            // 初始化ZuulRUnner
            init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);

            // Marks this request as having passed through the "Zuul engine", as opposed to servlets
            // explicitly bound in web.xml, for which requests will not have the same data attached
            RequestContext context = RequestContext.getCurrentContext();
            context.setZuulEngineRan();

            try {
                // 前置过滤器链
                preRoute();
            } catch (ZuulException e) {
                error(e);
                postRoute();
                return;
            }
            try {
                // 路由
                route();
            } catch (ZuulException e) {
                error(e);
                postRoute();
                return;
            }
            try {
                // 后置过滤器链
                postRoute();
            } catch (ZuulException e) {
                error(e);
                return;
            }

        } catch (Throwable e) {
            error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
        } finally {
            RequestContext.getCurrentContext().unset();
        }
    }

    /**
     * executes "post" ZuulFilters
     *
     * @throws ZuulException
     */
    void postRoute() throws ZuulException {
        zuulRunner.postRoute();
    }

    /**
     * executes "route" filters
     *
     * @throws ZuulException
     */
    void route() throws ZuulException {
        zuulRunner.route();
    }

    /**
     * executes "pre" filters
     *
     * @throws ZuulException
     */
    void preRoute() throws ZuulException {
        zuulRunner.preRoute();
    }

    /**
     * initializes request
     *
     * @param servletRequest
     * @param servletResponse
     */
    void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
        zuulRunner.init(servletRequest, servletResponse);
    }

    /**
     * sets error context info and executes "error" filters
     *
     * @param e
     */
    void error(ZuulException e) {
        RequestContext.getCurrentContext().setThrowable(e);
        zuulRunner.error();
    }
    ...
}
复制代码

三、核心路由实现源码解析

在之前动态路由配置的文章中有介绍到:Spring Cloud微服务网关Zuul动态路由配置Spring Cloud微服务网关Zuul动态路由配置优化和手动触发路由刷新。那么在本章节将深入源码解析这个类的作用和功能,以及如何去扩展。

其组成的类图如下:

3.1 两个接口RouteLocator和RefreshableRouteLocator

核心的顶层接口RouteLocator:

public interface RouteLocator {
    // 获取忽略的路径
    Collection<String> getIgnoredPaths();
    // 获取所有的路由信息
    List<Route> getRoutes();
    // 根据请求路径获取路由信息
    Route getMatchingRoute(String path);
}
复制代码

**RefreshableRouteLocator:**这个也是个接口,它是继承了接口RouteLocator。如果要实现路由刷新功能必须要实现该接口。

public interface RefreshableRouteLocator extends RouteLocator {
   // 刷新内存中路由信息
   void refresh();
}
复制代码

3.2 SimpleRouteLocator源码解析

这个类是真正持有并管理路由信息的类,但是这个类只能加载配置文件中的路由信息,最终这些路由信息被封装到一个Map中:routes。同时它也实现了Ordered接口,可以对定位器优先级进行设置。Spring大量使用了策略模式,通过Ordered接口来实现优先级。

源码如下:

public class SimpleRouteLocator implements RouteLocator, Ordered {

	private static final Log log = LogFactory.getLog(SimpleRouteLocator.class);
	private static final int DEFAULT_ORDER = 0;
    // 读取配置文件中zuul的配置
	private ZuulProperties properties;
	private PathMatcher pathMatcher = new AntPathMatcher();
	private String dispatcherServletPath = "/";
	private String zuulServletPath;
    // 所有的路由配置最终都会保存到这里
	private AtomicReference<Map<String, ZuulRoute>> routes = new AtomicReference<>();
	private int order = DEFAULT_ORDER;

	public SimpleRouteLocator(String servletPath, ZuulProperties properties) {
        this.properties = properties;
		if (StringUtils.hasText(servletPath)) {
			this.dispatcherServletPath = servletPath;
		}
        // 每一个servlet path 对应一个路由
		this.zuulServletPath = properties.getServletPath();
	}

	@Override
	public List<Route> getRoutes() {
        // 获取所有的路由配置
		List<Route> values = new ArrayList<>();
        // 遍历路由
		for (Entry<String, ZuulRoute> entry : getRoutesMap().entrySet()) {
			ZuulRoute route = entry.getValue();
			String path = route.getPath();
			values.add(getRoute(route, path));
		}
		return values;
	}

	@Override
	public Collection<String> getIgnoredPaths() {
        // 获取配置文件中的忽略路径
		return this.properties.getIgnoredPatterns();
	}

	@Override
	public Route getMatchingRoute(final String path) {
        // 根据请求路径获取对应的路由配置
		return getSimpleMatchingRoute(path);
	}

	protected Map<String, ZuulRoute> getRoutesMap() {
        // 如果路由信息为空,会把路由配置信息加载到内存
		if (this.routes.get() == null) {
            // 这里的locateRoutes方法这个类的方法,读取的是配置文件中的路由配置
			this.routes.set(locateRoutes());
		}
		return this.routes.get();
	}

	protected Route getSimpleMatchingRoute(final String path) {
        // 根据请求路径来获取路由配置信息
		if (log.isDebugEnabled()) {
			log.debug("Finding route for path: " + path);
		}
		// 调用以下,确保保存路由的map路由配置加载完成
		getRoutesMap();
		if (log.isDebugEnabled()) {
			log.debug("servletPath=" + this.dispatcherServletPath);
			log.debug("zuulServletPath=" + this.zuulServletPath);
			log.debug("RequestUtils.isDispatcherServletRequest()="
					+ RequestUtils.isDispatcherServletRequest());
			log.debug("RequestUtils.isZuulServletRequest()="
					+ RequestUtils.isZuulServletRequest());
		}
        // 调整请求路径
		String adjustedPath = adjustPath(path);
        // 根据路径获取路由表
		ZuulRoute route = getZuulRoute(adjustedPath);
        // 把ZuulRoute封装成Route
		return getRoute(route, adjustedPath);
	}

	protected ZuulRoute getZuulRoute(String adjustedPath) {
		if (!matchesIgnoredPatterns(adjustedPath)) {
			for (Entry<String, ZuulRoute> entry : getRoutesMap().entrySet()) {
				String pattern = entry.getKey();
				log.debug("Matching pattern:" + pattern);
				if (this.pathMatcher.match(pattern, adjustedPath)) {
					return entry.getValue();
				}
			}
		}
		return null;
	}

	protected Route getRoute(ZuulRoute route, String path) {
		if (route == null) {
			return null;
		}
		if (log.isDebugEnabled()) {
			log.debug("route matched=" + route);
		}
		String targetPath = path;
		String prefix = this.properties.getPrefix();
		if(prefix.endsWith("/")) {
			prefix = prefix.substring(0, prefix.length() - 1);
		}
		if (path.startsWith(prefix + "/") && this.properties.isStripPrefix()) {
			targetPath = path.substring(prefix.length());
		}
		if (route.isStripPrefix()) {
			int index = route.getPath().indexOf("*") - 1;
			if (index > 0) {
				String routePrefix = route.getPath().substring(0, index);
				targetPath = targetPath.replaceFirst(routePrefix, "");
				prefix = prefix + routePrefix;
			}
		}
		Boolean retryable = this.properties.getRetryable();
		if (route.getRetryable() != null) {
			retryable = route.getRetryable();
		}
		return new Route(route.getId(), targetPath, route.getLocation(), prefix,
				retryable,
				route.isCustomSensitiveHeaders() ? route.getSensitiveHeaders() : null, 
				route.isStripPrefix());
	}

	protected void doRefresh() {
        // 刷新路由信息,这里刷新操作实质是重新加载配置文件中的路由配置
		this.routes.set(locateRoutes());
	}

	protected Map<String, ZuulRoute> locateRoutes() {
		LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<>();
		for (ZuulRoute route : this.properties.getRoutes().values()) {
			routesMap.put(route.getPath(), route);
		}
		return routesMap;
	}

	protected boolean matchesIgnoredPatterns(String path) {
		for (String pattern : this.properties.getIgnoredPatterns()) {
			log.debug("Matching ignored pattern:" + pattern);
			if (this.pathMatcher.match(pattern, path)) {
				log.debug("Path " + path + " matches ignored pattern " + pattern);
				return true;
			}
		}
		return false;
	}

	private String adjustPath(final String path) {
		String adjustedPath = path;

		if (RequestUtils.isDispatcherServletRequest()
				&& StringUtils.hasText(this.dispatcherServletPath)) {
			if (!this.dispatcherServletPath.equals("/")) {
				adjustedPath = path.substring(this.dispatcherServletPath.length());
				log.debug("Stripped dispatcherServletPath");
			}
		}
		else if (RequestUtils.isZuulServletRequest()) {
			if (StringUtils.hasText(this.zuulServletPath)
					&& !this.zuulServletPath.equals("/")) {
				adjustedPath = path.substring(this.zuulServletPath.length());
				log.debug("Stripped zuulServletPath");
			}
		}
		else {
			// do nothing
		}

		log.debug("adjustedPath=" + adjustedPath);
		return adjustedPath;
	}

	@Override
	public int getOrder() {
		return order;
	}
	
	public void setOrder(int order) {
		this.order = order;
	}
}
复制代码

3.3 DiscoveryClientRouteLocator

这个类是继承了SimpleRouteLocator,并实现了RefreshableRouteLocator。重写了的locateRoutes()是核心,这个路由定位器的功能就是可以读取注册中心中的服务然后加载其路由配置信息,从源码里面可以看到依赖了DiscoveryClient

public class DiscoveryClientRouteLocator extends SimpleRouteLocator
		implements RefreshableRouteLocator {

	private static final Log log = LogFactory.getLog(DiscoveryClientRouteLocator.class);
	public static final String DEFAULT_ROUTE = "/**";
	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();
	}

	public void addRoute(ZuulRoute route) {
		this.properties.getRoutes().put(route.getPath(), route);
		refresh();
	}

	@Override
	protected LinkedHashMap<String, ZuulRoute> locateRoutes() {
		LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<>();
		routesMap.putAll(super.locateRoutes());
		if (this.discovery != null) {
			Map<String, ZuulRoute> staticServices = new LinkedHashMap<>();
			for (ZuulRoute route : routesMap.values()) {
				String serviceId = route.getServiceId();
				if (serviceId == null) {
					serviceId = route.getId();
				}
				if (serviceId != null) {
					staticServices.put(serviceId, route);
				}
			}
			// Add routes for discovery services by default
			List<String> services = this.discovery.getServices();
			String[] ignored = this.properties.getIgnoredServices()
					.toArray(new String[0]);
			for (String serviceId : services) {
				// Ignore specifically ignored services and those that were manually
				// configured
				String key = "/" + mapRouteToService(serviceId) + "/**";
				if (staticServices.containsKey(serviceId)
						&& staticServices.get(serviceId).getUrl() == null) {
					// Explicitly configured with no URL, cannot be ignored
					// all static routes are already in routesMap
					// Update location using serviceId if location is null
					ZuulRoute staticRoute = staticServices.get(serviceId);
					if (!StringUtils.hasText(staticRoute.getLocation())) {
						staticRoute.setLocation(serviceId);
					}
				}
				if (!PatternMatchUtils.simpleMatch(ignored, serviceId)
						&& !routesMap.containsKey(key)) {
					// Not ignored
					routesMap.put(key, new ZuulRoute(key, serviceId));
				}
			}
		}
		if (routesMap.get(DEFAULT_ROUTE) != null) {
			ZuulRoute defaultRoute = routesMap.get(DEFAULT_ROUTE);
			// Move the defaultServiceId to the end
			routesMap.remove(DEFAULT_ROUTE);
			routesMap.put(DEFAULT_ROUTE, defaultRoute);
		}
		LinkedHashMap<String, ZuulRoute> values = new LinkedHashMap<>();
		for (Entry<String, ZuulRoute> entry : routesMap.entrySet()) {
			String path = entry.getKey();
			// Prepend with slash if not already present.
			if (!path.startsWith("/")) {
				path = "/" + path;
			}
			if (StringUtils.hasText(this.properties.getPrefix())) {
				path = this.properties.getPrefix() + path;
				if (!path.startsWith("/")) {
					path = "/" + path;
				}
			}
			values.put(path, entry.getValue());
		}
		return values;
	}

	@Override
	public void refresh() {
		doRefresh();
	}

	protected String mapRouteToService(String serviceId) {
		return this.serviceRouteMapper.apply(serviceId);
	}

	protected void addConfiguredRoutes(Map<String, ZuulRoute> routes) {
		Map<String, ZuulRoute> routeEntries = this.properties.getRoutes();
		for (ZuulRoute entry : routeEntries.values()) {
			String route = entry.getPath();
			if (routes.containsKey(route)) {
				log.warn("Overwriting route " + route + ": already defined by "
						+ routes.get(route));
			}
			routes.put(route, entry);
		}
	}
}
复制代码

3.4 CompositeRouteLocator

This is to combine all RouteLocatorclasses, and when injected into the Spring container, annotations CompositeRouteLocatorare used , that is to say. The default is @Primarywhen you want to fetch in the Spring container . There is no substantial modification to this class, it will be introduced into all Spring containers . Then execute them one by one .RouteLocatorCompositeRouteLocatorRouteLocatorRouteLocator

public class CompositeRouteLocator implements RefreshableRouteLocator {
	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;
	}

	@Override
	public Collection<String> getIgnoredPaths() {
		List<String> ignoredPaths = new ArrayList<>();
		for (RouteLocator locator : routeLocators) {
			ignoredPaths.addAll(locator.getIgnoredPaths());
		}
		return ignoredPaths;
	}

	@Override
	public List<Route> getRoutes() {
		List<Route> route = new ArrayList<>();
		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();
			}
		}
	}
}
复制代码

3.5 Custom route locator

Guess you like

Origin juejin.im/post/7222268370269159480