1. 程式人生 > >WEB 中的檔案下載

WEB 中的檔案下載

在 WEB 開發中,我們會期望使用者在點選某個連結的時候,下載一個檔案(不管這個檔案能不能被瀏覽器解析,都要下載)。以前接觸過一種方式,就是在響應 header 中設定 force-download :

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

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

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”.

Quentin

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

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”;

Jörg Wagner

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

問題發現

最近接手了一個新專案,今天剛好有空熟悉一下之前的功能。於是開啟線上地址,輸入測試賬號,進入一個列表頁面,這個列表頁面提供了下載資料為 Excel 檔案的功能,點了一下下載連結,猛然發現,下載的檔名字怎麼是 download ?為啥呢?

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

我一同事 Chrome 47,可以完全正常下載!

先看看為啥我的瀏覽器不行吧!

第一步探索

開啟 Chrome 開發者工具,檢視 HTTP 請求,發現響應頭部有如下兩項:

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

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

第二步探索

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

好吧,包起來試試!

第三步探索

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

靈機一動,去看看別人怎麼做的吧!於是找到別人網站一個下載 Excel 的頁面,點選下載,發現響應 header 裡面是這樣的:

12 Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8Content-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 裡面還未見過中文……

好了,我們後端的程式碼大致做法是這樣的:

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

看起來,只需要用 filename*= 附上編碼就行了,於是後端程式碼改成:

12 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 :

12 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 有 inline 和 attachment :
    • inline:建議瀏覽器使用預設的行為處理響應體。
    • attachment:建議瀏覽器將響應體儲存到本地,而不是正常處理響應體。
  • Content-Disposition 中可以傳入 filename 引數,有兩種形式:
    • filename=yourfilename.suffix:直接指明檔名和字尾。
    • filename*=utf-8’’yourfilename.suffix:指定了檔名編碼。其中,編碼後面那對單引號中還可以填入內容,此處不贅述,可參考規範
    • 有些瀏覽器不認識 filename*=utf-8''yourfilename.suffix (估計因為這東西比較複雜),所以最好帶上 filename=yourfilename.suffix 。
  • 原文連結  http://yibuyisheng.github.io/blogs/site/blogs/filedownload.html