springcloud -zuul(2-執行流程及源碼)


官方圖

1.Servlet

zuul.servletPath默認配置為/zuul,故請求為/zuul開頭的會跳過dispatcherServlet直接進入ZuulServlet,該配置可以自定義配置,例如用於大文件上傳

2.ZuulServlet中service方法

public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
            try {
                this.init((HttpServletRequest)servletRequest, (HttpServletResponse)servletResponse);
                RequestContext context = RequestContext.getCurrentContext();
                context.setZuulEngineRan();

                try {
                    //運行pre過濾器
                    this.preRoute();
                } catch (ZuulException var12) {
                    //有異常,執行errorFilter
                    this.error(var12);
                    //再執行postFilter
                    this.postRoute();
                    return;
                }

                try {
                    //運行rote過濾器
                    this.route();
                } catch (ZuulException var13) {
                    //有異常,執行errorFilter
                    this.error(var13);
                    //再執行postFilter
                    this.postRoute();
                    return;
                }

                try {
                    //運行post過濾器
                    this.postRoute();
                } catch (ZuulException var11) {
                    //有異常,執行errorFilter
                    this.error(var11);
                }
            } catch (Throwable var14) {
                this.error(new ZuulException(var14, 500, "UNHANDLED_EXCEPTION_" + var14.getClass().getName()));
            } finally {
                RequestContext.getCurrentContext().unset();
            }
        }

3.FilterProcessor

其運行交由FilterProcessor中的方法runFilters,根據service中的順序,取不同的filter類型,執行其中的run方法

public Object runFilters(String sType) throws Throwable {
            if (RequestContext.getCurrentContext().debugRouting()) {
                Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
            }

            boolean bResult = false;
            List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
            if (list != null) {
                for(int i = 0; i < list.size(); ++i) {
                    ZuulFilter zuulFilter = (ZuulFilter)list.get(i);
                    Object result = this.processZuulFilter(zuulFilter);//見下面zuulFilter的runFilter()
                    if (result != null && result instanceof Boolean) {
                        bResult |= ((Boolean)result).booleanValue();
                    }
                }
            }

            return bResult;
        }

zuulFilter的runFilter方法,當filter的shouldFilter()返回true時才執行run()方法

public ZuulFilterResult runFilter() {
            ZuulFilterResult zr = new ZuulFilterResult();
            if (!this.isFilterDisabled()) {
                if (this.shouldFilter()) {
                    Tracer t = TracerFactory.instance().startMicroTracer("ZUUL::" + this.getClass().getSimpleName());

                    try {
                        Object res = this.run();
                        zr = new ZuulFilterResult(res, ExecutionStatus.SUCCESS);
                    } catch (Throwable var7) {
                        t.setName("ZUUL::" + this.getClass().getSimpleName() + " failed");
                        zr = new ZuulFilterResult(ExecutionStatus.FAILED);
                        zr.setException(var7);
                    } finally {
                        t.stopAndLog();
                    }
                } else {
                    zr = new ZuulFilterResult(ExecutionStatus.SKIPPED);
                }
            }

            return zr;
        }

4.獲取過濾器FilterRegistry

其中的屬性private final ConcurrentHashMap<String, ZuulFilter> filters = new ConcurrentHashMap();
保存所有的過濾器
例子中有12個(其中有兩個為自定義的):

[org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter@3dc68586, 
        org.springframework.cloud.netflix.zuul.filters.pre.Servlet30WrapperFilter@4001d8c1, 
        org.springframework.cloud.netflix.zuul.filters.pre.ServletDetectionFilter@60dc1a4e, 
        org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter@7a2fce12, 
        com.example.springbootzuul.filters.AuthFilter@14fc9bd, 
        org.springframework.cloud.netflix.zuul.filters.pre.DebugFilter@74960e9d, 
        org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter@61037caf, 
        org.springframework.cloud.netflix.zuul.filters.pre.FormBodyWrapperFilter@3c88191b, 
        org.springframework.cloud.netflix.zuul.filters.post.SendErrorFilter@670342a2, 
        com.example.springbootzuul.filters.LoginPostFilter@7ed49a7f, 
        org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter@357bc488, 
        org.springframework.cloud.netflix.zuul.filters.route.SendForwardFilter@d5e575c]

5.各個filter作用

pre過濾器:

org.springframework.cloud.netflix.zuul.filters.pre.ServletDetectionFilter:
該過濾器order值為-3,是pre階段第一個過濾器,並且總是會運行。主要用途是判斷該請求是被spring的DispatcherServlet處理還是被zuul的ZuulServlet處理,並且將判斷結果設置到context中,后續處理中可以依照此結果進行個性化處理
org.springframework.cloud.netflix.zuul.filters.pre.Servlet30WrapperFilter:
該過濾器order值為-2,是pre階段第二個過濾器,並且總是會運行。Zuul默認僅對servlet2.5兼容,該過濾器可以將request包裝成3.0兼容的形式
org.springframework.cloud.netflix.zuul.filters.pre.FormBodyWrapperFilter:
該過濾器order值為-1,是pre階段第三個過濾器,僅針對兩類請求生效,第一種是Context-Type為application/x-www-form-urlencoded,第二種是由spring的DispatcherServlet處理的Context-Type為multipart/form-data的請求。該過濾器的主要目的是將上述兩種請求包裝成FormBodyRequestWrapper
org.springframework.cloud.netflix.zuul.filters.pre.DebugFilter:
該過濾器order值為1,是pre階段第四個過濾器,僅在請求參數中出現debug=true(參數名稱可設置)時執行。具體執行邏輯就是在context中設置debugRouting=true及debugRequest=true。在后續執行中可以通過這兩個值來預埋一些debug信息,用於出現問題時提供排查所需的信息
org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter:
該過濾器order值為5,是pre階段最后一個過濾器,僅在forward.to和serviceId都沒有出現在context中的時候才執行。具體來說就是對請求做一些預處理,包括使用RouteLocator獲取路由信息,在context中設置一些后續處理需要的信息,還有就是在請求頭中添加一些代理信息,比如X-Forwarded-For
com.example.springbootzuul.filters.AuthFilter 自定義的。。

route:

org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter:
該過濾器order值為10,是route階段第一個過濾器,僅在context中存在serviceId的情況下運行。存在serviceId,就是說需要面向服務進行路由,服務的路由信息就是我們上面講過的兩種方式,配置文件(靜態)及服務注冊。具體就是創建RibbonCommandContext,然后交由ribbon和hystrix向下游服務進行請求
org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter:
該過濾器order值為100,是route階段第二個過濾器,僅在context中存在routeHost的情況下運行。存在routeHost,就是說我們配置了具體的http或者https url的請求信息。具體邏輯就是通過HttpClient直接向目標url發起請求,不再經過ribbon及hystrix,所以也就沒有負載均衡以及熔斷
org.springframework.cloud.netflix.zuul.filters.route.SendForwardFilter:
該過濾器order值為500,是route階段第三個(最后一個)過濾器,僅在context中存在forward.to的情況下運行。存在forward.to,就是說我們配置了類似forward:/index的請求信息。具體就是通過RequestDispatcher進行forward

post:

com.example.springbootzuul.filters.LoginPostFilter 自定義的。。
org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter:
該過濾器order值為1000,是post階段最后一個過濾器,僅在context中存在zuulResponseHeaders、responseDataStream、responseBody(三者是或的關系)的情況下運行,簡單來說,就是在有響應數據的時候運行。我們以responseBody舉例,來看下responseBody是什么時候被設置到context中的。還記得RibbonRoutingFilter吧,在他的run方法中會調用一個setResponse方法,responseBody就是在這個方法中被設置到context中

error:

org.springframework.cloud.netflix.zuul.filters.post.SendErrorFilter@670342a2,
該過濾器order值為0,是error階段唯一一個過濾器,僅在context中存在throwable的情況下運行,也就是說有異常產生的情況下運行。將錯誤狀態碼、錯誤信息、異常對象設置到request中,然后forward到/error(默認,可配置)。之后我們可以自己定義一個/error端口對錯誤進行響應

6.關於路由處理

6.1 Zuul在自動配置加載時注入了2個RouteLocator

CompositeRouteLocator:是 @Primary的,它是組合多個RouteLocator的Locator
DiscoveryClientRouteLocator:存放至CompositeRouteLocator的屬性routeLocators中,當調用RouteLocator時會調用CompositeRouteLocator中的
DiscoveryClientRouteLocator中的locateRoutes方法運行后就已經加載了配置文件中所有路由信息,以及注冊中心中的服務路由信息,有的通過URL路由,有的通過serviceId路由

protected LinkedHashMap<String, ZuulRoute> locateRoutes() {
                //保存ZuulRoute的LinkedHashMap
                LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<String, ZuulRoute>();
                //調用父類SimpleRouteLocator#locateRoutes()
                //加載ZuulProperties中的所有配置文件中的路由信息
                routesMap.putAll(super.locateRoutes());
                //如果服務發現客戶端discovery存在
                if (this.discovery != null) {
                    //將routesMap已經存在的配置文件中的ZuulRoute放入staticServices<serviceId, ZuulRoute>
                    Map<String, ZuulRoute> staticServices = new LinkedHashMap<String, ZuulRoute>();
                    for (ZuulRoute route : routesMap.values()) {
                        String serviceId = route.getServiceId();
                        
                        //如果serviceId為null,以id作為serviceId,此情況適合 zuul.routes.xxxx=/xxxx/** 的情況
                        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(); //到注冊中心找到所有service
                    String[] ignored = this.properties.getIgnoredServices()
                            .toArray(new String[0]);
                    //遍歷services
                    for (String serviceId : services) {
                        // Ignore specifically ignored services and those that were manually
                        // configured
                        String key = "/" + mapRouteToService(serviceId) + "/**";
                        //如果注冊中心的serviceId在staticServices集合中,並且此路由沒有配置URL
                        //那么,更新路由的location為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);
                            }
                        }
                        //如果注冊中心的serviceId不在忽略范圍內,且routesMap中還沒有包含,添加到routesMap
                        //(例子中的配置#忽略所有服務 ignoredServices: '*')
                        if (!PatternMatchUtils.simpleMatch(ignored, serviceId)
                                && !routesMap.containsKey(key)) {
                            // Not ignored
                            routesMap.put(key, new ZuulRoute(key, serviceId));
                        }
                    }
                }
                
                // 如果routesMap中有 /** 的默認路由配置
                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);
                }
                //將routesMap中的數據微調后,放到values<String, ZuulRoute>,返回
                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;
            }

6.2 路由前的預處理:PreDecorationFilter.run()

public Object run() {
                RequestContext ctx = RequestContext.getCurrentContext();
                final String requestURI = this.urlPathHelper.getPathWithinApplication(ctx.getRequest());
                Route route = this.routeLocator.getMatchingRoute(requestURI); //如下.找到匹配的路由
                // ==== 匹配到路由信息
                if (route != null) {
                    String location = route.getLocation();
                    if (location != null) {
                        /** //RequestContext設置 requestURI:路由的pattern路徑
                        //RequestContext設置 proxy:路由id
                        //設置需要忽略的敏感頭信息,要么用全局默認的,要么用路由自定義的
                        //設置重試信息
                        //如果location是 http/https開頭的,RequestContext設置 routeHost:URL
                        //如果location是 forward:開頭的,RequestContext設置 forward信息、routeHost:null
                        //其它 RequestContext設置 serviceId、routeHost:null、X-Zuul-ServiceId
                        //是否添加代理頭信息 X-Forwarded-For
                        //是否添加Host頭信息
                        */
                    }
                }
                // ==== 沒有匹配到路由信息
                else {
                    /**............*/
                }
                return null;
            }

RouteLocator.getMatchingRoute(requestURI)

public Route getMatchingRoute(final String path) {
                    return getSimpleMatchingRoute(path);
                }
                
                protected Map<String, ZuulRoute> getRoutesMap() {
                    if (this.routes.get() == null) {
                        this.routes.set(locateRoutes());
                    }
                    return this.routes.get();
                }

                protected Route getSimpleMatchingRoute(final String path) {
                    //未初始化則初始化
                    getRoutesMap();
                    //獲取准確的path:根據servlet的類型將path中的serveletPath截取掉
                    String adjustedPath = adjustPath(path);
                    //通過path匹配已初化的ZuulRoute
                    ZuulRoute route = getZuulRoute(adjustedPath);
                    //通過ZuulRoute 獲取route
                    return getRoute(route, adjustedPath);
                }

在獲取Zuulroute后通過getRoute獲取到最后的Route

protected Route getRoute(ZuulRoute route, String path) {
                    if (route == null) {
                        return null;
                    }
                    String targetPath = path;
                    //配置的前綴prefix
                    String prefix = this.properties.getPrefix();
                    if(prefix.endsWith("/")) {
                        prefix = prefix.substring(0, prefix.length() - 1);
                    }
                    //訪問path以前綴開頭且配置截取前綴為true(不配置默認為true),截取前綴
                    if (path.startsWith(prefix + "/") && this.properties.isStripPrefix()) {
                        targetPath = path.substring(prefix.length());
                    }
                    //配置截取前綴為true(不配置默認為true)
                    if (route.isStripPrefix()) {
                        //路由path有通配符
                        int index = route.getPath().indexOf("*") - 1;
                        if (index > 0) {
                            //最后的targetPath即為各個服務里的路徑
                            String routePrefix = route.getPath().substring(0, index);
                            targetPath = targetPath.replaceFirst(routePrefix, "");
                            prefix = prefix + routePrefix;
                        }
                    }
                    //標記是否默認支持重試(未配置默認false)
                    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());
                }

6.3 處理路由

RibbonRoutingFilter:使用Ribbon、Hystrix和可插入的http客戶端發送請求

public Object run() {
                    RequestContext context = RequestContext.getCurrentContext();
                    this.helper.addIgnoredHeaders();
                    try {
                        RibbonCommandContext commandContext = buildCommandContext(context);
                        ClientHttpResponse response = forward(commandContext);
                        setResponse(response);
                        return response;
                    }
                    catch (ZuulException ex) {
                        throw new ZuulRuntimeException(ex);
                    }
                    catch (Exception ex) {
                        throw new ZuulRuntimeException(ex);
                    }
                }

SimpleHostRoutingFilter:簡單路由,通過HttpClient向預定的URL發送請求

public Object run() {
                    RequestContext context = RequestContext.getCurrentContext();
                    HttpServletRequest request = context.getRequest();
                    MultiValueMap<String, String> headers = this.helper
                            .buildZuulRequestHeaders(request);
                    MultiValueMap<String, String> params = this.helper
                            .buildZuulRequestQueryParams(request);
                    String verb = getVerb(request);
                    InputStream requestEntity = getRequestBody(request);
                    if (request.getContentLength() < 0) {
                        context.setChunkedRequestBody();
                    }
                    String uri = this.helper.buildZuulRequestURI(request);
                    this.helper.addIgnoredHeaders();
                    try {
                        CloseableHttpResponse response = forward(this.httpClient, verb, uri, request,
                                headers, params, requestEntity);
                        setResponse(response);
                    }
                    catch (Exception ex) {
                        throw new ZuulRuntimeException(ex);
                    }
                    return null;
                }

SendForwardFilter:forward到本地URL

public Object run() {
                    try {
                        RequestContext ctx = RequestContext.getCurrentContext();
                        String path = (String) ctx.get(FORWARD_TO_KEY);
                        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;
                }

7.其他重要組件

FilterRegistry:使用ConcurrentHashMap存儲全部的filter
RequestContext:請求上下文對象,繼承於ConcurrentHashMap<String, Object>,用於請求時的所有參數或其他信息,該對象放入線程中的,故在各個filter中都可獲取到
//注意這里的ThreadLocal實例的initialValue()方法,當ThreadLocal的get()方法返回null的時候總是會調用initialValue()方法

protected static final ThreadLocal<? extends RequestContext> threadLocal = new ThreadLocal<RequestContext>() {
                @Override
                protected RequestContext initialValue() {
                    try {
                        return contextClass.newInstance();
                    } catch (Throwable e) {
                        throw new RuntimeException(e);
                    }
                }
            };
            public RequestContext() {
                super();
            }
            public static RequestContext getCurrentContext() {
                /**省略*/
                if (testContext != null) return testContext;
                //當ThreadLocal的get()方法返回null的時候總是會調用initialValue()方法,所以這里是"無則新建RequestContext"的邏輯
                RequestContext context = threadLocal.get();
                return context;
            }

 參考資料:

  https://www.cnblogs.com/liangzs/p/8946740.html

  https://www.jianshu.com/p/2cc9e2ba2256

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM