美文网首页Web前端之路
chrome 连续下载大文件 net::err_failed

chrome 连续下载大文件 net::err_failed

作者: 嘻哈章鱼小丸子 | 来源:发表于2023-11-08 18:18 被阅读0次
    背景

    后端下载接口采用blob 流式下载大文件且需要鉴权,前端也相应的改用流式下载,2G 以下的测试没问题。

    大文件下载测试

    随后测试了下大文件下载,准备了一个6G 多的文件。第一个次下载可以,再次下载该文件就报错:net::err_failed。如下图,两个文件大概到了10G 多就失败了,报错如下:

    大文件下载.png net_err_failed.png

    但是,再次下载小于5G 的文件是能下载的。

    前端实现方式如下:

     const res = await $API('downloadFile', null, null, id, {
            timeout: 7200 * 1000,
            responseType: 'blob'
        })
        // 直接返回文件内容,有code码表示失败
        if (res.code) {
            errorMessageTip({
                tipMessage: res.message || '下载文件失败',
                title: '下载文件'
            })
        } else {
            downloadFile(name, res, true)
            ElMessage.success('下载文件成功!')
        }
    
    export const downloadFile = (fileName, content) => {
        const fileNames = fileName.split('.')
        const fileType = fileNames[fileNames.length - 1]
        let b = new Blob([content])
        let link = document.createElement('a')
        const url = URL.createObjectURL(b)
        link.href = url
        link.download = fileName
        link.style.display = 'none'
        document.body.appendChild(link)
        link.click()
        link.remove()
        URL.revokeObjectURL(url)
    }
    
    原因分析
    1、是不是磁盘满了?

    查看虚拟机磁盘还剩30G ,排除。

    2、chrome浏览器是不是会缓存blob,并对blob 大小有限制?

    搜索了很多文章发现,确实会缓存并且有限制。blob存储在浏览器的沙盒文件系统中,当浏览器下载或读取Blob文件时,会将文件存储在浏览器的缓存中。这种缓存机制会受到内存限制,会遇到内存不足的问题。所以不好判断,但肯定和内存有关。

    chrome浏览器对blob 有限制
    blob 限制

    所以这种写法可能是超出浏览器的blob 限制或者内存限制了。因为axios 是需要等待整个blob文件流返回才会结束请求,整个响应是加载到浏览器的内存中的,响应结束之后前端才能构建 blob 对象,转化成文件下载,而不是边下载边保存文件的。有时也会出现页面崩溃的情况。

    解决办法

    由于后端不想绕过登录解决鉴权,问题,只能从前端先想办法。在FileSaver.js 里看到推荐下载2G 以上的文件用StreamSaver.js,搭配 fetch 可以实现边下载边保存。

    1、StreamSaver.js下载原理

    模拟了服务器保存文件所要做的事情:给mitm.html 页面发送一个带有Content-Disposition标头的流,告诉浏览器保存文件。同时创建一个sw.js作为服务器,由 service worker 创建一个下载链接,然后打开这个链接。StreamSaver.js 在github上的2个托管文件:

    • mitm.html:作为web页面和service worker消息通信的中间人,加工处理web页面消息以及MessageChannel给service worker;注册管理service worker,防重启。
    • sw.js:充当服务器,用来拦截请求,制造假的响应,让浏览器去下载资源

    它通过直接创建一个可写流到文件系统的方法,而不是将数据保存在客户端存储或内存中。解决了内存占用过大的问题。

    2、代码实现
    export const downloadFileByStreamSaver = (url, fileName) => {
        //fetch 默认没有超时限制
        fetch(url, {
            method: 'get',
            headers: {
                Authorization: localStorage.getItem('token'),
                responseType: 'blob'
            }
        }).then(res => {
            //如果是文件就下载,需要后端header设置Content-Disposition
            if (res.headers.get('Content-Disposition')) {
                // 创建一个文件,该文件支持写入操作
                const fileStream = streamSaver.createWriteStream(fileName)
                const readableStream = res.body
                // more optimized
                if (window.WritableStream && readableStream.pipeTo) {
                    return readableStream.pipeTo(fileStream).then(() => {
                        ElMessage.success('下载文件成功!')
                    })
                }
                // 监听文件内容是否读取完整,读取完就执行“保存并关闭文件”的操作
                const writer = fileStream.getWriter()
                const reader = res.body.getReader()
                const pump = () =>
                    reader.read().then(res => {
                        if (res.done) {
                            writer.close()
                        } else {
                            writer.write(res.value).then(pump)
                        }
                    })
    
                pump()
            } else {
               //不是文件应该就是报错了
                res.json().then(json => {
                    errorMessageTip({
                        tipMessage: json.message || '下载文件失败',
                        title: '下载文件'
                    })
                })
            }
        })
    }
    

    大文件下载问题解决。

    3、 缺点
    • 需要去访问官方的sw.js来拦截请求,故而下载时会出现一个短暂的弹框,影响交互体验(官方说明使用https 以后会没有弹框,故下面紧接着的问题可能也不会存在)
    https

    因为出于安全考量,Service workers只能由HTTPS承载,因此域名不是https 的话也会报错。

    https
    • 弹框受到浏览器限制,如果用户禁止弹框,那这个下载是会被拦截的,故而不会下载成功。


      下载被拦截
    • 为了稳定性需要自己部署mitm.html和serviceWorker.js,和index.html 同级就行。

    总结
    1、后端有鉴权的下载
    • blob:适合动态生成的下载一些动态数据或者小文件
    • fetch +StreamSaver:大文件
    • arraybuffer:没试验过,有兴趣的可以让后端试试
    • base64:大文件可能也有内存溢出问题
    2、后端无鉴权,可以生成静态url----最推荐
    • a 标签:<a href="https://www.baidu.top.pdf" download="附件.pdf">下载文件</a>
    • window.open或location.href
    3、 nginx 下载限制

    nginx代理的缓存默认为1个G,可以在nginx配置proxy_max_temp_file_size等关于缓存的配置项

    4、关于header 设置Content-disposition

    Content-disposition是告诉浏览器文件保存在本地还是浏览器内存。当响应类型为application/octet-stream时,如果使用了Content-Disposition头信息,那么意味着不想直接显示内容,而是想弹出一个“文件下载”的对话框。关键在于一定要加上attachment,这样的话,浏览器在打开的时候会提示保存还是打开,即使选择打开,也会使用相关联的程序,比如记事本打开,而不是浏览器直接打开。

    下载response header
    5、下面这种fetch写法还是blob文件流的下载方式,还是会先下载完全部blob数据才可以保存
    fetch blob
    所以,只要是blob文件流的下载方式,都是先下载完全部数据才弹出保存窗口。
    参考文章

    如何用 JavaScript 下载文件
    google-chrome - xhr blob responseType 的内存使用情况(Chrome)
    浏览器blob限制
    Fetch API
    Fetch API Response
    ReadableStream
    前端自个突破浏览器Blob和RAM大小限制保存文件的骚玩法!
    streamsaver——下载打包2GB以上的文件
    HTTP知多少——Content-disposition(文件下载)
    vue前端下载阿里oss超大文件的问题

    相关文章

      网友评论

        本文标题:chrome 连续下载大文件 net::err_failed

        本文链接:https://www.haomeiwen.com/subject/amuzidtx.html