淺談Spring Cloud zuul http請求轉(zhuǎn)發(fā)原理
spring cloud 網(wǎng)關(guān),依賴于netflix 下的zuul 組件
zuul 的流程是,自定義 了ZuulServletFilter和zuulServlet兩種方式,讓開發(fā)者可以去實(shí)現(xiàn),并調(diào)用
先來看下ZuulServletFilter的實(shí)現(xiàn)片段
@Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { try { init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse); try { preRouting(); } catch (ZuulException e) { error(e); postRouting(); return; } // Only forward onto to the chain if a zuul response is not being sent if (!RequestContext.getCurrentContext().sendZuulResponse()) { filterChain.doFilter(servletRequest, servletResponse); return; } try { routing(); } catch (ZuulException e) { error(e); postRouting(); return; } try { postRouting(); } catch (ZuulException e) { error(e); return; } } catch (Throwable e) { error(new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_FROM_FILTER_" + e.getClass().getName())); } finally { RequestContext.getCurrentContext().unset(); } }
從上面的代碼可以看到,比較關(guān)心的是preRouting、routing,postRouting三個(gè)方法 ,這三個(gè)方法會(huì)調(diào)用 注冊為ZuulFilter的子類,首先來看下這三個(gè)方法
preRouting: 是路由前會(huì)做一些內(nèi)容
routing():開始路由事項(xiàng)
postRouting:路由結(jié)束,不管是否有錯(cuò)誤都會(huì)經(jīng)過該方法
那這三個(gè)方法是怎么和ZuulFilter
聯(lián)系在一起的呢?
先來分析下 preRouting:
void postRouting() throws ZuulException { zuulRunner.postRoute(); }
同時(shí) ZuulRunner
再來調(diào)用
public void postRoute() throws ZuulException { FilterProcessor.getInstance().postRoute(); }
最終調(diào)用 FilterProcessor
的 runFilters
public void preRoute() throws ZuulException { try { runFilters("pre"); } catch (ZuulException e) { throw e; } catch (Throwable e) { throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_PRE_FILTER_" + e.getClass().getName()); } }
看到了runFilters 是通過 filterType(pre ,route ,post )來過濾出已經(jīng)注冊的 ZuulFilter:
public Object runFilters(String sType) throws Throwable { if (RequestContext.getCurrentContext().debugRouting()) { Debug.addRoutingDebug("Invoking {" + sType + "} type filters"); } boolean bResult = false; //通過sType獲取 zuulFilter的列表 List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType); if (list != null) { for (int i = 0; i < list.size(); i++) { ZuulFilter zuulFilter = list.get(i); Object result = processZuulFilter(zuulFilter); if (result != null && result instanceof Boolean) { bResult |= ((Boolean) result); } } } return bResult; }
再來看下 ZuulFilter的定義
public abstract class ZuulFilter implements IZuulFilter, Comparable<ZuulFilter> { private final DynamicBooleanProperty filterDisabled = DynamicPropertyFactory.getInstance().getBooleanProperty(disablePropertyName(), false); /** * to classify a filter by type. Standard types in Zuul are "pre" for pre-routing filtering, * "route" for routing to an origin, "post" for post-routing filters, "error" for error handling. * We also support a "static" type for static responses see StaticResponseFilter. * Any filterType made be created or added and run by calling FilterProcessor.runFilters(type) * * @return A String representing that type */ abstract public String filterType(); /** * filterOrder() must also be defined for a filter. Filters may have the same filterOrder if precedence is not * important for a filter. filterOrders do not need to be sequential. * * @return the int order of a filter */ abstract public int filterOrder(); /** * By default ZuulFilters are static; they don't carry state. This may be overridden by overriding the isStaticFilter() property to false * * @return true by default */ public boolean isStaticFilter() { return true; }
只列出了一部分字段,但可以看到filterType和filterOrder兩個(gè)字段,這兩個(gè)分別是指定filter是什么類型,排序
這兩個(gè)決定了實(shí)現(xiàn)的ZuulFilter會(huì)在什么階段被執(zhí)行,按什么順序執(zhí)行
當(dāng)選擇好已經(jīng)注冊的ZuulFilter后,會(huì)調(diào)用ZuulFilter的runFilter
public ZuulFilterResult runFilter() { ZuulFilterResult zr = new ZuulFilterResult(); if (!isFilterDisabled()) { if (shouldFilter()) { Tracer t = TracerFactory.instance().startMicroTracer("ZUUL::" + this.getClass().getSimpleName()); try { Object res = run(); zr = new ZuulFilterResult(res, ExecutionStatus.SUCCESS); } catch (Throwable e) { t.setName("ZUUL::" + this.getClass().getSimpleName() + " failed"); zr = new ZuulFilterResult(ExecutionStatus.FAILED); zr.setException(e); } finally { t.stopAndLog(); } } else { zr = new ZuulFilterResult(ExecutionStatus.SKIPPED); } } return zr; }
其中run 是一個(gè)ZuulFilter的一個(gè)抽象方法
public interface IZuulFilter { /** * a "true" return from this method means that the run() method should be invoked * * @return true if the run() method should be invoked. false will not invoke the run() method */ boolean shouldFilter(); /** * if shouldFilter() is true, this method will be invoked. this method is the core method of a ZuulFilter * * @return Some arbitrary artifact may be returned. Current implementation ignores it. */ Object run(); }
所以,實(shí)現(xiàn)ZuulFilter的子類要重寫 run方法,我們來看下 其中一個(gè)階段的實(shí)現(xiàn) PreDecorationFilter 這個(gè)類是Spring Cloud封裝的在使用Zuul 作為轉(zhuǎn)發(fā)的代碼服務(wù)器時(shí)進(jìn)行封裝的對象,目的是為了決定當(dāng)前的要轉(zhuǎn)發(fā)的請求是按ServiceId,Http請求,還是forward來作轉(zhuǎn)發(fā)
@Override 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) { ctx.put("requestURI", route.getPath()); ctx.put("proxy", 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", route.getRetryable()); } // 如果配置的轉(zhuǎn)發(fā)地址是http開頭,會(huì)設(shè)置 RouteHost if (location.startsWith("http:") || location.startsWith("https:")) { ctx.setRouteHost(getUrl(location)); ctx.addOriginResponseHeader("X-Zuul-Service", location); } // 如果配置的轉(zhuǎn)發(fā)地址forward,則會(huì)設(shè)置forward.to else if (location.startsWith("forward:")) { ctx.set("forward.to", StringUtils.cleanPath(location.substring("forward:".length()) + route.getPath())); ctx.setRouteHost(null); return null; } else { // 否則以serviceId進(jìn)行轉(zhuǎn)發(fā) // set serviceId for use in filters.route.RibbonRequest ctx.set("serviceId", location); ctx.setRouteHost(null); ctx.addOriginResponseHeader("X-Zuul-ServiceId", location); } if (this.properties.isAddProxyHeaders()) { addProxyHeaders(ctx, route); String xforwardedfor = ctx.getRequest().getHeader("X-Forwarded-For"); String remoteAddr = ctx.getRequest().getRemoteAddr(); if (xforwardedfor == null) { xforwardedfor = remoteAddr; } else if (!xforwardedfor.contains(remoteAddr)) { // Prevent duplicates xforwardedfor += ", " + remoteAddr; } ctx.addZuulRequestHeader("X-Forwarded-For", xforwardedfor); } if (this.properties.isAddHostHeader()) { ctx.addZuulRequestHeader("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("http://", "/"); ctx.set("forward.to", forwardURI); } return null; }
這個(gè)前置處理,是為了后面決定以哪種ZuulFilter來處理當(dāng)前的請求 ,如 SimpleHostRoutingFilter,這個(gè)的filterType是post ,當(dāng) ``PreDecorationFilter設(shè)置了requestContext中的 RouteHost,如 SimpleHostRoutingFilter中的判斷
@Override public boolean shouldFilter() { return RequestContext.getCurrentContext().getRouteHost() != null && RequestContext.getCurrentContext().sendZuulResponse(); }
在 SimpleHostRoutingFilter中的run中,真正實(shí)現(xiàn)地址轉(zhuǎn)發(fā)的內(nèi)容,其實(shí)質(zhì)是調(diào)用 httpClient進(jìn)行請求
@Override 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 { HttpResponse response = forward(this.httpClient, verb, uri, request, headers, params, requestEntity); setResponse(response); } catch (Exception ex) { context.set(ERROR_STATUS_CODE, HttpServletResponse.SC_INTERNAL_SERVER_ERROR); context.set("error.exception", ex); } return null; }
最后如果是成功能,會(huì)調(diào)用 注冊 為post的ZuulFilter ,目前有兩個(gè) SendErrorFilter 和 SendResponseFilter 這兩個(gè)了,一個(gè)是處理錯(cuò)誤,一個(gè)是處理成功的結(jié)果
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Spring Cloud Zuul路由網(wǎng)關(guān)服務(wù)過濾實(shí)現(xiàn)代碼
- SpringCloud Zuul過濾器和谷歌Gauva實(shí)現(xiàn)限流
- SpringCloud Zuul實(shí)現(xiàn)動(dòng)態(tài)路由
- SpringCloud Zuul在何種情況下使用Hystrix及問題小結(jié)
- 詳解SpringCloud Zuul過濾器返回值攔截
- SpringCloud實(shí)戰(zhàn)之Zuul網(wǎng)關(guān)服務(wù)
- spring cloud 使用Zuul 實(shí)現(xiàn)API網(wǎng)關(guān)服務(wù)問題
- Spring Cloud入門教程之Zuul實(shí)現(xiàn)API網(wǎng)關(guān)與請求過濾
- Spring Cloud學(xué)習(xí)教程之Zuul統(tǒng)一異常處理與回退
- spring cloud zuul修改請求url的方法
- SpringCloud Zuul基本使用方法匯總
相關(guān)文章
SpringBoot生成PDF的五種實(shí)現(xiàn)方法總結(jié)
這篇文章主要介紹了SpringBoot生成PDF的五種實(shí)現(xiàn)方法,在開發(fā)中經(jīng)常會(huì)遇到需要進(jìn)行對一些數(shù)據(jù)進(jìn)行動(dòng)態(tài)導(dǎo)出PDF文件,然后讓用戶自己選擇是否需要打印出來,這篇文章我們來介紹五種實(shí)現(xiàn)方法,需要的朋友可以參考下2024-10-10java?io文件操作從文件讀取數(shù)據(jù)的六種方法
這篇文章主要為大家介紹了java?io操作總結(jié)從文件讀取數(shù)據(jù)的六種方法,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-03-03使用redisTemplate的scan方式刪除批量key問題
這篇文章主要介紹了使用redisTemplate的scan方式刪除批量key問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12Fluent MyBatis實(shí)現(xiàn)動(dòng)態(tài)SQL
MyBatis 令人喜歡的一大特性就是動(dòng)態(tài) SQL。本文主要介紹了Fluent MyBatis實(shí)現(xiàn)動(dòng)態(tài)SQL,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08SpringBoot實(shí)現(xiàn)API接口的完整代碼
這篇文章主要給大家介紹了關(guān)于SpringBoot實(shí)現(xiàn)API接口的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10IDEA對使用了第三方依賴jar包的非Maven項(xiàng)目打jar包的問題(圖文詳解)
這篇文章主要介紹了IDEA對使用了第三方依賴jar包的非Maven項(xiàng)目打jar包的問題,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07