這個問題,CS開發模式總會遇到過。在此詳細記錄,以作技能儲備。
先說段歷史故事:
史前世界:1945~1994年 有一位美國科學家叫Vannevar Bush3在1945年虛構出來了一台名為Memex的桌面設備作為Web理念最早期的原型。這個Memex呢,用於在微縮膠卷上創建和標注跨文檔鏈接,並按照這些鏈接而跳轉切換到所引用的其他微縮膠卷上,使用方式大略類似於我們現在的書簽和超鏈接。
他超級興奮,認為這將革命性地改變知識管理和數據挖掘;
在當時,一直到20世紀90年代初期,挺多的人覺得這種想法非常的愚笨天真可笑。
20世紀60年代,誕生了IBM的GML(Generalized Markup Language,通用標記語言),它用可供機器讀取識別的指令作為文檔的標識符,以標志每段文本的功用,可以明確地指明“這里是文檔的頭部”,“這里是幾個列表項目”諸如此類。
在此后經過20多年的發展,GML(一開始只是用在一些IBM笨重大型機的文本編輯器里)逐漸演變成SGML(Standard Generalized Markup Language,標准通用標記語言)。SGML語言更通用靈活,它把GML原來基於冒號和句號的笨拙語法,改成了我們熟悉的尖括號格式的語法。
又過10年之后,有兩位科研人員,Tim Berners-Lee 和Dan Connolly 開始尋找新的跨域引用方案—這個方案必須非常簡潔明了。他們草擬了HTML(HyperText Markup Language,超文本標記語言),Html是一套繼承自SGML的精簡版語言。隨后他們又進而開發了HTTP協議(HyperText Transfer Protocol ,超文本傳輸協議)
他們研究工作的總成果就是這個誕生於 1991~1993之間由Tim Berners-Lee開發的World Wide Web 程序,這個最原始狀態的瀏覽器可以解析HTML文件,還可以把用戶提交的數據顯示出來,並且只需要點擊一下鼠標,就可以在不同頁面之間切換瀏覽。
W3C 成立原因: 瀏覽器開發商之間的這場軍備競賽主要體現在各競爭產品都在非常快速地開發迭代,以及瘋狂加入各種新功能,也就完全無法顧及產品是否符合規范標准,甚至來不及用正兒八經的文檔記錄下各種新代碼新功能。對核心HTML特性的擅自調整包括各種蠢事(如閃爍的文字,這是Netscape的發明創造,但最終淪為笑柄)乃至一些著名的特性,如可更換字樣(Typeface)或可以在所謂的框架(Frame)里嵌入外部文檔。在各瀏覽器廠商的產品里,往往還內置對自家編程語言(如JavaScript和Visual Basic)支持,以及可在用戶機器上執行跨平台Java或Flash小程序的插件,支持有用但頗詭異的各種HTTP擴展(如Cookie)。這一階段的瀏覽器盡管囿於某些專利和商標上的原因,彼此間會有兼容性問題,但這些不兼容大都還比較表面。 隨着Web的日益發展壯大和百花齊放,一種隱秘的惡疾悄然在瀏覽器引擎之間傳播開來,盡管表面上還勉強維持着兼容性。這么做最開始的理由聽上去還蠻合情合理的:如果瀏覽器A可以正常顯示一個有問題的頁面,而瀏覽器B卻拒絕解析這個頁面(無論基於何種原因),用戶肯定會認為這是瀏覽器B有問題,而一股腦地選擇貌似更強大的瀏覽器A。為了確保瀏覽器可以正確地顯示任何網頁,工程師的開發變得越來越復雜,也沒有什么正式的文檔來描述瀏覽器對於網站管理員胡亂提供的網頁,是怎么進行主動猜測解析的,而在這些處理過程中往往會犧牲掉安全性,偶爾也會累及兼容性。遺憾的是,這樣的變動往往又會進一步縱容各種不靠譜的網頁設計觀念,迫使其他瀏覽器開發商為免掉隊,也只能亦步亦趨地跟進。當然,相關規范標准的細節缺失,更新也不及時,更是助長了這種惡疾的蔓延。 |
第一次瀏覽器大戰:1995~1999年
網景導航者 Netscape Navigator:
1994公布0.9版,修改后發布1.0版本;
1996年網景的占有率達到70%的高峰。
1995年發布IE1;
1996年,Windows操作系統綁定安裝了IE瀏覽器,開始占領市場;
2002年IE擁有了95%的市場份額。
IE完勝。
Netscape Navigator退出市場
同時壟斷也滋生了自滿,微軟處於無敵地位之后就完全缺乏動力去改進自己的瀏覽器。
雖然有很多漏洞和安全問題,但其他勢單力孤的瀏覽器廠商難帶來什么翻天覆地的變化。
另一方面,緩慢的開發進展使W3C得以追上瀏覽器的實際狀況,並認真探索未來Web的一些新概念。
XMLHttpRequest微軟推出的一個頗不起眼的專有API,沒想到現在卻如此的大放異彩。
第二次瀏覽器大戰:2004年至今
2004年,瀏覽器舞台上出現了一位新選手:
Mozilla Firefox(原網景公司Navigator瀏覽器的后裔,由開源社區開發)它針對的正是IE糟糕的安全性和與標准的不兼容性。在獲得IT專欄作家和安全專家的普遍肯定后,Firefox 很快獲得了20%的市場份額。盡管這位后來者很快也被證明和微軟瀏覽器一樣,受到各種安全漏洞困擾,但由於Firefox的開源特性,以及無需迎合頑固的企業用戶,使它的問題修復較為迅速及時。
注意:為什么瀏覽器開發領域里的競爭如此激烈呢?嚴格來說,瀏覽器的市場份額並沒有辦法直接轉化成金錢收入。但專家們認為這關乎權勢地位:因為可以通過瀏覽器來捆綁、推銷或邊緣化某個在線服務(即使像默認搜索引擎這么簡單的服務),也就是說誰控制了瀏覽器,誰就控制了互聯網。注意 為什么瀏覽器開發領域里的競爭如此激烈呢?嚴格來說,瀏覽器的市場份額並沒有辦法直接轉化成金錢收入。但專家們認為這關乎權勢地位:因為可以通過瀏覽器來捆綁、推銷或邊緣化某個在線服務(即使像默認搜索引擎這么簡單的服務),也就是說誰控制了瀏覽器,誰就控制了互聯網。 這些事實連同突然殺入市場的蘋果公司瀏覽器Safari和Opera瀏覽器在智能手機領域的步步領先,一定使微軟的高層深覺頭痛不已。他們已經錯失了20世紀90年代互聯網第一波高潮;當然他們不想再犯同樣的錯誤。微軟重新加大了對IE瀏覽器的投入,發布了有極大提升和在某些方面來說更安全的版本,從IE7、8迅速迭代到了IE9。 好像還嫌事情不夠混亂,由於對W3C理事會在創新性上的不滿,一群參與者創建了一個全新的標准組織,叫網頁超文本技術工作小組(Web Hypertext Application Technology Working Group,WHATWG)來主導HTML5協議的開發,這是對現有標准的第一次整體性和把安全也考慮進去的修訂,但據報道,他們經常由於專利紛爭而沒法和微軟達成一致(所以HTML5在IE中存在不兼容)。 |
再說個歷史問題。
首先,根據 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 來進行多語言編碼的特性從未被主流瀏覽器支持過)。 可是這個問題卻的確是現實需要的,所以瀏覽器就各自想出了一些辦法:
- IE支持兩種格式的混合版:filename="encoded_text" (這里采用的是百分號編碼)。本來按照 RFC 2616 ,引號內的部分應當直接被當作內容,就算它“看起來像是編碼后的字符串”;可是IE卻會“自動”對這樣的文件名進行解碼——前提是該文件名必須有一個不會被編碼的后綴名(即正常的英文字母后綴名)!
- 其他一些瀏覽器則支持一種更為粗暴的方式——允許在 filename="TEXT" 中直接使用 UTF-8 編碼的字符串!
Content-Disposition: attachment; filename="$encoded_fname"; filename*=utf-8''$encoded_fname
問題表現:
兩個完全相同的系統,一個在本機,一個在服務器上。
服務器上的系統,部分IE(版本相同)下載文件名會亂碼。
解決思路:
查看了兩次請求的 請求頭 與響應頭
正常請求 | ![]() |
亂碼請求 | ![]() |
正常響應 | ![]() |
異常響應 | ![]() |
發現 User-Agent (用戶代理)不同。
更改判斷IE條件並更改對應的編碼。
IE的話,通過URLEncoder對filename進行UTF8編碼。
而其他的瀏覽器(firefox、chrome、safari、opera),則要通過字節轉換成ISO8859-1
只需要判斷客戶端是否為IE,然后對filename進行相對應的編碼。
if ((request.getHeader("User-Agent").toUpperCase().indexOf("MSIE") > 0) || (request.getHeader("User-Agent").contains("Trident"))) { filename = URLEncoder.encode(filename, "UTF-8"); System.out.println("IE"); } else { filename = new String(filename.getBytes("UTF-8"), "ISO8859-1"); System.out.println("非IE"); }
完畢!