概述
開發Web項目的過程中,經常遇到瀏覽器中顯示的內容亂碼,或者服務器獲取瀏覽器請求參數時亂碼的問題,很多同學基本都是在遇到亂碼的時候去網上一頓搜索,然后看哪篇文章比較靠譜就照着上面的內容去配后亂碼成功消失了,然后就沒然后了...
最后基本只是停留在知道怎么樣設置能避免常見的亂碼問題,而不知道具體的原理,一旦遇到了網上查不到的亂碼場景就不知道如何解決了~
本文會深入的讓你了解針對於HTTP請求時,這一去一回(Request,Response)之間,到底做了怎樣的事情,讓你徹底告別Web項目中的亂碼煩惱。本文的內容是基於Tomcat 8.0.23版本的,其他容器也可以參考本文的內容,畢竟理論都是通的~
Response亂碼
當你在瀏覽器中看到響應的內容是亂碼的時候,第一反應就是,是不是我程序的問題,是不是我程序吐出的內容就是個亂碼,所以才導致了瀏覽器里面看到了亂碼。那么接下來我將帶你過一遍Response的過程,以及對剛才的猜測進行驗證~
首先,我建了一個非常簡單的Web項目,里面只有一個Servlet,作用是直接返回我要響應的內容~
項目結構如下:
Servlet內容如下:
web.xml內容如下:
接下來我們在瀏覽器中訪問:http://localhost:8080
首先在Firefox中訪問,結果如下:
接下來再在QQ Browser中訪問,結果如下:
此時我們看見2個瀏覽器中展示的內容無論是瀏覽器正文還是下面的Response標簽中顯示的字符都是亂碼,並且是相同的亂碼內容~
那么這個亂碼是怎么產生的呢?回頭再去看我們servlet中的代碼:
代碼中可以看見我們沒有指定任何的編碼相關內容,那么Tomcat在將result字符串轉換成字節數組時會使用HTTP規范的默認編碼ISO-8859-1,於是輸出的內容就是將result進行ISO-8859-1編碼后的字節數組,
因為ISO-8859-1能表示的字符數量有限,它無法表示中文,所以在此時Response的內容與就已經是亂碼了。
然后Response到達瀏覽器,瀏覽器會獲取Response中的內容,因為Response中的響應頭中沒有指定信息的編碼類型,所以瀏覽器會嘗試根據編碼規則進行推測,並根據推測進行解碼,由於我使用的是中文系統,Firefox
的正文部分采用的是GBK編碼,而Response中的內容則使用了UTF-8編碼。QQ Browser正文部分使用的也是GBK編碼,而Resopnse中的內容則使用了ISO-8859-1編碼(不同瀏覽器,或者同一個瀏覽器的不同版本解碼使用的編碼都可能會不一樣),
解碼之后就是我所看見的 ???~????! 亂碼了。
下面這段程序就可以驗證上面的內容:
上面的驗證本質上是因為在Servlet中處理Response的時候沒有指定編碼,從而導致了使用了默認的ISO-8859-1對Response的內容(楚楚街~賣得漂亮!)進行了編碼,但是因為ISO-8859-1無法表示中文,結果在Servlet編碼
時就已經產生了亂碼,之后無論你怎么解碼它都是個亂碼。
接下來我們改進下Servlet,使其在Response的時候使用UTF-8(因為UTF-8可以表示中文)進行編碼,代碼如下:
然后我們再次請求http://localhost:8080
Firefox結果如下:
QQ Browser結果如下:
從結果我們可以看出,Firefox中正文依然亂碼,而Response的內容正確解析了,而QQ Browser中正文和Response中的內容都是亂碼。
根據之前的分析,我們已經知道Firefox的正文是GBK編碼,Response內容是UTF-8編碼,而QQ Browser中的正文也是GBK編碼,Response的內容是ISO-8859-1編碼。
下面代碼對上面的內容進行了驗證:
我們在對Servlet返回給2個瀏覽器的Response進行一下抓包,看下Response的內容到底是啥~
對Firefox的抓包:
對QQ Browser的抓包:
從結果我們可以看到2個瀏覽器收到的Response的結果都是相同的,其中Data部分都是e6a59ae6a59ae8a1977ee4b9b0e79a84e6bc82e4baae21,這串值就是
對 楚楚街~買的漂亮! 進行UTF-8編碼后的值。
到此為止,做一個小總結:
1.Response在返回前會對要返回的內容做編碼,在不指定(setCharacterEncoding("UTF-8"))的情況下會使用默認的ISO-8859-1編碼。
2.不同的瀏覽器在收到返回的Response時,獲取到的內容是一致的,之后瀏覽器會根據自身的策略對內容進行解析。
繼續上面的亂碼問題,為什么這次使用UTF-8對中文進行了編碼后,瀏覽器里面依然是亂碼呢?原因就是在Response中沒有對響應的內容使用的是哪種編碼做說明,導致了
瀏覽器不知道使用哪種編碼做解碼,然后就使用了瀏覽器默認的解碼行為進行解碼,從而導致了亂碼。
接下來我們為Response加上響應頭Content-Type:text/plain; charset=utf-8來告訴瀏覽器,Response內容應該用什么編碼來解碼,代碼如下:
上面的代碼中,我們指定了2次編碼並且2次的編碼不一致,那么到底會使用哪個編碼呢?還會出現亂碼么?看實際結果說話~
Firefox結果:
QQ Browser結果:
我們可以看到,2個瀏覽器無論是正文部分還是Response的內容中都正確的解碼了~
總結:
1.Response需要告知瀏覽器使用哪一種編碼來解碼其內容。
2.Response所使用的編碼為最后一次設置的編碼,也就是說后面的編碼設置信息會覆蓋掉前面的編碼設置信息。
接下來我們再來使用命令行來請求下試試,如下圖:
發現結果居然是亂碼,難道之前的結論是錯誤的?什么鬼...
其實之前的結論並沒錯,而是之前的結論需要加個限定條件(只適用於瀏覽器),我們把HTTP請求的Response可以划分為2個步驟,第一步是獲取到這個Response,第二步就是對
這個Response進行解析,上面的問題就出現在了第二步(解析)。因為瀏覽器是遵循HTTP規范的,HTTP規范中說明了響應頭中的Content-Type屬性中的charset指定的編碼就是Response
中內容的實際編碼,所以瀏覽器會使用這個charset中指定的編碼對Response中的內容進行解碼,那么自然沒問題了,而命令行則不需要遵循HTTP中的規范,所以命令行並沒有關心charset指定
的內容,而是使用命令行默認的編碼進行解碼的,所以導致了亂碼,知道了問題的根本原因,那么是不是我們改變了命令行的編碼就可以正確顯示了呢?看下面結果:
首先我們修改了命令行的編碼為UTF-8(chcp 65001),然后重新請求后,發現顯示正常了~
到此為止~Response時的亂碼問題成功解決啦~
Response亂碼總結:
1.Response在返回前會對要返回的內容做編碼,在不指定編碼的情況下會使用默認的ISO-8859-1編碼。
2.不同的瀏覽器在收到返回的Response時,獲取到的內容是一致的,之后瀏覽器會根據自身的策略對內容進行解析。
3.Response需要告知瀏覽器使用哪一種編碼來解碼其內容。
4.Response所使用的編碼為最后一次設置的編碼,也就是說后面的編碼設置信息會覆蓋掉前面的編碼設置信息。
5.瀏覽器會遵循HTTP規范,使用Content-Type屬性中的charset指定的編碼去解碼,而命令行工具則不會關注charset的內容,而是根據命令行自身的編碼方案進行解碼。
至此,一文讓你從此告別HTTP亂碼系列文章全部結束了~希望對遇到亂碼的同學有所幫助,哪怕只有一點點~
附上上一篇文章的連接:一文讓你從此告別HTTP亂碼(一)Request篇