本章解決在AJAX中常見的中文問題,分析中文亂碼產生的緣由,以及如何解決亂碼問題javascript
1. HTTP協議的編碼規定html
在HTTP協議中,瀏覽器不能向服務器直接傳遞某些特殊字符,必須是這些字符進行URL編碼後再進行傳送。url編碼遵循的規則:java
將空格轉換爲(+)web
對0-9,a-z,A-Z之間的字符保持不變。ajax
對於全部其餘的字符,用這個字符的當前字符集編碼在內存中的十六進制格式表示,並在每一個十六進制字節前加上一個百分號%。例如,字符「+」用%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」,那麼被轉換後的結果就app
是」%E5%BC%A0%E4%B8%89」,注意這個轉換結果與網頁的編碼沒有任何關係,由於這兩個函數老是拿到字符所對應的UTF-8碼,而後再進行URL編碼的。也就是說無論網頁是GBK的編碼仍是UTF-8的編碼,轉換的結果都同樣。函數
因此若是咱們發送給服務器的請求包含有中文或者其它比較特殊的字符如空格「+」等符號的時候,就就須要使用者兩個函數對字符進行URL編碼。post
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便可。