1. 程式人生 > >深入剖析tomcat容器的亂碼問題

深入剖析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))方式去做的,後臺手動解碼,其實還是由於兩端編碼和解碼不一致造成的,完全不需要這麼使用。