官方圖
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