瀏覽器輸入圖片鏈接無法預覽,變成直接下載,點擊按鈕無法下載文件


今天給大家分享兩個比較有用的瀏覽器行為與預期不一致的現象

現象1:點擊按鈕無法實現文件下載

在瀏覽器里點擊實現好的「下載商品圖片」按鈕卻無法下載(預期應該下載 zip 文件)

但如果你在瀏覽器的地址欄里輸入此下載地址卻又能直接從瀏覽器里下載,這是為何?

我們可以打開調試工具「網絡部分」,然后點擊一下上面的「下載商品圖片」,首先看一下網絡請求是否正常。

1、 首先看請求頭,可以看出狀態碼是 200,另外還有 content-disposition 與 Content-Type 這兩個 response header

畫外音:Content-Type: application/octet-stream 告訴客戶端這是一個二進制文件,content-disposition 告訴客戶端這是一個需要下載的附件並告訴瀏覽器該附件默認的文件名。

2、再看此請求的 response body,是否和步驟一的 application/octet-stream 相符:

可以看到 response 就是一堆亂碼,即文件的二進制流表現形式,所以從請求來看其實是沒有問題的,文件是正常的返回的,但為啥文件卻沒有下載下來,下載下來的文件去哪里了呢,注意看上圖的另一個紅框 XHR ,它的全稱是 XMLHttpRequest,是 ajax 請求的一種表現形式。

ajax 本身無法觸發瀏覽器的下載功能, 它的 response 會交由 JavaScript 處理,使用 ajax 下載完成后,response 以字符串的形式存儲在內存中,那使用 ajax 就沒法下載了嗎?不是的,我們看下瀏覽器為啥能下載

我們發現使用瀏覽器的  GET 請求(主要以 frame 加載, a 標簽點擊觸發)或 POST請求(以 form 的形式存在)是可以下載文件的,因為這是瀏覽器的內置事件,下載的 response 會交由瀏覽器自己處理,瀏覽器如果識別到是二進制流數據則下載,如果識別到是可以打開的文件,如 xml, image 等則不會下載,會以預覽的樣式存在。

那么為啥 ajax 不能默認實現文件下載呢,這是瀏覽器的安全策略限制的,試想如果 ajax 可以下載文件,那就意味着 ajax 可以直接與磁盤交互,這會存在嚴重的安全隱患。

根據以上分析,要使用  ajax 下載文件我們也就有思路了,既然使用 a 標簽(或  frame)的點擊事件可以觸發瀏覽器的內置下載行為,那我們在用 ajax 下載拿到 response 后,可以用 js 新建一個隱藏的 a 標簽(標簽的 href 指向文件的鏈接),執行它的 click 事件,這樣就觸發了瀏覽器的內置下載事件,就可以下載文件了,不過需要注意的事,創建的 a 標簽中要添加一個 download 屬性。 。

這個 download 屬性有啥用呢,對於瀏覽器能打開的文件,例如 html,xml 等,如果你不加 download,點擊 a 標簽就不是下載了,而是打開。(注意 download 屬性目前只被火狐和谷歌兼容)

使用 ajax 來執行下載文件的代碼示例如下:

const filename = response.headers['content-disposition'].match(
      /filename=(.*)/
)[1]
// 首先要創建一個 Blob 對象(表示不可變、原始數據的類文件對象)
const blob = new Blob([response.data], {type: 'application/zip'});
if (typeof window.navigator.msSaveBlob !== 'undefined') {
    // 兼容IE,window.navigator.msSaveBlob:以本地方式保存文件
    window.navigator.msSaveBlob(blob, decodeURI(filename))
} else {
    let elink = document.createElement("a"); // 創建一個<a>標簽
    elink.style.display = "none"; // 隱藏標簽
    elink.href = window.URL.createObjectURL(blob); // 配置href,指向本地文件的內存地址
    elink.download = filename;
    elink.click();
    URL.revokeObjectURL(elink.href); // 釋放URL 對象
    document.body.removeChild(elink); // 移除<a>標簽
}

 

現象2:在瀏覽器輸入圖片鏈接想預覽,結果卻變成了下載圖片

這個問題其實經由上文分析,相信你不難猜出是咋回事,我們先抓包看一下:

可以看到返回的 Content-Type 為 octet-stream,上文我們提到,它指任意類型的二進制流數據,一般下載文件返回的是這種類型,瀏覽器由於無法識別打開流數據,所以會下載,那為啥大多數圖片在瀏覽器上是可以預覽的呢,因為它返回的 Content-Type 是 image/png 或 image/jpeg 等瀏覽器可以直接識別打開的文件,這樣就不會執行下載事件

總結

以上兩個問題需要我們對瀏覽器的工作機制與 HTTP 協議有一定的了解,所以基礎真的很重要啊,不然很可能你排查半天也無從下手,但如果你知道了這些原理,抓個包分析一下它們的 Content-Type,瞬間就豁然開朗了!另外對一些疑難雜症,了解 HTTP 協議與瀏覽器的工作機制也有助於幫助你快速定位解決問題。

比如上圖的解決方案中我們通過 content-disposition 來獲取文件的名稱

const filename = response.headers['content-disposition'].match(
      /filename=(.*)/
)[1]
 

但在最開始發現這段代碼有問題,打印日志發現 response.headers['content-disposition'] 居然為空,可是打開瀏覽器的 network 會發現, content-disposition 明明存在啊

那為啥在 reponse 的 header 里拿不到 content-disposition 呢?

一查發現原來還是 HTTP 協議的問題

默認情況下,header 只有七種 simple response headers (簡單響應首部)可以暴露給外部:

Cache-Control
Content-Language
Content-Length
Content-Type
Expires
Last-Modified
Pragma

這里的暴露給外部,意思是讓客戶端(比如 Chrome)可以訪問得到,既可以在 Network 里看到,也可以在代碼里獲取到他們的值。

而 content-disposition  不在其中,所以即使服務器在協議回包里加了該字段,如下

response.setHeader("content-disposition", "attachment; filename=" + filename);
 

但因沒“暴露”給外部,客戶端就「看得到,吃不到」。

而響應首部 Access-Control-Expose-Headers 就是控制“暴露”的開關,它列出了哪些首部可以作為響應的一部分暴露給外部。

所以如果想要讓客戶端可以訪問到其他的首部信息,服務器不僅要在 header 里加入該首部,還要將它們在 Access-Control-Expose-Headers 里面列出來,如下:

response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
response.setHeader("content-disposition", "attachment; filename=" + filename);
 

這樣的話 JS 的 response header 里就有 content-disposition 的值啦。

參考鏈接: Access-Control-Expose-Headers: http://1il58.cn/AptUz(文章來源前端大全)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM