阅读 965

Node.js 解决Gzip下获取真实的下载进度问题

当在项目中需要获取接口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并不能等效相除等到正确的百分比。

alt

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;
复制代码
文章分类
前端
文章标签