• 临时生成的文件下载“提速” (提早开始下载)(node.js)


    我们可能会有这样一个需求:提供一个下载文件接口,该接口从数据库(可能查询慢并且数据大)拉取数据生成文件,返回文件内容提供下载。

     不考虑优化的情况,我们可能是这样的:

    async function downloadAction(req, res) {
        const data = await getData(); // 获取数据
        const fileBinary = await createFile(data); // 使用数据创建文件
        res.writeHead(200, {
            'Content-Type': 'application/vnd.ms-excel',
            'Content-Disposition': `filename=aa.xls`,
        });
        res.body = fileBinary; // 返回文件内容
    }

    假如getData用了2s,createFile用了1s,下载需要使用2s。其它时间比较少忽略不计。

    那么用户请求该接口的时候下载开始前会等待 2s+1s=3s,而下载完则需要 2s+1s+2s=5s 才能下载完,也就是3-5s期间才是用户需要的下载流程。

    产生的问题就是下载前的等待比较久(3s),请求到下载完成也比较久(5s)。

    在这种情况下,我们就可以用 node.js的stream来优化。

    async function downloadAction(req, res) {
        const dataReadableStream = getDateStream(); // 创建数据读取流 
        const fileDuplexStream = createFileStream(); // 创建对应文件格式生成的流
        res.writeHead(200, {
            'Content-Type': 'application/vnd.ms-excel',
            'Content-Disposition': `filename=aa.xls`,
        });
        dataReadableStream.pipe(fileDuplexStream).pipe(res); // 数据流的数据流向文件生成流,文件生成流的数据给到response
    }

    我们再假设getData 耗时的2s中 1s为生成数据1s为传输数据。

    那么,使用流后下载开始的等待时间 为 1s(getData的生成数据),

    使用流后的下载完成耗时约 1s(getData的生成耗时)+2s(传输给用户)≈3s

      因为流式操作是并行的,相当于我们边获取数据,边生成文件,边返回给用户,所以时间缩短了(同时进行的操作以最长耗时的操作为准)。

    也就是1-3s期间才是用户需要的下载流程。比原流程快。(我们这次不讲流的具体操作,因为不同数据,不同文件格式的实现都不一样,需要具体问题具体分析)

    我们是否能让下载更快点呢?

    我们可以让用户感觉快一点,就是让下载提前开始。

    async function downloadAction(req, res) {
        res.writeHead(200, {
            'Content-Type': 'application/force-download',  // 强制下载的请求头,这样浏览器收到该标识,就可以开始下载了
            'Content-Disposition': `filename=aa.xls`,
        });
        res.flushHeaders(); // 设置完返回头后,先把返回头发给用户
        const dataReadableStream = getDateStream(); // 创建数据读取流
        const fileDuplexStream = createFileStream(); // 创建对应文件格式生成的流
    
        dataReadableStream.pipe(fileDuplexStream).pipe(res); // 数据流的数据流向文件生成流,文件生成流的数据给到response
    }

    以上,我们把content-type设置为强制开始下载,并且明确提前把请求头发给用户的浏览器,这样用户一收到该头部,就会吊起一个下载流程,达到让用户看到的下载提前开始的效果。

    使用该优化后0-3s期间才是用户需要的下载流程,总下载时间没变化,但用户一触发下载按钮,就能给出开始下载的反应,体验比较好。

    可能有些人会在自己的项目里面这样处理了,也没达到提前开始下载的体验,原因可能是:使用了反向代理。

    譬如nginx反向代理,默认情况下都会对返回数据进行缓冲,提高数据传输效率。而这个缓冲也就导致我们提前要返回的返回头被nginx拦截了,延迟发给用户的浏览器。

    遇到这种情况只需要在nginx中把你的下载的接口对应的buffer给关掉即可:

    location /download/ {
            proxy_buffering off; # 关闭buffer
                proxy_pass http://127.0.0.1:8888/download;
    }

    如果从用户到接口有多层的反向代理,需要每层都关闭缓冲。

    PS:这里的优化是不考虑错误提示的。这种提前返回请求头的方式,对错误的提醒不友好,因为返回头已经提前返回了,目前我没有了解到另外抛错的好方法。

  • 相关阅读:
    案例十:shell编写nginx服务启动程序
    Linux在实际中的应用
    案例九:shell脚本自动创建多个新用户,并设置密码
    数据架构的演变
    第一个Struts2程序
    关于eclipse导入Tomact报404的问题
    单选框 RadioButton
    EditText编辑框
    Button控件的三种点击事件
    1319: 同构词
  • 原文地址:https://www.cnblogs.com/chianquan/p/13261401.html
Copyright © 2020-2023  润新知