当前位置: 首页>后端>正文

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

背景

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

大文件下载测试

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

chrome 连续下载大文件 net--err_failed,第1张
大文件下载.png
chrome 连续下载大文件 net--err_failed,第2张
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 连续下载大文件 net--err_failed,第3张
chrome浏览器对blob 有限制

chrome 连续下载大文件 net--err_failed,第4张
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 以后会没有弹框,故下面紧接着的问题可能也不会存在)
chrome 连续下载大文件 net--err_failed,第5张
https

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

chrome 连续下载大文件 net--err_failed,第6张
https
  • 弹框受到浏览器限制,如果用户禁止弹框,那这个下载是会被拦截的,故而不会下载成功。


    chrome 连续下载大文件 net--err_failed,第7张
    下载被拦截
  • 为了稳定性需要自己部署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,这样的话,浏览器在打开的时候会提示保存还是打开,即使选择打开,也会使用相关联的程序,比如记事本打开,而不是浏览器直接打开。

chrome 连续下载大文件 net--err_failed,第8张
下载response header
5、下面这种fetch写法还是blob文件流的下载方式,还是会先下载完全部blob数据才可以保存
chrome 连续下载大文件 net--err_failed,第9张
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超大文件的问题


https://www.xamrdz.com/backend/3bc1933867.html

相关文章: