WEB 中的文件下載

原文連接html

在 WEB 開發中,咱們會指望用戶在點擊某個連接的時候,下載一個文件(無論這個文件能不能被瀏覽器解析,都要下載)。之前接觸過一種方式,就是在響應 header 中設置 force-downloadjava

Content-Type: application/force-download
Content-Disposition: attachment; filename="test.zip"

然而,這是一種 hack 方式,並不推薦使用:android

Content-Type: application/force-download means "I, the web server, am going to lie to you (the browser) about what this file is so that you will not treat it as a PDF/Word Document/MP3/whatever and prompt the user to save the mysterious file to disk instead". It is a dirty hack that breaks horribly when the client doesn't do "save to disk".

引述自:Utility of HTTP header 「Content-Type: application/force-download」 for mobile?git

有位小夥伴就遇到了不奏效的狀況:github

ATTENTION:
If you use any of the lines below your download will probably NOT WORK on Android 2.1.

Content-Type: application/force-download
Content-Disposition: attachment; filename=MyFileName.ZIP
Content-Disposition: attachment; filename="MyFileName.zip"
Content-Disposition: attachment; filename="MyFileName.ZIP";

引述自:Android and the HTTP download file headersweb

那麼,究竟怎麼辦呢?接下來描述個人同事和我遇到的問題。後端

問題發現

最近接手了一個新項目,今天恰好有空熟悉一下以前的功能。因而打開線上地址,輸入測試帳號,進入一個列表頁面,這個列表頁面提供了下載數據爲 Excel 文件的功能,點了一下下載連接,猛然發現,下載的文件名字怎麼是 download ?爲啥呢?api

我用的瀏覽器是 Chrome 51 ,系統是 OS EI Capitan 10.11.5 。瀏覽器

我一同事 Chrome 47,能夠徹底正常下載!app

先看看爲啥個人瀏覽器不行吧!

第一步探索

打開 Chrome 開發者工具,查看 HTTP 請求,發現響應頭部有以下兩項:

Content-Type: application/octet-stream;charset=GBK
Content-Disposition: attachment; filename="%D6%D0%CE%C4.xlsx

噢,filename 那裏多了一個雙引號,去掉吧!

第二步探索

然而,引號去掉以後,問題依舊!什麼狀況?難道是 filename 須要引號包起來?

好吧,包起來試試!

第三步探索

包起來後問題依舊,什麼鬼?

靈機一動,去看看別人怎麼作的吧!因而找到別人網站一個下載 Excel 的頁面,點擊下載,發現響應 header 裏面是這樣的:

Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8
Content-Disposition: inline;filename="%D6%D0%CE%C4.xlsx";filename*=utf-8''%D6%D0%CE%C4.xlsx

Content-Type 指明瞭具體的文件類型,而後 Content-Disposition 多了一個 filename*= ,這是什麼東西? utf-8 是什麼編碼?

通過一堆胡亂搜索,猜想 utf-8 就是文件名的編碼。爲啥文件名要編碼呢?呃,HTTP header 裏面還未見過中文……

好了,咱們後端的代碼大體作法是這樣的:

response.addHeader("Content-Type", "application/octet-stream");
response.addHeader("Content-Disposition", "attachment; filename=\"" + new String(fileName.getBytes("GBK"), "ISO-8859-1") + "\".xlsx");

看起來,只須要用 filename*= 附上編碼就好了,因而後端代碼改爲:

response.addHeader("Content-Type", "application/octet-stream");
response.addHeader("Content-Disposition", "attachment; filename=\"" + new String(fileName.getBytes("GBK"), "ISO-8859-1") + "\".xlsx;filename*=GBK''" + new String(fileName.getBytes("GBK"), "ISO-8859-1"));

好了,我再點擊下載,沒問題!

第四步探索

看起來好像是 OK 了,可是,用 IE 試一下,又不正常了,文件名字不對了!

爲何呢?別人網站在 IE 下都能正常下載的!如今主要有兩處區別:

  • 咱們的 Content-Type 沒有寫具體;

  • 咱們使用了 GBK 編碼。

一思索,感受編碼的嫌疑較大,爲啥呢?由於對於文件下載,瀏覽器根本不用管文件內容是個啥,只須要按照二進制流寫入本地磁盤就行了,而且,此處也只是文件名錯了,下載下來的文件內容仍是沒問題的。

那就改編碼吧,改爲 UTF-8 :

response.addHeader("Content-Type", "application/octet-stream");
response.addHeader("Content-Disposition", "attachment; filename=\"" + new String(fileName.getBytes("UTF-8"), "ISO-8859-1") + "\".xlsx;filename*=UTF-8''" + new String(fileName.getBytes("UTF-8"), "ISO-8859-1"));

經測試,一切正常!

總結

在文件下載功能中,通常都會藉助於這兩個 header 來達到效果,那麼兩個 header 的具體做用是什麼呢?

  • Content-Type:告訴瀏覽器當前的響應體是個什麼類型的數據。當其爲 application/octet-stream 的時候,就說明 body 裏面是一堆不知道是啥的二進制數據。

  • Content-Disposition:用於向瀏覽器提供一些關於如何處理響應內容的額外的信息,同時也能夠附帶一些其它數據,好比在保存響應體到本地的時候應該使用什麼樣的文件名。

細想一下, Content-Type 好像對於文件下載沒什麼做用?事實上的確如此。但是再想一下,若是瀏覽器不理會 Content-Disposition ,不下載文件怎麼辦?若是此時提供了 Content-Type ,至少瀏覽器還有機會根據具體的 Content-Type 對響應體進行處理。

但是爲何瀏覽器會不理會 Content-Disposition 呢?由於這個 Content-Disposition 頭部並非 HTTP 標準中的內容,只是被瀏覽器普遍實現的一個 header 而已。

話題轉一轉, Content-Disposition 的語法見此處,其中相對重要的點此處羅列一下:

  • 經常使用的 disponsition-type 有 inlineattachment

    • inline:建議瀏覽器使用默認的行爲處理響應體。

    • attachment:建議瀏覽器將響應體保存到本地,而不是正常處理響應體。

  • Content-Disposition 中能夠傳入 filename 參數,有兩種形式:

    • filename=yourfilename.suffix:直接指明文件名和後綴。

    • filename*=utf-8''yourfilename.suffix:指定了文件名編碼。其中,編碼後面那對單引號中還能夠填入內容,此處不贅述,可參考規範

    • 有些瀏覽器不認識 filename*=utf-8''yourfilename.suffix (估計由於這東西比較複雜),因此最好帶上 filename=yourfilename.suffix

相關文章
相關標籤/搜索