当在项目中需要获取接口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;复制代码