上一篇文章: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分为以下情况
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方法进行转发。