本章解決在AJAX中常見的中文問題,分析中文亂碼產生的原因,以及如何解決亂碼問題
1. HTTP協議的編碼規定
在HTTP協議中,瀏覽器不能向服務器直接傳遞某些特殊字符,必須是這些字符進行URL編碼后再進行傳送。url編碼遵循的規則:
將空格轉換為(+)
對0-9,a-z,A-Z之間的字符保持不變。
對於所有其他的字符,用這個字符的當前字符集編碼在內存中的十六進制格式表示,並在每個十六進制字節前加上一個百分號%。例如,字符“+”用%2B表示,字符“=”用%3D表示,字符“&”用%26表示,字符“國”用%B9%FA表示注意,同一個中文字符在不同的字符集編碼方式下,在內存中的編碼值也是不同的,一個字符的URL編碼是針對字符在內存中的碼值而言的,采用不同編碼的同一個字符的URL編碼結果是不同的。
2. encodeURI()與encodeURIComponent()函數
javaScript中提供了兩個函數來對字符進行URL編碼:encodeURI()與encodeURIComponent(),兩者的區別在於,encodeURI函數不會對以下的字符進行處理: “! @ # $ & * ( ) = : / ; ? + ' ”,而encodeURIComponent函數會對更多的字符進行處理比如 URI的組成部分 “/” 就會被encodeURIComponent進行處理。這兩個方法對傳遞的值進行URL編碼,過程是先找到字符所對應的UTF-8編碼,比如“張三”兩個字的UTF-8編碼是”0xE5BCA0E4B889”(前面的是零x,表示是16進制編碼).“張”是”0xE5BCA0”,”三”是”0xE4B889”,那么被轉換后的結果就
是”%E5%BC%A0%E4%B8%89”,注意這個轉換結果與網頁的編碼沒有任何關系,因為這兩個函數總是拿到字符所對應的UTF-8碼,然后再進行URL編碼的。也就是說不管網頁是GBK的編碼還是UTF-8的編碼,轉換的結果都一樣。
所以如果我們發送給服務器的請求包含有中文或者其它比較特殊的字符如空格“+”等符號的時候,就就需要使用者兩個函數對字符進行URL編碼。
3. 封裝Ajax請求代碼,供后面使用。
新建一個web項目,在web項目中添加一個ajax.js文件,內容包含兩個函數如下:
createXmlHttp()
function createXmlHttp() {
if (window.XMLHttpRequest) {
//alert("非IE瀏覽器");
return new XMLHttpRequest();
} else if (window.ActiveXObject && !window.XMLHttpRequest){
var aVersion = ["MSXML2.XMLHttp.6.0",
"MSXML2.XMLHttp.5.0", "MSXML2.XMLHttp.4.0",
"MSXML2.XMLHttp.3.0", "MSXML2.XMLHttp",
"Microsoft.XMLHttp"];
for (var i = 0; i < aVersion.length; i++) {
try {
var oXmlHttp = new ActiveXObject(aVersion[i]);
//alert("IE瀏覽器版本"+aVersion[i]);
return oXmlHttp;
}
catch (ex) {}
}
}
throw new Error("創建XMLHttpRequest對象出錯!");
}
doGet(url,callBack)函數,該函數有兩個參數,將來要發送AjAX GET請求可以直接調用該方法。 第一個參數表示要發送的請求的URL地址,第二個是回調函數,回調函數需要處理從服務端返回的數據。
/**
* @param url 請求的URL地址
* @param callBack 回調函數
* @return
*/
function doGet(url,callBack){
var request=createXmlHttp();
request.onreadystatechange=function(){
if(request.readyState==4 && request.status==200){
//注意我們定義回調函數的時候要多加一個參數接收返回的數據
callBack(request.responseText);
}
};
request.open("GET",url);
request.send(null);
}
4. 編寫頁面,該頁面使用的字符集是UTF-8編碼:
HTML部分:
<body>
<h3>驗證用戶名是否存在</h3>
輸入用戶名:<input type="text" id="userName" /> <span id="warning"></span><br />
<input type="button" value="驗證" onclick="checkUserName('userName')" />
</body>
JavaScript部分:
首先引入ajax.js文件,然后編寫當按鈕點擊的時候的要執行的代碼:
<script type="text/javascript" src="ajax.js"></script>
<script type="text/javascript">
function checkUserName(tagID){
//獲取文本框中輸入的值
var userName=document.getElementById(tagID).value;
//對中文進行URL編碼
①var url="ajax.do?"+encodeURI("userName="+userName);
//data是從服務端返回來的數據
doGet(url,function(data){
document.getElementById("warning").innerHTML=data;
});
}
</script>
頁面效果:
當在文本框中輸入“張三”后,點擊驗證后,javaScript代碼執行到 ① 之后,url的值就變成了 “ajax.do?userName=%E5%BC%A0%E4%B8%89”,可以通過firefox瀏覽器的firebug插件進行斷點調試,得到發送的url的值。
這里為什么沒有使用encodeURIComponent()函數呢?這是因為encodeURIComponent函數會將”=”變成“%3D”,“?”變成” %3F”, 如果有多個參數的話會用到“&”符號,同樣也會被轉換,而這些字符不用轉換也可以提交,所以這里使用了encodeURI,這個函數不會對”?”,”=”,”&”進行轉換。后面的“%E5%BC%A0%E4%B8%89”就是“張三”兩個漢字按照UTF-8字符集進行URL編碼之后的結果
5. 在服務端取得發送過來的數據
編寫一個Servlet,這個Servlet的映射是 /ajax.do,其中的doGet方法如下:
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//告訴客戶端響應的信息的編碼格式是UTF-8
response.setContentType("text/html;charset=UTF-8");
②String userName=request.getParameter("userName");
PrintWriter out=response.getWriter();
out.print("您要驗證的用戶名是:"+userName+",該用戶名可以使用");
}
我們在②處放置一個斷點,然后以斷點的方式啟動Tomcat,提交后程序進入斷點處我們發現取得的userName的值是: “??????”,為什么會是亂碼?
我們分析一下,客戶端Ajax想服務器發送的請求是
”ajax.do?userName=%E5%BC%A0%E4%B8%89”,那么服務器上的
request.getParameter()方法在取參數值的時候,首先要進行URL解碼(其實就是去掉字符當中的“%“),解碼之后將只剩下的字節部分按照Tomcat在內部默認的ISO-8859-1字符集的方式轉換成字符串,於是亂碼開始在這里出現 了。因為發送過來的字節在去掉%后剩下的字節應該按照UTF-8轉換字符串才對,但是卻采用了ISO-8859-1,於是亂碼產生了。
那么知道原因之后,解決起來就很容易了。既然是按照ISO-8859-1轉換得到的字符串,那我們就得到這個字符串還原為ISO-8859-1的字節,然后再將字節按照正確的UTF-8轉換為字符串,這樣就得到了正確的字符了,修改Servlet中的代碼如下:
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//告訴客戶端響應的信息的編碼格式是UTF-8
response.setContentType("text/html;charset=UTF-8");
System.out.println("進入Servlet");
String userName=request.getParameter("userName");
userName=new String(userName.getBytes("iso-8859-1"),"UTF-8");
System.out.println(userName);
PrintWriter out=response.getWriter();
out.print("您要驗證的用戶名是:"+userName+",該用戶名可以使用");
}
客戶端響應為:
6. 試一試將提交方式改成POST方式
在ajax.js文件中添加一個函數,該函數專門用於提交POST請求
/**
*
* @param url 要提交的URL
* @param submitData 要提交的數據
* @param callBack 回調函數
* @return
*/
function doPost(url,submitData,callBack){
var request=createXmlHttp();
request.onreadystatechange=function(){
if(request.readyState==4 && request.status==200){
//注意我們定義回調函數的時候要多加一個參數接收返回的數據
callBack(request.responseText);
}
};
request.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
request.open("POST",url);
request.send(submitData);
}
修改頁面上的javaScript代碼:
<script type="text/javascript" src="ajax.js"></script>
<script type="text/javascript">
function checkUserName(tagID){
//獲取文本框中輸入的值
var userName=document.getElementById(tagID).value;
//data是從服務端返回來的數據
doPost("ajax.do","userName="+userName,function(data){
document.getElementById("warning").innerHTML=data;
});
}
</script>
當我們發送post請求的時候,盡管我們為請求頭設置了
application/x-www-form-urlencoded,但是發送的數據並沒有進行URL編碼,而傳統的將form表單的提交方式設置成post,在提交的時候會自動進行URL編碼。
所以Ajax中的post請求時將數據原封不動的傳遞到了服務器上,所以只需要調用reqeust.setCharacterEncoding() 設置正確的編碼集后,就可以取出數據了。
7. 最佳解決方案
前面的方式我們雖然分別解決了GET方式和POST方式的中文問題,但是需要分開進行處理,並且對於不同的服務器,默認的編碼集是不同的,這樣對於GET方式我們進行的手工轉碼就不能通用了。
那么不管是Get請求還是POST,有沒有可以統一的解決方案?我們可以做如下的處理:
將提交的數據使用javaScript的encodeURI()進行兩次URL編碼
服務端進行一次URL 解碼即可
這種方式的優點是與客戶端網頁的編碼集無關,與服務器的默認編碼集無關,而且能夠兼容幾乎所有的瀏覽器。
下面以GET方式為例來理解分析全過程:
修改javaScript代碼為:
<script type="text/javascript" src="ajax.js"></script>
<script type="text/javascript">
function checkUserName(tagID){
//獲取文本框中輸入的值
var userName=document.getElementById(tagID).value;
//data是從服務端返回來的數據
var url="ajax.do? userName="+encodeURI(encodeURI(userName));
doGet(,function(data){
document.getElementById("warning").innerHTML=data;
});
}
</script>
Servlet代碼修改為:
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//告訴客戶端響應的信息的編碼格式是UTF-8
response.setContentType("text/html;charset=UTF-8");
String userName=request.getParameter("userName");
userName=URLDecoder.decode(userName,"UTF-8");
System.out.println(userName);
PrintWriter out=response.getWriter();
out.print("您要驗證的用戶名是:"+userName+",該用戶名可以使用");
}
運行后,在各種瀏覽器中都沒有出現亂碼問題。換成POST方式,也沒有出現亂碼問題。頁面如果換成GBK編碼,也沒有出現亂碼問題.
為什么這種方式沒有出現問題,為什么要進行兩次 encodeURI?我們只需要跟蹤一下提交的數據即可:
假如我們提交的是 “張三”:
①我們第一次進行encodeURI之后的結果為:
%E6%9D%8E%E5%9B%9B
②第二次進行encodeURI之后的結果為:
%25E6%259D%258E%25E5%259B%259B
③我們對比一下兩個值,發現第一次URL編碼后中間有%,而第二次URL編碼后將第一次編碼結果中的%替換成了%25,所以最終發送的數據為:
ajax.do?userName=%25E6%259D%258E%25E5%259B%259B
④在服務端的Servlet中,我們通過調用request.getParameter(“userName”)取值的時候,getParameter方法會對%25E6%259D%258E%25E5%259B%259B進行URL解碼,解碼后的結果為%E6%9D%8E%E5%9B%9B,也就是將%25換成了%,那么此時Tomcat服務器按照默認的iso-8859-1轉換的字符串的時候根本就沒有做任何變換,還是%E6%9D%8E%E5%9B%9B
⑤當我們再次進行URL解碼的時候即: URLDecoder.decode(userName,"UTF-8"),此時去掉其中的%后變成了E69D8EE59B9B,這正好是”張三”的UTF-8編碼,所以使用UTF-8碼轉換成字符串“張三“.
從整個過程看來,這種方式的優勢在於與頁面的編碼無關,也與服務器所使用的編碼集無關。我們需要做的只需要將提交的數據(不管是POST的數據還是GET的數據),進行兩次encodeURI即可。