當在專案中需要獲取介面response返回資料進度的時候通常是通過以下方式操作的
// AJAX
var xhr = newXMLHttpRequest();
xhr.open('GET','your-http-path');
xhr.onprogress = function(event){
if(event.lengthComputable){
// 獲取返回報文總位元組長度
let total = max=event.total;
// 當前已返回位元組長度
let loaded =event.loaded;
// 計算下載進度
return 100 * (loaded / total);
}
};
// Axios
axios.create({
withCredentials: true,
headers:{
'X-Requested-With': 'XMLHttpRequest'
},
onDownloadProgress: p => {
return 100 * ( p.loaded / p.total )
}
})
複製程式碼
實際上total的數值是從response headers的Content-Length獲取的,但是當後端服務或者nginx開啟gzip之後,Content-Length的長度是gzip壓縮之後的,然而loaded得到的又是解壓完實際的字串長度,所以 total 和 loaded並不能等效相除等到正確的百分比。
google了很多 solution 並不能很好解決以上的問題,忽然靈機一動想到以下的solution
我使用的是Axios作為前端http請求庫
ajax 監聽Progress事件控制程式碼,回撥引數中物件event能獲得請求返回頭,
但是這個headers中只攜帶了Content-type 和 Content-Length, 我嘗試著將未壓縮的資料的大小通過設定Content-type的值攜帶過去
Content-Type: application/json;charset=utf-8;real-length=2018
結果成功的得到了以下的方案
補充1:有人會問為什麼不自定義一個headers,通過自定義的頭進行傳遞,很遺憾我試過在服務端設定自定義headers頭,但是就像上面所說的可能是從安全形度出發在progress事件中只能拿到Content-type 和 Content-Length。
補充2: 有人又會說為什麼不直接在服務端response headers中直接修改Content-Length的大小,要繞怎麼大一圈把前後端都驚動了呢,因為就算你設定Content-length為未壓縮之前的大小,
前端發起請求時,需要Content-length 和 請求實際返回的二進位制流的資料包大小是一樣的,不然請求會假死。
補充3: 在計算機編碼中一個位元組佔用8 bit(1 byte = 8 bit),而一個字元可能是一個單位元組字元,也可能是雙位元組字元。
另外,Buffer.byteLength()方法在寫http響應頭時經常要用到,如果想改寫http響應頭Cotent-Length時,千萬記得一定要用Buffer.byteLength()方法,而不要使用String.prototype.length屬性
複製程式碼
Front End Client
- /util/axios.js
function getDownloadProgress(event){
let header = e.currentTarget.getResponseHeader('Content-Type'),
arr = header.split(';'),
realLenghtArr = arr[2] && arr[2].split('=') || [],
realLenght = realLenghtArr[1] || 0,
progress = ( + e.loaded ) / (+ realLenght) * 100;
return progress
}
axios.create({
withCredentials: true,
headers:{
'X-Requested-With': 'XMLHttpRequest'
},
onDownloadProgress: e => {
let progress = getDownloadProgress(e);
return progress;
}
})
複製程式碼
Node.js Servers
- /middleware/gzip.js
// 關鍵程式碼
let realLength = Buffer.byteLength(ctx.body, 'utf8');
ctx.set({
'Content-Type': `application/json;charset=utf-8;real-length=${ realLength }`
});
// gzip
let buf = await zlib.gzipSync(ctx.body);
ctx.body = buf;
複製程式碼