背景
因為公司業(yè)務(wù)需求,對接一款終端設(shè)備,需要下載黑名單。可是設(shè)備的性能較差,每次下載的報文大小有限制。所以需要采用報文分段下載來實現(xiàn)。
當(dāng)然其實這種需求最好是用查詢分頁來達到分段下載是最好的,可是因為終端那邊實現(xiàn)問題還有以及其他各種原因,不得已采用這種分段下載方式。
找了很多帖子比較少有關(guān)于這種分段返回報文的內(nèi)容,可是有發(fā)現(xiàn)可以利用斷點下載的思想,去實現(xiàn)這個分段報文返回。有斷點下載經(jīng)驗的同學(xué),應(yīng)該很快能理解本文介紹的內(nèi)容,其實都是很皮毛的東西。
正題
分段下載我們主要用到兩個http頭信息:
http 響應(yīng)頭 Accept-Ranges
http 請求頭 Range
服務(wù)器使用 HTTP 響應(yīng)頭Accept-Ranges標(biāo)識自身支持范圍請求(partial requests)。字段的具體值用于定義范圍請求的單位。當(dāng)瀏覽器/客戶端發(fā)現(xiàn) Accept-Ranges 頭時,可以嘗試?yán)^續(xù)中斷了的下載,而不是重新開始。用法:Accept-Ranges: bytes
Range用來標(biāo)志本次請求,獲取數(shù)據(jù)的范圍。如:Range: bytes=100-200
步驟
客戶端請求頭都帶上Range,第一個請求Range:bytes=0-99,第二個Range:bytes=100-199,以此類推。
服務(wù)端除了響應(yīng)頭帶上Accept-Ranges: bytes,還需要根據(jù)請求中的Range來切割報文,返回Range范圍內(nèi)的數(shù)據(jù)。
http響應(yīng)碼設(shè)置為206
http響應(yīng)頭新增Content-Range,標(biāo)識本次響應(yīng)的內(nèi)容的范圍,和整個完整報文的長度
需要注意一點的是,Range單位是byte而不是字符數(shù),所以計算長度的時候要要將字符串換算成byte計算。
接下來展示controller層部分代碼:
// 省略業(yè)務(wù)代碼
//rsp為黑名單的響應(yīng)dto對象,全量的黑名單
String returnJson = JsonUtil.Object2Json(rsp);
httpResp.setContentType("application/json");
//無緩存
httpResp.setHeader("Cache-Control", "no-cache");
//設(shè)置UTF-8編碼
httpResp.setCharacterEncoding(Constant.CHARACTER_ENCODING);
//可以斷點下載
httpResp.setHeader("Accept-Ranges", "bytes");
//設(shè)置http響應(yīng)碼為206
httpResp.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
//獲取頭信息Range,并且去掉bytes=
String range = httpReq.getHeader("Range").replaceAll("bytes=", "");
String[] rangeSplit = range.split("-");
//整段報文的總長度,要用utf-8的編碼字節(jié)長度計算
int returnJsonLength = returnJson.getBytes("UTF-8").length;
//offset 第一次請求默認(rèn)從0開始
int offset = Integer.parseInt(rangeSplit[0]);
// 初始化 length 和 endIndex
// length:本次報文返回的長度;endIndex:本次報文結(jié)束的下標(biāo)位置,默認(rèn)是報文的總長度所以是returnJsonLength - 1
int length = returnJsonLength - offset;
int endIndex = returnJsonLength - 1;
//設(shè)置 length 和 endIndex 值
if (rangeSplit.length > 1) {
//strSplit數(shù)組長度大于1,說明Range是一個范圍,而不是一個固定值
endIndex = Integer.parseInt(rangeSplit[1]);
//endIndex不允許大于等于報文長度
if (endIndex >= returnJsonLength) {
endIndex = returnJsonLength - 1;
}
//因為endIndex最大值比報文長度小于1的,而length是報文返回長度,所以要+1
length = endIndex - offset + 1;
}
httpResp.setHeader("Content-Length", Long.toString(returnJsonLength));
//Content-Range:bytes[json的開始字節(jié)]-[json的結(jié)束字節(jié)]/[報文的總大小]
httpResp.setHeader("Content-Range", "bytes " + offset + "-" + endIndex + "/" + returnJsonLength);
// 獲得寫出流
try {
OutputStream outputStream = httpResp.getOutputStream();
//重點來了,輸出報文,只返回原完整報文的offset至offset + length的字節(jié)內(nèi)容
outputStream.write(returnJson.getBytes("utf-8"), offset, length);
outputStream.flush();
outputStream.close();
} catch (Exception e) {
LogUtil.addProclog(procid, "返回報文錯誤:" + CommonUtil.getTrace(e));
}
缺點:
每次請求只返回部分的報文,可是每次都是獲取全量的黑名單,數(shù)據(jù)庫查詢多次。可以用redis緩存黑名單,減少數(shù)據(jù)庫查詢。僅以記錄本次的解決方案,如果有更好的思路不妨一起交流一下,不才獻丑了。