遇到問題
起因是這樣的:我在完成一個Excel導出的功能,寫完代碼之后Excel能夠正常導出下載,但是卻無法打開。
開發環境:
JDK 1.8
spring boot 1.5.6.RELEASE
easypoi 3.0.3
調試過程
- 首先我嘗試將生成的Workbook接入本地File,能夠生成Excel文件,并且能夠正常打開,證明Excel生成的代碼沒有問題。
- 同樣的生成Excel代碼在另一個項目中能夠正常生成并下載之后也能正常打開,排除是代碼的問題。
- 既然生成文件沒有問題,但是經過接口傳輸后的Excel卻無法打開,查看Response的Header,同一個Excel多次請求接口Content-Length的值卻不一樣,因此懷疑是這個造成的。
- 對比兩個項目的區別,正常的Response的Header中只有
transfer-encoding: chunked
,并沒有Content-Length,而不正常的項目中有Content-Length但是沒有transfer-encoding。
分析
Content-Length會影響Response body的接口:HTTP Content-Length深入實踐 。
而Transfer-Encoding表示body的長度不確定:HTTP協議之Transfer-Encoding 。
因此懷疑是Content-Length的值不正確導致接收的Excel不完整,所以無法打開。而Transfer-Encoding則會完整地接收Excel,因此能夠正常打開。
難點
找到了原因,但是我沒有找到解決的辦法,兩個項目的環境版本都是一致的。而且也沒有手動的設置Content-Length和Transfer-Encoding,這兩個Header都是spring boot框架自動添加。我也沒有找到更改輸出的控制。最終轉化成一個問題:在spring boot中如何控制返回的Response Header中Content-Length還是Transfer-Encoding?
另外基于HTTP的規則:如果存在Transfer-Encoding(重點是chunked),則在header中不能有Content-Length,有也會被忽視。我嘗試手動添加Transfer-Encoding以覆蓋Content-Length。但是當手動設置Transfer-Encoding之后,接口無法訪問:swagger顯示TypeError: Failed to fetch
。服務端日志中也沒有報錯信息。
解決
后來這篇文章:transfer-encoding和content-length的不同實現 給了我思路。Transfer-Encoding 是傳輸數據編碼,Content-Encoding 是傳輸內容編碼,卻別在于傳輸的內容,于是我想到是否有可能是我改變了body中的內容才導致的。
然后我查找了項目,注意到自定了一個LogFilter,用于輸出request和response的header,param,body的內容,實際上這里就吧response的body轉換成了字符串進行輸入,實際上就是改變了body中的數據。
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
String basePath = servletRequest.getScheme() + "://" + servletRequest.getServerName()
+ ":" + servletRequest.getServerPort() + request.getRequestURI();
log.info("request path: " + basePath);
if (!isSwaggerPath(request)) {
log.info("headers: {}", getHeaders(request));
ContentCachingRequestWrapper wrapperRequest = new ContentCachingRequestWrapper(request);
ContentCachingResponseWrapper wrapperResponse = new ContentCachingResponseWrapper((HttpServletResponse) servletResponse);
String urlParams = getRequestParams(request);
filterChain.doFilter(wrapperRequest, wrapperResponse);
String requestBodyStr = getRequestBody(wrapperRequest);
log.info("params[{}] | request body:{}", urlParams, requestBodyStr);
String responseBodyStr = getResponseBody(wrapperResponse);
log.info("response body:{}", responseBodyStr);
wrapperResponse.copyBodyToResponse();
} else {
filterChain.doFilter(servletRequest, servletResponse);
}
}
嘗試去掉這個Filter,果然response的Header變成了Transfer-Encoding,Excel也能正常打開了。