Apache、Zuul進行轉發後報錯問題的解決
今天用Zuul寫了一個filter進行地址轉換,邏輯比較簡單,前端請求通過Zuul的filter,把前端的token反解為員工號,加入一些特定的引數後,遞交到第三方服務端。大致花了一個小時寫了一個filter,本機測試良好,但是放到測試環境測試,服務端返回500,未返回具體的錯誤message。由於服務端不是我們維護開發的,檢視具體500報錯資訊很難,所以只能進行本地除錯。
系統架構:
整個架構很簡單,外網請求通過Apache反向代理到內網的Nginx,再請求Zuul,Zuul通過一系列轉換後請求第三方系統。本地除錯一切正常,但是在模擬環境部署後,發現問題:通過瀏覽器直接訪問Zuul,返回正常;訪問Nginx,返回正常;但是訪問Apache,返回500。
第一個反應就是找所有請求的差異。從架構和列印的除錯資訊分析,通過apache訪問,請求已經正確到達Zuul應用。所以首先要找出通過nginx正常訪問和通過apache不正常訪問,在zuul伺服器上的差異。
一次GET請求,就包含了請求URL、請求引數和請求頭,首先我們在zuul上加個filter,把這些資訊打出來。
filter的Order放在原來寫的Filter之後。
具體程式碼:
package com.xxx.portalZuul; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper; import org.springframework.stereotype.Component; import org.springframework.util.MultiValueMap; import javax.servlet.http.HttpServletRequest; /** * @author : xxx * @version : * @date : Created in 18:26 2018/8/2 * @description: * @modified By: */ @Component public class testRequestFilter extends ZuulFilter { /** * 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 */ private ProxyRequestHelper helper; @Override public String filterType() { return "pre"; } /** * 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 */ @Override public int filterOrder() { return 1; } /** * 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 */ @Override public boolean shouldFilter() { return true; } /** * 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. */ @Override public Object run() { RequestContext context = RequestContext.getCurrentContext(); HttpServletRequest request = context.getRequest(); this.helper = new ProxyRequestHelper(); MultiValueMap<String, String> headers = this.helper .buildZuulRequestHeaders(request); MultiValueMap<String, String> params = this.helper .buildZuulRequestQueryParams(request); String uri = this.helper.buildZuulRequestURI(request); System.out.println("=========request========="); System.out.println("Headers:"+headers); System.out.println("Params:"+params); System.out.println("URI:"+uri); return null; } }
這樣打出資訊:
報200正確的訪問(敏感資訊已經用XXX代替):
=========request========= Headers:{content-type=[application/x-www-form-urlencoded], cache-control=[no-cache], postman-token=[d6860958-b147-469c-a402-d679e3e8fa13], user-agent=[PostmanRuntime/7.2.0], accept=[*/*], apikey=[XXXXXXXXXXXXXXXX], Accept-Encoding=[gzip]} Params:{token=[XXXXXXXXXXXXXXXXXXXXXX], UserNumber=[XXXXXX]} URI:/XXXXX/XXX/XXXXX/XXXXXXXXXXXXXXXXXX
報500的訪問:在上述資訊的Headers裡,多出了以下資訊:
x-forwarded-for=[172.XX.XX.XX], x-forwarded-host=[172.YY.YY.YY], x-forwarded-server=[172.YY.YY.YY]
但是將這些X-Forwarded資訊直接寫到Http頭裡,然後向第三方伺服器請求,是正常返回,而同樣的資訊,通過Zuul轉換再請求,則報500。
查詢了Zuul的文件所知,Zuul會通過PreDecorationFilter這個過濾器,在頭資訊加入x-forwarded相關資訊。而這個過濾器的過濾順序為5
再次修改上述testRequestFilter ,將Order值改為6,放在PreDecorationFilter之後執行,然後輸出除錯資訊,發現每個請求裡均加入了x-forwarded相關資訊。而通過apache訪問的原本就帶有x-forwarded資訊的請求則變成了以下資訊:
x-forwarded-for=[172.XX.XX.XX, 172.ZZ.ZZ.ZZ], x-forwarded-host=[172.YY.YY.YY,172.ZZ.ZZ.ZZ:8769], x-forwarded-server=[172.ZZ.ZZ.ZZ]
猜測第三方介面伺服器在處理以上資訊的時候,不符合其處理格式,所以報錯。通知第三方伺服器相關部門,其修改處理邏輯後,返回正常。
最後總結排查時候遇到的兩個坑:
- apache做反向代理時候會加上x-forwarded資訊,而nginx做反向代理時候預設不會加上x-forwarded資訊。
- 一開始用RequestContext.getZuulRequestHeaders().toString() 這個方法,只會輸出非x-forwarded開頭的頭資訊,不會輸出帶有x-forwarded開頭的頭資訊,導致一開始除錯好久,還以為一模一樣的頭,照樣報錯,百思不得其解。