由來
在寫一個小小的表單提交功能的時候,出現了亂碼,很奇怪request上來的參數全部是亂碼,而從數據庫查詢出來的中文顯示到頁面正常,鎖定肯定是request對象那里出了問題。后來經過排查,發現是我封裝的框架中出了問題,總結為在setCharacterEncoding方法之前,調用了getParameter方法,導致字符集改變失敗。沒看過Tomcat實現Servlet的源碼,貌似是一旦調用getParameter方法Request的參數就會全部被解析,從而再調用setCharacterEncoding就無效了。
原理解析
其實編碼問題本質還是兩點:
瀏覽器在封裝Http請求的時候的編碼和服務器在解析Http請求編碼不一致
服務器返回數據的時候編碼和瀏覽器解析不同。
那么我們就從這兩點入手解析。
瀏覽器請求
在點擊提交表單的那一刻,瀏覽器把表單內容封裝成一個Http請求,數據通過a=1&b=2這樣的形式直接請求服務器,表單值會被瀏覽器最一次urlencode,對于不同的請求方式編碼不同:
Get和Post請求
瀏覽器會讀取頁面的編碼(頁面編碼會在Content-type頭中體現),用此編碼對表單值做urlencode,那么到服務器的編碼方式就是你Content-Type里的編碼。很多通過JS提交表單為了規避瀏覽器的urlencode帶來的編碼混淆,會對數據首先做一次urlencode,這樣在服務器上做一次urldecode既可(因為js做完urlencode后內容為ASCII字符,所以這樣的字符無論瀏覽器用什么編碼解碼出來都是一樣的)
AJAX請求
在Jquery中AJAX請求全部使用utf8編碼封裝請求,如果你的頁面和項目用的非utf8編碼,一定會出現亂碼
瀏覽器地址欄直接輸入帶參數的地址
這種情況就比較復雜,不同的瀏覽器編碼也不相同。Chrome之類的瀏覽器默認使用utf8編碼(urlencode),而IE則使用GBK(死變態IE!!!)。
服務器端解碼
對于服務器端我在此只討論Servlet。
Get請求
對于Get請求,有兩種方式解碼:
在Servlet容器中設置,例如Tomcat設置URIEncoding="UTF-8",就會對Get請求用utf8解碼(貌似Tomcat7會報無效,具體解決請百度,反正我不同這種方法)
String name = new String(request.getParameter("name").getBytes("iso-8859-1"),"GBK"));第一個編碼就是你Servlet容器(例如Tomcat)里設置的編碼,默認iso-8859-1,第二個參數就是你瀏覽器使用的編碼格式。如果你用表單提交,那這個編碼就是頁面的編碼(Content-Type里的charset=XXX),如果你直接用瀏覽器地址欄里敲,恭喜你,你得判斷userAgent來使用不同編碼了。這也是我為啥不提倡第一種方式,因為它遇到瀏覽器直接敲出來的參數就非常不靈活。
至于為什么要使用getBytes("iso-8859-1"),是因為在你瀏覽器用某種編碼后,Servlet容器自作多情給你用iso-8859-1解碼了一下,如果你設置了URIEncoding="UTF-8"它就會用utf8給你解碼,運氣好你瀏覽器用的也是這種編碼,那解出來就直接用了,所以在ISO-8859-1的情況下你得再“原路返回”到二進制,重新用正確的編碼解碼一下。
Post請求和Ajax請求
Post請求就比較簡單一點了,同樣你可以使用Get請求中的方法2來解決,不過比較麻煩,這時候我們就可以使用Servlet里的方法request.setCharacterEncoding方法設置你的解碼類型,例如你的頁面編碼是utf8,表單則urlencode成utf8了,那么你在調用getParameter方法之前(記住,一定要之前!!在第一次調用getParameter之前!)使用setCharacterEncoding方法。 Ajax請求同理。
響應請求
響應也是相同道理,這回輪到服務器做編碼,瀏覽器做解碼。只需要設置response.setCharacterEncoding,就會自動在響應頭的Content-Type中加入charset=XXX,返回的內容就可以被正常解析啦~
我想我說的相對比較清楚了,網上很多解決亂碼的帖子都只是講你加上某句代碼就會解決,這樣是不科學的,一定也要知道原理,也要知道每句代碼背后做了哪些工作。其實我們在操作HttpServlet對象的時候,本質上是對Http頭的一些信息做修改。
如果有什么問題或者理解錯誤的地方,歡迎指正討論。