深入剖析tomcat容器的亂碼問題
http的本質還是socket,所以底層傳輸的還是位元組流(不要深究到二進位制層面),既然是位元組流,那麼肯定會涉及到編碼和解碼.
亂碼的原因大家肯定都知道,也很簡單,那就是編碼和解碼的格式不一致
。
既然知道了根源,那麼我們是否能從這個角度來解決問題?是的,只要你保證前臺編碼和後臺解碼的格式一樣的時候,就肯定不會出現亂碼了。
下面要用到一些例子,這裡先給出程式:
index.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=gbk"> <title>Insert title here</title> <script type="text/javascript"> function loadFunction() { var url = "encodingServlet?name="; url += encodeURI("嚴"); document.getElementById("name").href = url + "&d=" + new Date(); } function escapeCode() { alert(encodeURI("嚴")) } </script> </head> <body onload="loadFunction()"> <a id="name">name</a> <form action="encodingServlet" method="get"> <input type="text" name="name"/> <input type="submit" text="submit"/> </form> <button onclick="escapeCode()">aaa</button> <br> <a href="encodingServlet?name=嚴">aaaa</a> </body> </html>
首先看前臺編碼的幾種情況:
1.首先最常見的就是你在位址列直接輸入一個地址
比如:https://www.google.com/webhp?hl=en&tab=ww#hl=en&tbo=d&output=search&sclient=psy-ab&q=你好&oq=你好
非常不幸,這種情況下你根本無法控制瀏覽器如何對你輸入的內容進行編碼。我使用英文版的IE進行測試,它使用的是ISO-8859-1格式,而英文版的FireFox使用的是UTF-8。
這種情況就不討論了,google也會因為這種原因而導致亂碼,不過我相信大家總是會有辦法解決的。(怎麼解決請教我一下)
2.網頁裡面的一個超連結
比如:上面index.jsp中的<a href="encodingServlet?name=嚴">aaaa</a>
那麼這個時候前臺的編碼是以
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
裡面的pageEncoding決定的
3.FORM表單
無論get方式還是post方式都是以
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
裡面的charset決定的
4.encodeURI函式
該函式將引數中的字元將轉換成UTF-8編碼方式的byte陣列,並使用十六進位制轉義序列(%xx)生成替換。
過程用Java模擬如下:
byte[] data1 = "嚴".getBytes("UTF-8");
String result = "";
for(byte datai : data1)
{
result += "%" + Integer.toHexString(datai >= 0 ? datai : datai + 256);
}
再來看看後臺是如何解碼的:
我們都是通過request.getParameter("name");這樣的語句來得到引數的,
在我們呼叫這個方法的時候tomcat容器會自動幫我們做一次解碼,請看下面的tomcat部分原始碼(解析引數):
該方法位於:org.apache.catalina.util.RequestUtil
public static void parseParameters(Map map, byte[] data, String encoding)
throws UnsupportedEncodingException {
if (data != null && data.length > 0) {
int ix = 0;
int ox = 0;
String key = null;
String value = null;
while (ix < data.length) {
byte c = data[ix++];
switch ((char) c) {
case '&':
value = new String(data, 0, ox, encoding);
if (key != null) {
putMapEntry(map, key, value);
key = null;
}
ox = 0;
break;
case '=':
if (key == null) {
key = new String(data, 0, ox, encoding);
ox = 0;
} else {
data[ox++] = c;
}
break;
case '+':
data[ox++] = (byte)' ';
break;
case '%':
data[ox++] = (byte)((convertHexDigit(data[ix++]) << 4)
+ convertHexDigit(data[ix++]));
break;
default:
data[ox++] = c;
}
}
//The last value does not end in '&'. So save it now.
if (key != null) {
value = new String(data, 0, ox, encoding);
putMapEntry(map, key, value);
}
}
}
可以看到,這個方法會要求輸入編碼格式encoding,那麼這個引數是怎麼得到的呢。
它分為兩種情況,如果下面兩種情況你都沒有設定,就會採用ISO-8859-1的格式來解碼:
1.引數位於URL中,也就是通過GET的方式請求,這個encoding請在tomcat的聯結器中配置,也就是server.xml中的
<Connector connectionTimeout="20000" port="9180" protocol="HTTP/1.1" redirectPort="8443" URIEncoding="UTF-8"/>
加入了URIEncoding="UTF-8"
2.引數在請求實體中,也就是POST方式,這個時候你可以直接通過request.setCharacterEncoding("UTF-8");的方式設定,當然你可以運用一個過濾器來統一解決。
知道了原因,再去解決亂碼一般就不會有什麼問題了。
至於有些提出使用前臺encodeURI(encodeURI(str))方式去做的,後臺手動解碼,其實還是由於兩端編碼和解碼不一致造成的,完全不需要這麼使用。