本文摘自於 《Spring Cloud微服務 入門 實戰與進階》 一書。 #1. /routes 端點web
當@EnableZuulProxy與Spring Boot Actuator配合使用時,Zuul會暴露一個路由管理端點/routes。spring
藉助這個端點,能夠方便、直觀地查看以及管理Zuul的路由。api
將全部端點都暴露出來,增長下面的配置:bash
management.endpoints.web.exposure.include=*
複製代碼
訪問 http://localhost:2103/actuator/routes 能夠顯示全部路由信息:app
{
"/cxytiandi/**": "http://cxytiandi.com",
"/hystrix-api/**": "hystrix-feign-demo",
"/api/**": "forward:/local",
"/hystrix-feign-demo/**": "hystrix-feign-demo"
}
複製代碼
/fliters端點會返回Zuul中全部過濾器的信息。能夠清楚的瞭解Zuul中目前有哪些過濾器,哪些被禁用了等詳細信息。框架
訪問 http://localhost:2103/actuator/filters 能夠顯示全部過濾器信息:ide
{
"error": [
{
"class": "com.cxytiandi.zuul_demo.filter.ErrorFilter",
"order": 100,
"disabled": false,
"static": true
}
],
"post": [
{
"class": "org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter",
"order": 1000,
"disabled": false,
"static": true
}
],
"pre": [
{
"class": "com.cxytiandi.zuul_demo.filter.IpFilter",
"order": 1,
"disabled": false,
"static": true
}
],
"route": [
{
"class": "org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter",
"order": 10,
"disabled": false,
"static": true
}
]
}
複製代碼
建立一個新的Maven項目zuul-file-demo,編寫一個文件上傳的接口,如代碼清單7-20所示。微服務
代碼清單 7-20 文件上傳接口post
@RestController
public class FileController {
@PostMapping("/file/upload")
public String fileUpload(@RequestParam(value = "file") MultipartFile file) throws IOException {
byte[] bytes = file.getBytes();
File fileToSave = new File(file.getOriginalFilename());
FileCopyUtils.copy(bytes, fileToSave);
return fileToSave.getAbsolutePath();
}
}
複製代碼
將服務註冊到Eureka中,服務名稱爲zuul-file-demo,經過PostMan來上傳文件,如圖7-4所示學習
能夠看到接口正常返回了文件上傳以後的路徑,接下來咱們換一個大一點的文件,文件大小爲1.7MB。
能夠看到報錯了(如圖7-5所示),經過Zuul上傳文件,若是超過1M須要配置上傳文件的大小, Zuul和上傳的服務都要加上配置:
spring.servlet.multipart.max-file-size=1000Mb
spring.servlet.multipart.max-request-size=1000Mb
複製代碼
配置加完後從新上傳就能夠成功了,如圖7-6所示。
第二種解決辦法是在網關的請求地址前面加上/zuul,就能夠繞過Spring DispatcherServlet進行上傳大文件。
# 正常的地址
http://localhost:2103/zuul-file-demo/file/upload
# 繞過的地址
http://localhost:2103/zuul/zuul-file-demo/file/upload
複製代碼
經過加上/zuul前綴可讓Zuul服務不用配置文件上傳大小,可是接收文件的服務仍是須要配置文件上傳大小,不然文件仍是會上傳失敗。
在上傳大文件的時候,時間比較會比較長,這個時候須要設置合理的超時時間來避免超時。
ribbon.ConnectTimeout=3000
ribbon.ReadTimeout=60000
複製代碼
在Hystrix隔離模式爲線程下zuul.ribbon-isolation-strategy=thread,須要設置Hystrix超時時間。
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=60000
複製代碼
系統在生產環境出現問題時,排查問題最好的方式就是查看日誌了,日誌的記錄儘可能詳細,這樣你才能快速定位問題。
下面帶你們學習如何在Zuul中輸出請求響應的信息來輔助咱們解決一些問題。
熟悉Zuul的朋友都知道,Zuul中有4種類型過濾器,每種都有特定的使用場景,要想記錄響應數據,那麼必須是在請求路由到了具體的服務以後,返回了纔有數據,這種需求就適合用post過濾器來實現了。如代碼清單7-21所示。
代碼清單 7-21 Zull獲取請求信息
HttpServletRequest req = (HttpServletRequest)RequestContext.getCurrentContext().getRequest();
System.err.println("REQUEST:: " + req.getScheme() + " " + req.getRemoteAddr() + ":" + req.getRemotePort());
StringBuilder params = new StringBuilder("?");
// 獲取URL參數
Enumeration<String> names = req.getParameterNames();
if( req.getMethod().equals("GET") ) {
while (names.hasMoreElements()) {
String name = (String) names.nextElement();
params.append(name);
params.append("=");
params.append(req.getParameter(name));
params.append("&");
}
}
if (params.length() > 0) {
params.delete(params.length()-1, params.length());
}
System.err.println("REQUEST:: > " + req.getMethod() + " " + req.getRequestURI() + params + " " + req.getProtocol());
Enumeration<String> headers = req.getHeaderNames();
while (headers.hasMoreElements()) {
String name = (String) headers.nextElement();
String value = req.getHeader(name);
System.err.println("REQUEST:: > " + name + ":" + value);
}
final RequestContext ctx = RequestContext.getCurrentContext();
// 獲取請求體參數
if (!ctx.isChunkedRequestBody()) {
ServletInputStream inp = null;
try {
inp = ctx.getRequest().getInputStream();
String body = null;
if (inp != null) {
body = IOUtils.toString(inp);
System.err.println("REQUEST:: > " + body);
} catch (IOException e) {
e.printStackTrace();
}
}
}
複製代碼
輸出效果以下:
獲取響應內容第一種方式,如代碼清單7-22所示。
代碼清單 7-22 獲取響應內容(一)
try {
Object zuulResponse = RequestContext.getCurrentContext().get("zuulResponse");
if (zuulResponse != null) {
RibbonHttpResponse resp = (RibbonHttpResponse) zuulResponse;
String body = IOUtils.toString(resp.getBody());
System.err.println("RESPONSE:: > " + body);
resp.close();
RequestContext.getCurrentContext().setResponseBody(body);
}
} catch (IOException e) {
e.printStackTrace();
}
複製代碼
獲取響應內容第二種方式,如代碼清單7-23所示。
代碼清單 7-23 獲取響應內容(二)
InputStream stream = RequestContext.getCurrentContext().getResponseDataStream();
try {
if (stream != null) {
String body = IOUtils.toString(stream);
System.err.println("RESPONSE:: > " + body);
RequestContext.getCurrentContext().setResponseBody(body);
}
} catch (IOException e) {
e.printStackTrace();
}
複製代碼
爲何上面兩種方式能夠取到響應內容?
在RibbonRoutingFilter或者SimpleHostRoutingFilter中能夠看到下面一段代碼,如代碼清單7-24所示。
代碼清單 7-24 響應內容獲取源碼
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);
}
}
複製代碼
forward()方法對服務調用,拿到響應結果,經過setResponse()方法進行響應的設置,如代碼清單7-25所示。
代碼清單 7-25 setResponse(一)
protected void setResponse(ClientHttpResponse resp) throws ClientException, IOException {
RequestContext.getCurrentContext().set("zuulResponse", resp);
this.helper.setResponse(resp.getStatusCode().value(),
resp.getBody() == null ? null : resp.getBody(), resp.getHeaders());
}
複製代碼
上面第一行代碼就能夠解釋咱們的第一種獲取的方法,這邊直接把響應內容加到了RequestContext中。
第二種方式的解釋就在helper.setResponse的邏輯裏面了,如代碼清單7-26所示。
代碼清單 7-26 setResponse(二)
public void setResponse(int status, InputStream entity,
MultiValueMap<String, String> headers) throws IOException {
RequestContext context = RequestContext.getCurrentContext();
context.setResponseStatusCode(status);
if (entity != null) {
context.setResponseDataStream(entity);
}
// .....
}
複製代碼
Zuul中自帶了一個DebugFilter,一開始我也沒明白這個DebugFilter有什麼用,看名稱很容易理解,用來調試的,但是你看它源碼幾乎沒什麼邏輯,就set了兩個值而已,如代碼清單7-27所示。
代碼清單 7-27 DebugFilter run方法
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
ctx.setDebugRouting(true);
ctx.setDebugRequest(true);
return null;
}
複製代碼
要想讓這個過濾器執行就得研究下它的shouldFilter()方法,如代碼清單7-28所示。 代碼清單 7-28 DebugFilter shouldFilter 方法
@Override
public boolean shouldFilter() {
HttpServletRequest request = RequestContext.getCurrentContext().getRequest();
if ("true".equals(request.getParameter(DEBUG_PARAMETER.get()))) {
return true;
}
return ROUTING_DEBUG.get();
}
複製代碼
只要知足兩個條件中的任何一個就能夠開啓這個過濾器,第一個條件是請求參數中帶了某個參數=true就能夠開啓,這個參數名是經過下面的代碼獲取的,如代碼清單7-29所示。
代碼清單 7-29 DebugFilter啓用參數(一)
private static final DynamicStringProperty DEBUG_PARAMETER = DynamicPropertyFactory
.getInstance().getStringProperty(ZuulConstants.ZUUL_DEBUG_PARAMETER, "debug");
複製代碼
DynamicStringProperty是Netflix的配置管理框架Archaius提供的API,能夠從配置中心獲取配置,因爲Netflix沒有開源Archaius的服務端,因此這邊用的就是默認值debug,若是你們想動態去獲取這個值的話能夠用攜程開源的Apollo來對接Archaius,這個在後面章節給你們講解。
能夠在請求地址後面追加debug=true來開啓這個過濾器,參數名稱debug也能夠在配置文件中進行覆蓋,用zuul.debug.parameter指定,不然就是從Archaius中獲取,沒有對接Archaius那就是默認值debug。
第二個條件代碼,如代碼清單7-30所示。
代碼清單 7-30 DebugFilter啓用參數(二)
private static final DynamicBooleanProperty ROUTING_DEBUG = DynamicPropertyFactory
.getInstance().getBooleanProperty(ZuulConstants.ZUUL_DEBUG_REQUEST, false);
複製代碼
是經過配置zuul.debug.request來決定的,能夠在配置文件中配置zuul.debug.request=true開啓DebugFilter過濾器。
DebugFilter過濾器開啓後,並沒什麼效果,在run方法中只是設置了DebugRouting和DebugRequest兩個值爲true,因而繼續看源碼,發如今不少地方有這麼一段代碼,好比com.netflix.zuul.FilterProcessor.runFilters(String)中,如代碼清單7-31所示。
代碼清單 7-31 Debug信息添加
if (RequestContext.getCurrentContext().debugRouting()) {
Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
}
複製代碼
當debugRouting爲true的時候就會添加一些Debug信息到RequestContext中。如今明白了DebugFilter中爲何要設置DebugRouting和DebugRequest兩個值爲true。
到這步後發現仍是很迷茫,通常咱們調試信息的話確定是用日誌輸出來的,日誌級別就是Debug,但這個Debug信息只是累加起來存儲到RequestContext中,沒有對使用者展現。
繼續看代碼吧,功夫不負有心人,在org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter.addResponseHeaders()這段代碼中看到了但願。如代碼清單7-32所示。
代碼清單 7-32 Debug信息設置響應
private void addResponseHeaders() {
RequestContext context = RequestContext.getCurrentContext();
HttpServletResponse servletResponse = context.getResponse();
if (this.zuulProperties.isIncludeDebugHeader()) {
@SuppressWarnings("unchecked")
List<String> rd = (List<String>) context.get(ROUTING_DEBUG_KEY);
if (rd != null) {
StringBuilder debugHeader = new StringBuilder();
for (String it : rd) {
debugHeader.append("[[[" + it + "]]]");
}
servletResponse.addHeader(X_ZUUL_DEBUG_HEADER, debugHeader.toString());
}
}
}
複製代碼
核心代碼在於this.zuulProperties.isIncludeDebugHeader(),只有知足這個條件纔會把RequestContext中的調試信息做爲響應頭輸出,在配置文件中增長下面的配置便可:
zuul.include-debug-header=true
複製代碼
最後在請求的響應頭中能夠看到調試內容,如圖7-7所示。
本文摘自於 《Spring Cloud微服務 入門 實戰與進階》 一書。