Zuul作為微服務閘道器,匯聚使用者端請求,根據路徑規則,轉發給後臺微服務。
由於@EnableZuulServer註解使ZuulServerAutoConfiguration自動配置類生效,但該配置類只配置了SendForwardFilter作為路由使用,而SendForwardFilter需要執行緒本地變數RequestContext存在“forward。to”,才會使用RequestDispatcher將請求轉發給servlet容器裡地當前應用地其它servlet/jsp/html,用途實在有限。
一般我們不用@EnableZuulServer註解,而是使用增強版的@EnableZuulProxy註解。
而@EnableZuulProxy註解使ZuulProxyAutoConfiguration自動配置類生效,該配置類繼承了ZuulServerAutoConfiguration,引入了RibbonCommandFactory,還額外定義了DiscoveryClientRouteLocator、PreDecorationFilter、RibbonRoutingFilter、SimpleHostRoutingFilter、ZuulDiscoveryRefreshListener、SimpleServiceRouteMapper等。
首先在ZuulServerAutoConfiguration定義了ZuulController作為spring mvc地handler,其次定義的ZuulHandlerMapping作為spring mvc的HandlerMapping,從RouteLocator裡獲取路徑與ZuulController關聯註冊到handlerMap,供後續查詢。
回到spring mvc處理過程,先是DispatcherServlet透過HandlerMapping獲取Handler,此處的HandlerMapping是ZuulServerAutoConfiguration定義的ZuulHandlerMapping,其getHandler會走到lookUpHandler,程式碼如下:
# spring-cloud-netflix-core-1。3。6。RELEASE。jar!/org。springframework。cloud。netflix。zuul。web。ZuulHandlerMapping protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception { if (this。errorController != null && urlPath。equals(this。errorController。getErrorPath())) { return null; } String[] ignored = this。routeLocator。getIgnoredPaths()。toArray(new String[0]); // 1。 zuul。ignoredPattern預設未設定(見SimpleRouteLocator) if (PatternMatchUtils。simpleMatch(ignored, urlPath)) { return null; } RequestContext ctx = RequestContext。getCurrentContext(); if (ctx。containsKey(“forward。to”)) { // 2。 避免死迴圈 return null; } if (this。dirty) { synchronized (this) { if (this。dirty) { registerHandlers(); // 3。 關聯路徑和handler到handlerMap this。dirty = false; } } } return super。lookupHandler(urlPath, request); // 4。 從handlerMap匹配路徑返回handler } private void registerHandlers() { Collection
接著詳細看下獲取路由這塊,ZuulServerAutoConfiguration定義了CompositeRouteLocator,它的功能由SimpleRouteLocator和DiscoveryClientRouteLocator來代理。
SimpleRouteLocator較為簡單,程式碼如下:
# spring-cloud-netflix-core-1。3。6。RELEASE。jar!/org。springframework。cloud。netflix。zuul。filters。SimpleRouteLocator @Override public List
然後再看下DiscoveryClientRouteLocator處理邏輯(重寫locateRoutes):
# spring-cloud-netflix-core-1。3。6。RELEASE。jar!/org。springframework。cloud。netflix。zuul。filters。discovery。DiscoveryClientRouteLocator protected LinkedHashMap
即配置了路徑到某個服務,或者以服務名開頭的路徑能夠匹配到handler。
然後是HandlerAdapter是否匹配此Handler。此處應當由SimpleControllerHandlerAdapter(預設的HandlerAdapter之一)來處理,即handler本身實現了Controller介面,而ZuulController為ServletWrappingController子類,符合情況。
透過此HandlerAdapter執行handle,即SimpleControllerHandlerAdapter,此時觸發org。springframework。cloud。netflix。zuul。web。ZuulController的執行
# spring-cloud-netflix-core-1。3。6。RELEASE。jar!/org。springframework。cloud。netflix。zuul。web。ZuulController public ZuulController() { setServletClass(ZuulServlet。class); setServletName(“zuul”); setSupportedMethods((String[]) null); // Allow all } public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { try { // We don‘t care about the other features of the base class, just want to // handle the request return super。handleRequestInternal(request, response); // 1。 觸發ZuulServlet。service } finally { // @see com。netflix。zuul。context。ContextLifecycleFilter。doFilter RequestContext。getCurrentContext()。unset(); } }
ZuulServlet的執行比較簡單,程式碼如下:
# zuul-core-1。3。0。jar!/com。netflix。zuul。http。ZuulServlet public void service(javax。servlet。ServletRequest servletRequest, javax。servlet。ServletResponse servletResponse) throws ServletException, IOException { try { init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse); // Marks this request as having passed through the “Zuul engine”, as opposed to servlets // explicitly bound in web。xml, for which requests will not have the same data attached RequestContext context = RequestContext。getCurrentContext(); context。setZuulEngineRan(); try { preRoute(); // 1。 執行ZuulRunner。preRoute,觸發FilterProcessor。preRoute,導致從FilterLoad獲取“pre”型別ZuulFilter,執行ZuulFilter。runFilter,如果IZuulFilter。shouldFilter為true,則執行IZuulFilter。run } catch (ZuulException e) { error(e); postRoute(); return; } try { route(); // 2。 同preRoute,只是FilterLoader獲取ZuulFilter的型別為“route” } catch (ZuulException e) { error(e); postRoute(); return; } try { postRoute(); // 3。 同preRoute,只是FilterLoader獲取ZuulFilter的型別為“post” } catch (ZuulException e) { error(e); return; } } catch (Throwable e) { error(new ZuulException(e, 500, “UNHANDLED_EXCEPTION_” + e。getClass()。getName())); // 4。 同preRoute,只是FilterLoader獲取ZuulFilter的型別為“error” } finally { RequestContext。getCurrentContext()。unset(); } } void preRoute() throws ZuulException { zuulRunner。preRoute(); }
上面講自動配置漏掉的一處是ZuulServerAutoConfiguration有定義ZuulFilterInitializer,此bean在初始化後會將所有ZuulFilter註冊到FilterRegistry,而FilterLoad的ZuulFilter正是來源於FilterRegistry。(FilterLoad、FilterRegistry以static方式提供單例物件)
回到zuul本身,PreDecorationFilter用於決定將請求交給哪個路由,程式碼如下:
# spring-cloud-netflix-core-1。3。6。RELEASE。jar!/org。springframework。cloud。netflix。zuul。filters。pre。PreDecorationFilter public boolean shouldFilter() { RequestContext ctx = RequestContext。getCurrentContext(); return !ctx。containsKey(FORWARD_TO_KEY) // a filter has already forwarded && !ctx。containsKey(SERVICE_ID_KEY); // a filter has already determined serviceId } @Override public Object run() { RequestContext ctx = RequestContext。getCurrentContext(); final String requestURI = this。urlPathHelper。getPathWithinApplication(ctx。getRequest()); Route route = this。routeLocator。getMatchingRoute(requestURI); // 1。 去除servlet路徑,匹配route if (route != null) { String location = route。getLocation(); if (location != null) { // url或serviceId存在時 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)); // 2。 配置了“zuul。routes。{id}。url”,設定routeHost,路由交給SimpleHostRoutingFilter ctx。addOriginResponseHeader(SERVICE_HEADER, location); } else if (location。startsWith(FORWARD_LOCATION_PREFIX)) { // 3。 如果路徑以“forward”開頭,清除routeHost,路由交給SendForwardFilter 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); // 4。 配置了“zuul。routes。{id}。serviceId”,路由交給RibbonRoutingFilter 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())); } } } 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; }
無論是SimpleHostRoutingFilter使用apache httpClient直接發出請求,還是RibbonRoutingFilter服務發現做客戶端負載均衡,可以參考我之前的文章,此處不做更多涉及。