Zuul 生命周期 应用说明

 

1. 概述

  •  Zuul的请求引入还是来自于DispatcherServlet,然后交由ZuulHandlerMapping(由AbstractUrlHandlerMapping继承得来,它是SpringMVC对request进行分发的一个重要的抽象类)处理由初始化得来的路由定位器RouteLocator,为后续的请求分发做好准备,同时整合了基于事件从服务中心拉取服务列表的机制;
  • 进入ZuulController,它的主要职责是初始化ZuulServlet以及继承ServletWrapping-Controller,通过重写handleRequest方法来将ZuulServlet引入生命周期,之后所有的请求都会经过ZuulServlet;
  •  当请求进入ZuulServlet后,第一次调用会初始化ZuulRunner,非第一次调用就按Filter链order顺序执行;
  •  ZuulRunner中将请求和响应初始化为RequestContext,包装成FilterProcessor转化为调用preRoute()、route()、postRoute()和error()方法;
  • 最后便是我们的各个Filter执行逻辑了,原始请求在Filter中经过种种变换,最后得到预期的结果。

 

 

 

 

2. 详细

2.1.  初始化阶段

初始化(或是路由刷新后的第一次访问的监听事件阶段)阶段,通过SimpleRouteLocator的getRoute方法获取路由的路径信息,将路径信息作为key放到handlerMap中,spring mvc的servlet在分发阶段会在zuul预先注册好的mapping中查找路径对应的key,如果匹配则由zuul的servlet处理。(后续阶段还会调用SimpleRouteLocator的getRoute方法,是为了得到真正的转发地址)

Zuulhandlermapping类中,初始化的时候将路由定位器中获取的路由集合注册到map中,然后在Servlet分发阶段获取当前注册的hanlerMapping,并在HandlerMapping里持有的map中查找是否满足当前request地址(通过macher判断,而不是简单的判等,zuul中是用的AntPathMacher)的路由信息。如下。

 

private void registerHandlers() {
    Collection<Route> routes = this.routeLocator.getRoutes();
    if (routes.isEmpty()) {
        this.logger.warn("No routes found from RouteLocator");
    } else {
        Iterator var2 = routes.iterator();

        while(var2.hasNext()) {
            Route route = (Route)var2.next();
            this.registerHandler(route.getFullPath(), this.zuul);
        }
    }

}

如果路由更改,调用了路由刷新事件,在下一次任意controller被调用的时候,会执行红色代码块部分,重新将路由的url信息注册到servlet的map中。

protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
    if (this.errorController != null && urlPath.equals(this.errorController.getErrorPath())) {
        return null;
    } else if (this.isIgnoredPath(urlPath, this.routeLocator.getIgnoredPaths())) {
        return null;
    } else {
        RequestContext ctx = RequestContext.getCurrentContext();
        if (ctx.containsKey("forward.to")) {
            return null;
        } else {
            if (this.dirty) {
                synchronized(this) {
                    if (this.dirty) {
                        this.registerHandlers();
                        this.dirty = false;
                    }
                }
            }

            return super.lookupHandler(urlPath, request);
        }
    }
}

Super.lookupHandler调用调用抽象父类中AbstractUrlHandlerMapping中的lookupHandler方法,查找当前请求地址对应的handler。

@Nullable
protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
    Object handler = this.handlerMap.get(urlPath);
    if (handler != null) {
        if (handler instanceof String) {
            String handlerName = (String)handler;
            handler = this.obtainApplicationContext().getBean(handlerName);
        }

        this.validateHandler(handler, request);
        return this.buildPathExposingHandler(handler, urlPath, urlPath, (Map)null);
    } else {
        List<String> matchingPatterns = new ArrayList();
        Iterator var5 = this.handlerMap.keySet().iterator();

        while(var5.hasNext()) {
            String registeredPattern = (String)var5.next();
            if (this.getPathMatcher().match(registeredPattern, urlPath)) {
                matchingPatterns.add(registeredPattern);
            } else if (this.useTrailingSlashMatch() && !registeredPattern.endsWith("/") && this.getPathMatcher().match(registeredPattern + "/", urlPath)) {
                matchingPatterns.add(registeredPattern + "/");
            }
        }

        String bestMatch = null;
        Comparator<String> patternComparator = this.getPathMatcher().getPatternComparator(urlPath);
        if (!matchingPatterns.isEmpty()) {
            matchingPatterns.sort(patternComparator);
            if (this.logger.isTraceEnabled() && matchingPatterns.size() > 1) {
                this.logger.trace("Matching patterns " + matchingPatterns);
            }

            bestMatch = (String)matchingPatterns.get(0);
        }

        if (bestMatch != null) {
            handler = this.handlerMap.get(bestMatch);
            if (handler == null) {
                if (bestMatch.endsWith("/")) {
                    handler = this.handlerMap.get(bestMatch.substring(0, bestMatch.length() - 1));
                }

                if (handler == null) {
                    throw new IllegalStateException("Could not find handler for best pattern match [" + bestMatch + "]");
                }
            }

            String pathWithinMapping;
            if (handler instanceof String) {
                pathWithinMapping = (String)handler;
                handler = this.obtainApplicationContext().getBean(pathWithinMapping);
            }

            this.validateHandler(handler, request);
            pathWithinMapping = this.getPathMatcher().extractPathWithinPattern(bestMatch, urlPath);
            Map<String, String> uriTemplateVariables = new LinkedHashMap();
            Iterator var9 = matchingPatterns.iterator();

            while(var9.hasNext()) {
                String matchingPattern = (String)var9.next();
                if (patternComparator.compare(bestMatch, matchingPattern) == 0) {
                    Map<String, String> vars = this.getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath);
                    Map<String, String> decodedVars = this.getUrlPathHelper().decodePathVariables(request, vars);
                    uriTemplateVariables.putAll(decodedVars);
                }
            }

            if (this.logger.isTraceEnabled() && uriTemplateVariables.size() > 0) {
                this.logger.trace("URI variables " + uriTemplateVariables);
            }

            return this.buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables);
        } else {
            return null;
        }
    }
}

继承关系如下图:

 

 

 

 

 备注:

Pathmach使用的是AntPath

 

 

 

2.2.  Spring Mvc 请求分发

根据request请求的地址,匹配到zuulHandlerMapping中有已经注册的地址,所以交给zuulhandlermapping处理。

 

 

 

 

 

 

2.3.  Zuul网关Pre阶段

Zuul流程简化图如下:

 

pre阶段步骤2,先是在初始化阶段中生成的map中查找是否有匹配当前request请求地址的key,如果有,则取出对应key的zuulRoute对象,再

调用SimpleRouteLocator类中的getRoute方法

再次(之前初始化的时候也调用过getRoute方法,不过得到的对象放到了Path的handlerMapping中了,用于spring mvc的路径匹配)获取路由

Route route = this.routeLocator.getMatchingRoute(requestURI);

 

protected Route getRoute(ZuulRoute route, String path) {
    if (route == null) {
        return null;
    } else {
        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());
    }
}

pre阶段步骤3,为路由阶段的最后的转发地址做准备。

最后的转发地址=路由中的location+经过计算的path。路由中的location就是路由表中的location,直接读取即可。经过计算的path值则需要根据是否替换前缀以及zuul的prefix来确定,例如请求地址为/query/user/getUserInfo?userId=1,路由表中的原始配置为path=/query/**,location为http://127.0.0.1:8080/authserver。如果替换前缀则根据getRoute方法获取的路由对象的path为“/user/getUserInfo”(这和初始化时候的path不同,初始化时为/**,因为入参不同,初始化时候的入参为/query/**,而此时的入参为/query/user/getUserInfo,初始化时是为了servlet分发过程匹配/query/**后得到handler,进而才能进入到zuul的controller中),location不变,则最终的转发地址为http://127.0.0.1:8080/authserver/user/getUserInfo

如果不替换前缀,此时获取的path为/query/user/getUserInfo,那么最终的转发地址则为http://127.0.0.1:8080/authserver/query/user/getUserInfo

ctx.put("requestURI", route.getPath());
ctx.setRouteHost(this.getUrl(location));

2.4.  Zuul网关Route阶段

根据上下文中的requestURI和RouteHost,构建新的http请求发送到目的地

String contextURI = (String)context.get("requestURI");
URL host = RequestContext.getCurrentContext().getRouteHost();
public URL getRouteHost() {
    return (URL)this.get("routeHost");
}

3. 扩展

在pre阶段给path赋值,创建要转发的route信息的时候,当路由配置为替换前缀的情况下, 源代码中是按照“*”号的位置来进行逻辑处理,但是在某些情况下不能采用通配符的方式配置路由转发规则,所以扩展源代码,继承SimpleRouteLocator

//如果替换path前缀,会根据字符串中的*号来判断从哪替换,但是如果path前缀没有*号,那么就会出现错误。因此,修改这段代码如下:
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;
    }
    else{
        
        //源码中没有这个分支逻辑。这部分代码的目的是处理zuul中path没有ant匹配表达式的情形,
        //例如如下配置:
        /*
        zuul:
          routes:
            authserver:
              path: /user/getInfo
              url: http://localhost:8098/auth/api/getInfo
              stripPrefix: true
        */
        /* 星号的替换规则是将星号之前的替换成空,变成“/**”的格式。
        而这里是前端请求的地址需要被完全替换掉,转换成路由对象始终的location地址
         */
        targetPath = "";
        prefix = prefix + route.getPath();
    }

}

 

posted @ 2021-06-09 16:20  八方鱼  阅读(278)  评论(0)    收藏  举报