zuul路由转发源码解析

上一篇文章:zuul实现所有接口对于带指定前缀和不带前缀的url均能兼容访问
上一篇文章里我们说了SimpleRouteLocator类中根据url路径获取Route主要分为4步,上一篇文章主要说了第2步url路径的预处理,现在我们继续讲其它3步。

//SimpleRouteLocator
@Override
public Route getMatchingRoute(final String path) {

	return getSimpleMatchingRoute(path);

}
protected Route getSimpleMatchingRoute(final String path) {
	if (log.isDebugEnabled()) {
		log.debug("Finding route for path: " + path);
	}

	// This is called for the initialization done in getRoutesMap()
	//1.获取ZuulRoute的映射对
	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());
	}

	//2.对url路径预处理
	String adjustedPath = adjustPath(path);
	
	//3.根据路径获取匹配的ZuulRoute
	ZuulRoute route = getZuulRoute(adjustedPath);
	
	//4.根据ZuulRoute组装Route
	return getRoute(route, adjustedPath);
}

1…获取ZuulRoute的映射对getRoutesMap();

protected Map getRoutesMap() {
		if (this.routes.get() == null) {
			this.routes.set(locateRoutes());
		}
		return this.routes.get();
	}

从本地线程变量获取,weinull则调用locateRoutes方法。
接下来我们以DiscoveryClientRouteLocator类为例讲解locateRoutes方法。

@Override
protected LinkedHashMap locateRoutes() {
	LinkedHashMap routesMap = new LinkedHashMap<>();
	//1.1首先调用父类SimpleRouteLocator的方法
	routesMap.putAll(super.locateRoutes());
	if (this.discovery != null) {
		//1.2根据routesMap遍历组装成新的映射对staticServices,serveiceId为key值
		Map 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
		//1.3对于发现的服务,按照一定规则加入routesMap
		List 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));
			}
		}
	}
	//1.4对"/**"类型的路由规则移到最后面
	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);
	}
	//1.5配置了路由前缀,所有路由规则需要加上前缀
	LinkedHashMap values = new LinkedHashMap<>();
	for (Entry 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;
}

1.1首先调用父类SimpleRouteLocator的方法

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

不多说,把我们配置文件中的zuul.routes遍历加入routesMap,zuul.routes.path作为key值。
1.2根据routesMap遍历组装成新的映射对staticServices,serveiceId为key值。
如果serviceId为null则取id作为key值,serviceId和id都为null则跳过。
1.3对于发现的服务,按照一定规则加入routesMap
zuul通过Discovery发现服务,并排除配置的zuul.ignoredServices,然后进行遍历。
如果staticServices中对应的ZuulRoute没有配置url,使用serviceId作为location,这也说明了我们可以通过serviceId或url来指定转发的服务。
如果routesMap中还没有对应service的路由,补充加入
1.4对"/**"类型的路由规则移到最后面
routeMap的类型为LinkedHashMap是有序的,路由转发时也是按照顺序进行匹配的,因此我们有必要将该路由规则放在最后匹配,防止原来排序在其后面的路由规则无效。
1.5配置了路由前缀,所有路由规则需要加上前缀

对上面5步总结一下,locateRoutes获取到routesMap的操作逻辑是:
首先,遍历配置文件中的zuul.routes加入routesMap。
其次,发现所有的服务,如果服务已经有对应的路由规则但该规则没有配置url,采用serviceId作为location;如果服务没有对应的路由规则,按照默认的规则加入。需要注意的是,我们可以再配置文件中配置多条路由规则对应1个服务,没有配置url则采用serviceId作为location只针对每个服务的最后一条路由规则。
然后,将默认的路由规则(能够匹配所有url)移到配置顺序的最后面。
最后,给routesMap的每一条路由规则加上前缀zuul.prefix。

说明一下,DiscoveryClientRouteLocator还实现了RefreshableRouteLocator,通过doRefresh方法对routesMap进行刷新,因此routesMap获取了一次以后就一成不变的。

2.url路径的预处理,针对dispatcherServletPath和zuulServletPath进行处理,上一篇已经分析,这儿不在展开

3.根据路径获取匹配的ZuulRoute

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

3.1如果符合配置zuul.ignoredPatterns,返回null。
3.2遍历routesMap,如果匹配,则返回value值。

4.根据ZuulRoute组装Route

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());
}

组装Route分为以下情况

  1. zuul.routes.{routeId}.stripPrefix为false,zuul.prefix作为route的prefix,url路径截取route的prefix之后的字符串作为route的path。
  2. zuul.routes.{routeId}.stripPrefix为true,且zuul.routes.{routeId}.path以第一个"*“号为分隔符分成path1、path2两段,zuul.prefix+path1作为route的前缀,url路径截取route的prefix之后的字符串作为route的path。
    即如果我们配置文件中路由规则的stripPrefix属性为true,那么path属性中"*"之前的字符也会被作为前缀prefix,在路由转发的处理中被截取。比如下面的路由规则,对于一个url请求”/orders/1",转发到order服务的请求url路径为"/1",如果想要转发的结果是"/orders/1",需要将stripPrefix修改为false或者path修改为"**/orders/**"
zuul:
  host:
  routes:
    order:
      path: /orders/**
      service-id: order
      stripPrefix: true

接着我们回到PreDecorationFilter类
1.根据请求的url找到对应的路由Route 已经分析完成。
2.将路由的属性放入RequestContext,后续转发时也是从其中读取对应属性。其中的"requestURI"和"serviceId(或routeHost或forward.to)",对应转发的url路由和转发的目标,确定了其它服务里对应的接口。
从PreDecorationFilter
3.找不到路由时的fallback,从请求url的路径里截去zuulServletPath和dispatcherServletPath作为fallback的url,同样存储在RequestContext中。

@Override
public Object run() {
	//1.根据请求的url找到对应的路由Route
	RequestContext ctx = RequestContext.getCurrentContext();
	final String requestURI = this.urlPathHelper.getPathWithinApplication(ctx.getRequest());
	Route route = this.routeLocator.getMatchingRoute(requestURI);
	//2.根据Route进行相应的转发
	if (route != null) {
		String location = route.getLocation();
		if (location != null) {
			ctx.put(REQUEST_URI_KEY, route.getPath());
			ctx.put(PROXY_KEY, route.getId());
			if (!route.isCustomSensitiveHeaders()) {
				this.proxyRequestHelper
						.addIgnoredHeaders(this.properties.getSensitiveHeaders().toArray(new String[0]));
			}
			else {
				this.proxyRequestHelper.addIgnoredHeaders(route.getSensitiveHeaders().toArray(new String[0]));
			}

			if (route.getRetryable() != null) {
				ctx.put(RETRYABLE_KEY, route.getRetryable());
			}

			if (location.startsWith(HTTP_SCHEME+":") || location.startsWith(HTTPS_SCHEME+":")) {
				ctx.setRouteHost(getUrl(location));
				ctx.addOriginResponseHeader(SERVICE_HEADER, location);
			}
			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 for use in filters.route.RibbonRequest
				ctx.set(SERVICE_ID_KEY, location);
				ctx.setRouteHost(null);
				ctx.addOriginResponseHeader(SERVICE_ID_HEADER, location);
			}
			if (this.properties.isAddProxyHeaders()) {
				addProxyHeaders(ctx, route);
				String xforwardedfor = ctx.getRequest().getHeader(X_FORWARDED_FOR_HEADER);
				String remoteAddr = ctx.getRequest().getRemoteAddr();
				if (xforwardedfor == null) {
					xforwardedfor = remoteAddr;
				}
				else if (!xforwardedfor.contains(remoteAddr)) { // Prevent duplicates
					xforwardedfor += ", " + remoteAddr;
				}
				ctx.addZuulRequestHeader(X_FORWARDED_FOR_HEADER, xforwardedfor);
			}
			if (this.properties.isAddHostHeader()) {
				ctx.addZuulRequestHeader(HttpHeaders.HOST, toHostHeader(ctx.getRequest()));
			}
		}
	}
	//3.Route为null,进行相应的fallback处理
	else {
		log.warn("No route found for uri: " + requestURI);

		String fallBackUri = requestURI;
		String fallbackPrefix = this.dispatcherServletPath; // default fallback
															// servlet is
															// DispatcherServlet

		if (RequestUtils.isZuulServletRequest()) {
			// remove the Zuul servletPath from the requestUri
			log.debug("zuulServletPath=" + this.properties.getServletPath());
			fallBackUri = fallBackUri.replaceFirst(this.properties.getServletPath(), "");
			log.debug("Replaced Zuul servlet path:" + fallBackUri);
		}
		else {
			// remove the DispatcherServlet servletPath from the requestUri
			log.debug("dispatcherServletPath=" + this.dispatcherServletPath);
			fallBackUri = fallBackUri.replaceFirst(this.dispatcherServletPath, "");
			log.debug("Replaced DispatcherServlet servlet path:" + fallBackUri);
		}
		if (!fallBackUri.startsWith("/")) {
			fallBackUri = "/" + fallBackUri;
		}
		String forwardURI = fallbackPrefix + fallBackUri;
		forwardURI = forwardURI.replaceAll("//", "/");
		ctx.set(FORWARD_TO_KEY, forwardURI);
	}
	return null;
}

在将路由属性存入RequestContext后,上面我们提到url路径统一为requestURI属性,但是转发的目标分为3种属性,在filterType为route类型的zuulfilter对其实现最终的转发。
1.serviceId对应RibbonRoutingFilter类,order为10,委托RibbonCommand的execute方法进行转发。
2.routeHost对应SimpleHostRoutingFilter类,order为100,委托CloseableHttpClient的execute进行转发。
3.forward.to对应SendForwardFilter类,order为500,委托RequestDispatcher的forward方法进行转发。

你可能感兴趣的:(Zuul)