chrome 連續(xù)下載大文件 net::err_failed

背景

后端下載接口采用blob 流式下載大文件且需要鑒權(quán),前端也相應(yīng)的改用流式下載,2G 以下的測(cè)試沒(méi)問(wèn)題。

大文件下載測(cè)試

隨后測(cè)試了下大文件下載,準(zhǔn)備了一個(gè)6G 多的文件。第一個(gè)次下載可以,再次下載該文件就報(bào)錯(cuò):net::err_failed。如下圖,兩個(gè)文件大概到了10G 多就失敗了,報(bào)錯(cuò)如下:

大文件下載.png
net_err_failed.png

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

前端實(shí)現(xiàn)方式如下:

 const res = await $API('downloadFile', null, null, id, {
        timeout: 7200 * 1000,
        responseType: 'blob'
    })
    // 直接返回文件內(nèi)容,有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、是不是磁盤滿了?

查看虛擬機(jī)磁盤還剩30G ,排除。

2、chrome瀏覽器是不是會(huì)緩存blob,并對(duì)blob 大小有限制?

搜索了很多文章發(fā)現(xiàn),確實(shí)會(huì)緩存并且有限制。blob存儲(chǔ)在瀏覽器的沙盒文件系統(tǒng)中,當(dāng)瀏覽器下載或讀取Blob文件時(shí),會(huì)將文件存儲(chǔ)在瀏覽器的緩存中。這種緩存機(jī)制會(huì)受到內(nèi)存限制,會(huì)遇到內(nèi)存不足的問(wèn)題。所以不好判斷,但肯定和內(nèi)存有關(guān)。

chrome瀏覽器對(duì)blob 有限制

blob 限制

所以這種寫法可能是超出瀏覽器的blob 限制或者內(nèi)存限制了。因?yàn)閍xios 是需要等待整個(gè)blob文件流返回才會(huì)結(jié)束請(qǐng)求,整個(gè)響應(yīng)是加載到瀏覽器的內(nèi)存中的,響應(yīng)結(jié)束之后前端才能構(gòu)建 blob 對(duì)象,轉(zhuǎn)化成文件下載,而不是邊下載邊保存文件的。有時(shí)也會(huì)出現(xiàn)頁(yè)面崩潰的情況。

解決辦法

由于后端不想繞過(guò)登錄解決鑒權(quán),問(wèn)題,只能從前端先想辦法。在FileSaver.js 里看到推薦下載2G 以上的文件用StreamSaver.js,搭配 fetch 可以實(shí)現(xiàn)邊下載邊保存。

1、StreamSaver.js下載原理

模擬了服務(wù)器保存文件所要做的事情:給mitm.html 頁(yè)面發(fā)送一個(gè)帶有Content-Disposition標(biāo)頭的流,告訴瀏覽器保存文件。同時(shí)創(chuàng)建一個(gè)sw.js作為服務(wù)器,由 service worker 創(chuàng)建一個(gè)下載鏈接,然后打開(kāi)這個(gè)鏈接。StreamSaver.js 在github上的2個(gè)托管文件:

  • mitm.html:作為web頁(yè)面和service worker消息通信的中間人,加工處理web頁(yè)面消息以及MessageChannel給service worker;注冊(cè)管理service worker,防重啟。
  • sw.js:充當(dāng)服務(wù)器,用來(lái)攔截請(qǐng)求,制造假的響應(yīng),讓瀏覽器去下載資源

它通過(guò)直接創(chuàng)建一個(gè)可寫流到文件系統(tǒng)的方法,而不是將數(shù)據(jù)保存在客戶端存儲(chǔ)或內(nèi)存中。解決了內(nèi)存占用過(guò)大的問(wèn)題。

2、代碼實(shí)現(xiàn)
export const downloadFileByStreamSaver = (url, fileName) => {
    //fetch 默認(rèn)沒(méi)有超時(shí)限制
    fetch(url, {
        method: 'get',
        headers: {
            Authorization: localStorage.getItem('token'),
            responseType: 'blob'
        }
    }).then(res => {
        //如果是文件就下載,需要后端header設(shè)置Content-Disposition
        if (res.headers.get('Content-Disposition')) {
            // 創(chuàng)建一個(gè)文件,該文件支持寫入操作
            const fileStream = streamSaver.createWriteStream(fileName)
            const readableStream = res.body
            // more optimized
            if (window.WritableStream && readableStream.pipeTo) {
                return readableStream.pipeTo(fileStream).then(() => {
                    ElMessage.success('下載文件成功!')
                })
            }
            // 監(jiān)聽(tīng)文件內(nèi)容是否讀取完整,讀取完就執(zhí)行“保存并關(guān)閉文件”的操作
            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 {
           //不是文件應(yīng)該就是報(bào)錯(cuò)了
            res.json().then(json => {
                errorMessageTip({
                    tipMessage: json.message || '下載文件失敗',
                    title: '下載文件'
                })
            })
        }
    })
}

大文件下載問(wèn)題解決。

3、 缺點(diǎn)
  • 需要去訪問(wèn)官方的sw.js來(lái)攔截請(qǐng)求,故而下載時(shí)會(huì)出現(xiàn)一個(gè)短暫的彈框,影響交互體驗(yàn)(官方說(shuō)明使用https 以后會(huì)沒(méi)有彈框,故下面緊接著的問(wèn)題可能也不會(huì)存在)
https

因?yàn)槌鲇诎踩剂浚琒ervice workers只能由HTTPS承載,因此域名不是https 的話也會(huì)報(bào)錯(cuò)。

https
  • 彈框受到瀏覽器限制,如果用戶禁止彈框,那這個(gè)下載是會(huì)被攔截的,故而不會(huì)下載成功。


    下載被攔截
  • 為了穩(wěn)定性需要自己部署mitm.html和serviceWorker.js,和index.html 同級(jí)就行。

總結(jié)
1、后端有鑒權(quán)的下載
  • blob:適合動(dòng)態(tài)生成的下載一些動(dòng)態(tài)數(shù)據(jù)或者小文件
  • fetch +StreamSaver:大文件
  • arraybuffer:沒(méi)試驗(yàn)過(guò),有興趣的可以讓后端試試
  • base64:大文件可能也有內(nèi)存溢出問(wèn)題
2、后端無(wú)鑒權(quán),可以生成靜態(tài)url----最推薦
  • a 標(biāo)簽:<a download="附件.pdf">下載文件</a>
  • window.open或location.href
3、 nginx 下載限制

nginx代理的緩存默認(rèn)為1個(gè)G,可以在nginx配置proxy_max_temp_file_size等關(guān)于緩存的配置項(xiàng)

4、關(guān)于header 設(shè)置Content-disposition

Content-disposition是告訴瀏覽器文件保存在本地還是瀏覽器內(nèi)存。當(dāng)響應(yīng)類型為application/octet-stream時(shí),如果使用了Content-Disposition頭信息,那么意味著不想直接顯示內(nèi)容,而是想彈出一個(gè)“文件下載”的對(duì)話框。關(guān)鍵在于一定要加上attachment,這樣的話,瀏覽器在打開(kāi)的時(shí)候會(huì)提示保存還是打開(kāi),即使選擇打開(kāi),也會(huì)使用相關(guān)聯(lián)的程序,比如記事本打開(kāi),而不是瀏覽器直接打開(kāi)。

下載response header
5、下面這種fetch寫法還是blob文件流的下載方式,還是會(huì)先下載完全部blob數(shù)據(jù)才可以保存

fetch blob

所以,只要是blob文件流的下載方式,都是先下載完全部數(shù)據(jù)才彈出保存窗口。

參考文章

如何用 JavaScript 下載文件
google-chrome - xhr blob responseType 的內(nèi)存使用情況(Chrome)
瀏覽器blob限制
Fetch API
Fetch API Response
ReadableStream
前端自個(gè)突破瀏覽器Blob和RAM大小限制保存文件的騷玩法!
streamsaver——下載打包2GB以上的文件
HTTP知多少——Content-disposition(文件下載)
vue前端下載阿里oss超大文件的問(wèn)題

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,401評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,011評(píng)論 3 413
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 175,263評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 62,543評(píng)論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,323評(píng)論 6 404
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 54,874評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,968評(píng)論 3 439
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 42,095評(píng)論 0 286
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,605評(píng)論 1 331
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,551評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,720評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,242評(píng)論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 43,961評(píng)論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 34,358評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 35,612評(píng)論 1 280
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,330評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,690評(píng)論 2 370

推薦閱讀更多精彩內(nèi)容