轉:HTTP ---HTTP頭的編碼問題(Content-Disposition)

最近在作項目時遇到了一個 case :須要實現一個強制在瀏覽器中的下載功能(即強制讓瀏覽器彈出下載對話框),而且文件名必須保持和用戶以前上傳時相同(可能包含非 ASCII 字符)。 前一個需求很容易實現:使用 HTTP Header 的 Content-Disposition: attachment 便可,還能夠配合 Content-Type: application/octet-stream 來確保萬無一失。然後一個需求就比較蛋疼了,牽扯到 Header 的編碼問題(文件名是做爲 filename 參數放在 Content-Disposition 裏面的)。衆所周知, HTTP Header 中的 Content-Type 能夠指定內容的編碼,可 Header 自己的編碼又該如何制定?甚至, Header 到底是否容許非 ASCII 編碼呢? 若是聽任編碼問題無論,那麼恭喜你,你必定會遇到在某個系統及瀏覽器下下載文件時文件名亂碼的狀況。若是你嘗試搜索解決,那麼再一次恭喜你,你會找到一堆自相矛盾的解決方案(我能夠負責任地告訴你,其中的99%都是不符合標準的 trick 罷了)。讓咱們來看看到底應該如何優雅完美地解決這個問題吧! 爲了探索這個問題,我走了很多彎路。從本身嘗試,到 Google 、百度(分別嘗試過中英文搜索),再到閱讀 Discuz 等經典項目的源碼,衆說紛紜、莫衷一是。最後我纔想到迴歸 RFC ,從標準文檔中找辦法,果真有所收穫。因爲探究過程實在太曲折,我就先把標準作法寫下來。html

應該這樣設置 Content-Disposition :
Content-Disposition: attachment;
                      filename="$encoded_fname";
                      filename*=utf-8''$encoded_fname
其中,$encoded_fname指的是將 UTF-8 編碼的原始文件名按照  RFC 3986 進行百分號 urlencode 後獲得的( PHP 中使用 rawurlencode() 函數)。這幾行也能夠合併爲一行,推薦使用一個空格隔開。 另外,爲了兼容 IE6 ,請保證原始文件名必須包含英文擴展名!

好了,接下來咱們來看看爲何要這麼作以及爲何能這麼作。 首先,根據 HTTP 1.1 協議規範( RFC 2616 Section 4 ), HTTP 消息格式實際上是基於古老的 ARPA INTERNET TEXT MESSAGES ( RFC 822 Section 3 ),根據其規定,消息只能是 ASCII 編碼的。 RFC 2616 Section 2.2 又一次強調, TEXT 中若要使用其餘字符集,必須使用 RFC 2047 的規則將字符串編碼爲 ASCII 碼(事實上這個規則本來是針對 MIME 的擴展,使用的是 base64 編碼,格式與百分號編碼有很大不一樣)。總而言之,按照標準, HTTP Header 中的文本數據必須是 ASCII 編碼的。瀏覽器

filename="TEXT"
 ;這是 RFC 2616 標準,TEXT必須是 ASCII 字符且被認爲就是「原文」
filename*=charset'lang'encoded-text
 ;這是按照 RFC 2047 擴展後的,注意格式上的細微區別,採用 base64 編碼(編碼結果也是 ASCII 字符)

然而,事實上在1999年 HTTP 1.1 標準推出之時, Content-Dispostion 這個 Header 尚不是正式標準的一部分,只不過是由於被普遍使用而從 MIME 標準中直接借用過來了而已( RFC 2616 Section 19.5.1 )。於是幾乎沒有瀏覽器去支持 Content-Disposition 的多語言編碼特性這樣一個「擴展特性的擴展特性」(事實上, HTTP 1.1 草案中建議的使用 RFC 2047 來進行多語言編碼的特性從未被主流瀏覽器支持過)。 但是這個問題卻的確是現實須要的,因此瀏覽器就各自想出了一些辦法:app

  • IE支持兩種格式的混合版:filename="encoded_text" (這裏採用的是百分號編碼)。原本按照 RFC 2616 ,引號內的部分應當直接被看成內容,就算它「看起來像是編碼後的字符串」;但是IE卻會「自動」對這樣的文件名進行解碼——前提是該文件名必須有一個不會被編碼的後綴名(即正常的英文字母后綴名)!
  • 其餘一些瀏覽器則支持一種更爲粗暴的方式——容許在 filename="TEXT" 中直接使用 UTF-8 編碼的字符串!

這兩類瀏覽器的行爲是彼此互不兼容的。因此你能夠判斷 UA 而後對IE使用前一種辦法,其餘瀏覽器使用後一種,這樣即可以達到通常狀況下可以 just work 的效果( Discuz 就是這麼作的)。不過對於 Opera 和 Safari ,這樣作可能不必定有效。 時代在進步,2010年 RFC 5987 發佈,正式規定了 HTTP Header 中多語言編碼的處理方式,應當採用相似 MIME 擴展的 parameter*=charset'lang'value 的格式,可是其中 value 應根據 RFC 3986 Section 2.1 使用百分號進行編碼,而且規定瀏覽器至少應該支持 ASCII 和 UTF-8 。隨後,2011年 RFC 6266 發佈,正式將 Content-Disposition 歸入 HTTP 標準,並再次強調了 RFC 5987 中多語言編碼的方法,還給出了一個範例用於解決向後兼容的問題——就是我在一開始給出的例子:函數

Content-Disposition: attachment;
                      filename="encoded_text";
                      filename*=utf-8''encoded_text

在這個例子中,對於較新的 Firefox 、 Chrome 、 Opera 、 Safari 等瀏覽器,都支持新標準規定的 filename* ,而且會優先使用,因此儘管 filename=」encoded_text」 不被它們支持,仍然不會有問題;至於使用 UTF-8 只是由於它是標準中強制要求必須支持的。而對於舊版本的IE瀏覽器,它們沒法識別後面的 filename* ,會自動忽略並使用舊的 filename 。這樣一來就完美解決了多瀏覽器的多語言兼容問題,既不須要 UA 判斷,也符合標準。 P.S. 爲何 PHP 要使用 rawurlencode() 函數呢?由於這纔是真正符合 RFC 3986 的「百分號URL編碼」,只是因爲歷史緣由,以前先有了一個 urlencode() 函數用於實現 HTTP POST 中的相似的編碼規則,故而只好用這麼一個奇怪的名字。二者的區別在於前者會把空格編碼爲%20,然後者則會編碼爲+號。若是使用後者,那麼IE6在下載帶有空格的文件名時空格會變爲加號。通常狀況下,你是不會用到 urlencode() 這個函數的( Discuz 某些版本中錯誤地使用它來進行文件名編碼,從而致使空格變加號的BUG)。 via:Robot Shellpost

 

Content-disposition 是 MIME 協議的擴展,MIME 協議指示 MIME 用戶代理如何顯示附加的文件。當 Internet Explorer 接收到頭時,它會激活文件下載對話框,它的文件名框自動填充了頭中指定的文件名。(請注意,這是設計致使的;沒法使用此功能將文檔保存到用戶的計算機上,而不向用戶詢問保存位置。)
Content-Disposition就是當用戶想把請求所得的內容存爲一個文件的時候提供一個默認的文件名。具體的定義以下編碼

Content-Disposition: attachment; filename=「filename.xls」

 

固然filename參數能夠包含路徑信息,但User-Agnet會忽略掉這些信息,只會把路徑信息的最後一部分作爲文件名。當你在響應類型爲application/octet- stream狀況下使用了這個頭信息的話,那就意味着你不想直接顯示內容,而是彈出一個」文件下載」的對話框,接下來就是由你來決定「打開」仍是「保存」了。url

如:Response.AppendHeader("Content-Disposition","attachment;filename=MyExcel.xls");插件

 

Content disposition 容許 servlet 指定文檔表示的信息

HTTP response header中的content-disposition 容許 servlet 指定文檔表示的信息。使用這種header ,你就能夠將文檔指定成單獨打開(而不是在瀏覽器中打開),還能夠根據用戶的操做來顯示。若是用戶要保存文檔,你還能夠爲該文檔建議一個文件名。這個建議 名稱會出如今 Save As 對話框的「文件名」欄中。若是沒有指定,則對話框中就會出現 servlet 的名字。
servlet 中,將 header 設置成下面這樣:
response.setHeader("Content-disposition","attachment;filename="+ "Example.xls" );
response.setHeader("Content-Disposition", "inline; filename="fliename);//點擊打開會在ie中打開。 
須要說明的有三點: 
(1) 中文文件名須要進行iso8859-1轉碼方可正確顯示:
fileName = new String(fileName.getBytes("GBK"),"iso8859-1");
(2)傳遞的文件名,須要包含後綴名(若是此文件有後綴名),不然丟失文件的屬性,而不能自行選擇相關程序打開。
(3)有下載前詢問(是打開文件仍是保存到計算機)和經過IE瀏覽器直接選擇相關應用程序插件打開兩種方式,前者如上代碼所示,後者以下:
response.setHeader("Content-disposition","filename="+ "Example.xls" );設計

相關文章
相關標籤/搜索