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

背景

后端下载接口采用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超大文件的问题

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,692评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,482评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,995评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,223评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,245评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,208评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,091评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,929评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,346评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,570评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,739评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,437评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,037评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,677评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,833评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,760评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,647评论 2 354

推荐阅读更多精彩内容