開(kāi)始之前: 首先一個(gè)問(wèn)題就是通常 XMLHttpRequest 默認(rèn)的編碼都是UTF-8的, 所以我們建議所有頁(yè)面, 客戶端和服務(wù)器端都使用 UTF-8 作為編碼.
1. base64 encode 和 decode
??? 這個(gè)方案依賴于 JavaScript 實(shí)現(xiàn)的 base64 編碼/解碼方法, 在客戶端發(fā)送參數(shù)的時(shí)候用 base64 進(jìn)行編碼, 服務(wù)器端通過(guò) base64 進(jìn)行解碼后還原出原來(lái)的字符, 這個(gè)解決方案可以滿足需要, 但是有個(gè)問(wèn)題就是一是增加了客戶端代碼量, 還有個(gè)大問(wèn)題就是編碼后的內(nèi)容比原始內(nèi)容會(huì)大很多, 另外如果找到的 base64 JS 算法不夠標(biāo)準(zhǔn)的話, 服務(wù)器端就無(wú)法還原原來(lái)的值了. 現(xiàn)在網(wǎng)上有很多種 base64 的 JS 實(shí)現(xiàn)代碼, 例如如下的一個(gè)算法實(shí)現(xiàn):
<HTML>
<HEAD>
<TITLE>Base64</TITLE>
<script language=javascript>
var base64EncodeChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
var base64DecodeChars = new Array(
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
-1, 0, 1, 2, 3,? 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1);
function base64encode(str) {
var out, i, len;
var c1, c2, c3;
len = str.length;
i = 0;
out = "";
while (i < len) {
?c1 = str.charCodeAt(i++) & 0xff;
?if(i == len)
?{
out += base64EncodeChars.charAt(c1 >> 2);
out += base64EncodeChars.charAt((c1 & 0x3) << 4);
out += "==";
break;
?}
?c2 = str.charCodeAt(i++);
?if(i == len)
?{
out += base64EncodeChars.charAt(c1 >> 2);
out += base64EncodeChars.charAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4));
out += base64EncodeChars.charAt((c2 & 0xF) << 2);
out += "=";
break;
?}
?c3 = str.charCodeAt(i++);
?out += base64EncodeChars.charAt(c1 >> 2);
?out += base64EncodeChars.charAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4));
?out += base64EncodeChars.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >>6));
?out += base64EncodeChars.charAt(c3 & 0x3F);
}
return out;
}
function base64decode(str) {
var c1, c2, c3, c4;
var i, len, out;
len = str.length;
i = 0;
out = "";
while (i < len) {
?/* c1 */
?do {
c1 = base64DecodeChars[str.charCodeAt(i++) & 0xff];
?} while(i < len && c1 == -1);
?if(c1 == -1)
break;
?/* c2 */
?do {
c2 = base64DecodeChars[str.charCodeAt(i++) & 0xff];
?} while(i < len && c2 == -1);
?if(c2 == -1)
break;
?out += String.fromCharCode((c1 << 2) | ((c2 & 0x30) >> 4));
?/* c3 */
?do {
c3 = str.charCodeAt(i++) & 0xff;
if(c3 == 61)
return out;
c3 = base64DecodeChars[c3];
?} while(i < len && c3 == -1);
?if(c3 == -1)
break;
?out += String.fromCharCode(((c2 & 0XF) << 4) | ((c3 & 0x3C) >> 2));
?/* c4 */
?do {
c4 = str.charCodeAt(i++) & 0xff;
if(c4 == 61)
return out;
c4 = base64DecodeChars[c4];
?} while(i < len && c4 == -1);
?if(c4 == -1)
break;
?out += String.fromCharCode(((c3 & 0x03) << 6) | c4);
}
return out;
}
function utf16to8(str) {
var out, i, len, c;
out = "";
len = str.length;
for(i = 0; i < len; i++) {
?c = str.charCodeAt(i);
?if ((c >= 0x0001) && (c <= 0x007F)) {
out += str.charAt(i);
?} else if (c > 0x07FF) {
out += String.fromCharCode(0xE0 | ((c >> 12) & 0x0F));
out += String.fromCharCode(0x80 | ((c >> 6) & 0x3F));
out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F));
?} else {
out += String.fromCharCode(0xC0 | ((c >> 6) & 0x1F));
out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F));
?}
}
return out;
}
function utf8to16(str) {
var out, i, len, c;
var char2, char3;
out = "";
len = str.length;
i = 0;
while (i < len) {
?c = str.charCodeAt(i++);
?switch(c >> 4)
?{
case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
// 0xxxxxxx
out += str.charAt(i-1);
break;
case 12: case 13:
// 110x xxxx 10xx xxxx
char2 = str.charCodeAt(i++);
out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));
break;
case 14:
// 1110 xxxx 10xx xxxx 10xx xxxx
char2 = str.charCodeAt(i++);
char3 = str.charCodeAt(i++);
out += String.fromCharCode(((c & 0x0F) << 12) |
((char2 & 0x3F) << 6) |
((char3 & 0x3F) << 0));
break;
?}
}
return out;
}
function doit() {
var f = document.f
f.output.value = base64encode(utf16to8(f.source.value))
f.decode.value = utf8to16(base64decode(f.output.value))
}
</script>
</HEAD>
<BODY>
<H1>Base64</H1>
<FORM NAME="f">
原碼& lt;BR>
<TEXTAREA NAME="source" ROWS=4 COLS=60 WRAP="soft"></TEXTAREA><BR><BR>
Base64 encode<BR>
<TEXTAREA NAME="output" ROWS=4 COLS=60 WRAP="soft"></TEXTAREA><BR><BR>
Base64 decode<BR>
<TEXTAREA NAME="decode" ROWS=4 COLS=60 WRAP="soft"></TEXTAREA><BR><BR>
<INPUT TYPE=BUTTON VALUE="轉(zhuǎn)換" ONCLICK="doit()">
</FORM>
</BODY>
在每個(gè)表單值被提交之前調(diào)用 base64encode, 然后在服務(wù)器端調(diào)用 base64 解碼器即可.
在 JSP 中可以通過(guò)這樣做來(lái)實(shí)現(xiàn):
??? ??? sun.misc.BASE64Decoder base64decoder = new sun.misc.BASE64Decoder();
??? ??? byte[] data =?base64decoder.decodeBuffer(request.getParameter("input"));
??? ??? String result = new String(data, "UTF-8");// 注意建議這里制定字符集來(lái)歡迎到原來(lái)的字符串
在這種情況下服務(wù)器端返回的字符也可以通過(guò)先 base64 編碼的方式傳遞到客戶端, 客戶端之后調(diào)用 JS 形式的解碼器即可還原到原來(lái)的字符串. 服務(wù)器端可以使用 sun.misc.BASE64Encoder (不要用 java.netURLEncoder).
2.使用 JS 自帶的 escape() & encodeURI() & encodeURIComponent()
escape() & encodeURI() & encodeURIComponent()這三個(gè)函數(shù)都可以用來(lái)對(duì)URI進(jìn)行encode或過(guò)濾特殊字符(#/$&+=?/等)。我的經(jīng)驗(yàn)是最好用encodeURIComponent()(需要IE 5.5以上,F(xiàn)ireFox當(dāng)然沒(méi)問(wèn)題),因?yàn)閷?duì)UTF-8支持比較好,不會(huì)遇到中文亂碼問(wèn)題,否則還需要進(jìn)行編碼轉(zhuǎn)換,很麻煩的。使用其它兩個(gè)函數(shù)都會(huì)發(fā)生丟失特殊字符的問(wèn)題,例如空格變+號(hào)或者空格,引號(hào),&=?等丟失的問(wèn)題, 至少使用 JSP 作為服務(wù)器端的話會(huì)發(fā)生這種情況, 有興趣的朋友可以將本文最后的例子代碼中的編碼部分修改后做個(gè)測(cè)試.
下面是MSDN上對(duì)這三個(gè)函數(shù)的解釋:
escape(charString)
The escape method returns a string value (in Unicode format) that contains the contents of charstring. All spaces, punctuation, accented characters, and any other non-ASCII characters are replaced with %xx encoding, where xx is equivalent to the hexadecimal number representing the character. For example, a space is returned as "%20."
Characters with a value greater than 255 are stored using the %uxxxx format.
Note?? The escape method should not be used to encode Uniform Resource Identifiers (URI). Use encodeURI and encodeURIComponent methods instead.
http://msdn.microsoft.com/library/en-us/script56/html/js56jsmthescape.asp
?encodeURI(URIString)
The encodeURI method returns an encoded URI. If you pass the result to decodeURI, the original string is returned. The encodeURI method does not encode the following characters: ":", "/", ";", and "?". Use encodeURIComponent to encode these characters.
http://msdn.microsoft.com/library/en-us/script56/html/js56jsmthfencodeuri.asp
encodeURIComponent(encodedURIString)
3. 使用一個(gè)簡(jiǎn)便的客戶端數(shù)據(jù)解析方案The encodeURIComponent method returns an encoded URI. If you pass the result to decodeURIComponent, the original string is returned. Because the encodeURIComponent method encodes all characters, be careful if the string represents a path such as /folder1/folder2/default.html. The slash characters will be encoded and will not be valid if sent as a request to a web server. Use the encodeURI method if the string contains more than a single URI component.
http://msdn.microsoft.com/library/en-us/script56/html/js56jsmthencodeuricomponent.asp
最偷懶的辦法就是返回一段 HTML 顯示出來(lái)了. 至于如果是想帶一些數(shù)據(jù), 解析處理的話, 方案很多, 利用 XML 啊, JSON 啊什么的不一而足. 我這里呢就給出一個(gè)相當(dāng)簡(jiǎn)便的方案: 使用 JS 內(nèi)置的 eval 方法來(lái)解析. 這個(gè)方案是在幫助一個(gè)同事想最快的已最短的代碼解析返回的對(duì)象的多個(gè)變量的時(shí)候提出的.
服務(wù)器端返回一個(gè)字符串:
var _dataObject = {
?? username : "beansoft",
??????? age : 24
};
客戶端在得到這個(gè)字符串后可以通過(guò)下面一段代碼搞定:
var responseText = xmlhttp.responseText;
eval(responseText);
alert("_dataObject.username=" + _dataObject.username);
好了, 解析出來(lái)了!
如果要傳遞多個(gè)變量呢, 就用 var _dataObject1, var _dataObject2...這樣就可以了, 客戶端就依次是 _dataObject1.username, _dataObject2.username...
等等: 我的變量里寫了特殊字符怎么辦? 例如我用的字符串是 'abc"'', 這時(shí)候我不得不拋出殺手锏了, 這就是用 Java 實(shí)現(xiàn)的 escape(), unescape() 方法, 其實(shí)本例中只需要 escape() 的 Java 版本就可以了(這個(gè)方案也幫助另一個(gè)同事解決了從JSP端傳遞的變量含有'號(hào)結(jié)果導(dǎo)致客戶端沒(méi)法顯示的問(wèn)題):
??? public static String escape(String src) {
??? ??? int i;
??? ??? char j;
??? ??? StringBuffer tmp = new StringBuffer();
??? ??? tmp.ensureCapacity(src.length() * 6);
??? ??? for (i = 0; i < src.length(); i++) {
??? ??? ??? j = src.charAt(i);
??? ??? ??? if (Character.isDigit(j) || Character.isLowerCase(j)
??? ??? ??? ??? ??? || Character.isUpperCase(j))
??? ??? ??? ??? tmp.append(j);
??? ??? ??? else if (j < 256) {
??? ??? ??? ??? tmp.append("%");
??? ??? ??? ??? if (j < 16)
??? ??? ??? ??? ??? tmp.append("0");
??? ??? ??? ??? tmp.append(Integer.toString(j, 16));
??? ??? ??? } else {
??? ??? ??? ??? tmp.append("%u");
??? ??? ??? ??? tmp.append(Integer.toString(j, 16));
??? ??? ??? }
??? ??? }
??? ??? return tmp.toString();
??? }
??? public static String unescape(String src) {
??? ??? StringBuffer tmp = new StringBuffer();
??? ??? tmp.ensureCapacity(src.length());
??? ??? int lastPos = 0, pos = 0;
??? ??? char ch;
??? ??? while (lastPos < src.length()) {
??? ??? ??? pos = src.indexOf("%", lastPos);
??? ??? ??? if (pos == lastPos) {
??? ??? ??? ??? if (src.charAt(pos + 1) == 'u') {
??? ??? ??? ??? ??? ch = (char) Integer.parseInt(src
??? ??? ??? ??? ??? ??? ??? .substring(pos + 2, pos + 6), 16);
??? ??? ??? ??? ??? tmp.append(ch);
??? ??? ??? ??? ??? lastPos = pos + 6;
??? ??? ??? ??? } else {
??? ??? ??? ??? ??? ch = (char) Integer.parseInt(src
??? ??? ??? ??? ??? ??? ??? .substring(pos + 1, pos + 3), 16);
??? ??? ??? ??? ??? tmp.append(ch);
??? ??? ??? ??? ??? lastPos = pos + 3;
??? ??? ??? ??? }
??? ??? ??? } else {
??? ??? ??? ??? if (pos == -1) {
??? ??? ??? ??? ??? tmp.append(src.substring(lastPos));
??? ??? ??? ??? ??? lastPos = src.length();
??? ??? ??? ??? } else {
??? ??? ??? ??? ??? tmp.append(src.substring(lastPos, pos));
??? ??? ??? ??? ??? lastPos = pos;
??? ??? ??? ??? }
??? ??? ??? }
??? ??? }
??? ??? return tmp.toString();
??? }
這樣, 在服務(wù)器端的時(shí)候可以變成:
<%
String username = "'abc\"''";// 其實(shí)這個(gè)普通子串轉(zhuǎn)換成 Java 語(yǔ)言中的字符串也有工具可以用的, 例如本人開(kāi)發(fā)的 Native2JavaString, 改日再講.
%>
var _dataObject = {
?? username : "<%=escape(username)%>",
??????? age : 24
};
客戶端呢, 就可以簡(jiǎn)單的來(lái)JS自帶的 unescape() 函數(shù)來(lái)取出原來(lái)的字符串:
var responseText = xmlhttp.responseText;
eval(responseText);
alert("_dataObject.username=" + unescape(_dataObject.username));
就是服務(wù)器端用 Java 寫的 escape(), 客戶端呢就用 JS 自帶的 unescape().
4.?實(shí)例代碼
好了, 說(shuō)了這么多, 就推出個(gè)人的解決方案吧. 簡(jiǎn)單的講就是我寫了一個(gè)腳本對(duì)象 AjaxFormer, 使用的是 escape來(lái)自動(dòng)的將原來(lái)的 POST/GET 方式的提交代碼自動(dòng)的轉(zhuǎn)換成 AJAX 的方式.
/**
?* @constructor
?* This is a ajax form helper class.
?*
?* @param form - the document form
?* @param resultDivId - the result div id
?*/
function AjaxFormer (form, resultDivId);
構(gòu)造器的第一個(gè)參數(shù)是個(gè) form 對(duì)象, 第二個(gè)是個(gè)可選的結(jié)果 DIV 對(duì)象, 也就是說(shuō)你可以指定服務(wù)器端返回的 HTML 代碼顯示的地方, 如果保持為空的話, 那么返回的 HTML?會(huì)被附加到文檔的末尾. 本對(duì)象有一個(gè)名字為 ajaxSubmitForm() 的方法來(lái)自動(dòng)的遍歷所有表單元素, 然后將結(jié)果拼成一個(gè)字符串, 最后根據(jù)原來(lái)的表單的提交方式(get/post)來(lái)自動(dòng)再客戶端用 AJAX 模擬提交這個(gè)表單到原來(lái)的表單的 action 屬性所指定的頁(yè)面中去.
用法示例:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>AJAX Form Submit Test</title>
<script src='ajax_common.js'></script>
</head>
<body>
<h3>AJAX Form Submit Test</h3>
Fill the form and then click submit
<form method="POST" id="form1" name="form1"
action="form_action.jsp"
onSubmit="former.ajaxSubmitForm();return false;">
??? <p><input type="hidden" name="hidden1" value="hiddenValue">
??? text:<input type="text" name="textf&1" size="20" value="text1">
??? checkbox:<input type="checkbox" name="checkbox1" value="ON" checked>
??? radio:<input type="radio" value="V1" checked name="radio1">
??? select:<select size="1" name="select1">
??? <option selected value="option1">D1</option>
??? </select>
??? <br>
??? <br>
??? <input type="submit" name="B1" value="submit">
??? <input type="reset" name="B2" value="reset">
??? </p>
</form>
<script type="text/javascript">
var former = new AjaxFormer($('form1'));
</script>
</body>
</html>
紅色的字體就是您從一個(gè)非 AJAX 的表單提交改變成一個(gè) AJAX 的表單提交所需要做的工作, 看上去夠簡(jiǎn)單吧?
運(yùn)行時(shí)的效果如圖所示:

下載本文的源碼:?
將源碼解壓縮到JSP服務(wù)器的任意目錄下即可, 例如 $TOMCAT_HOME\webapps\ROOT 下, 然后在瀏覽器里鍵入:
http://localhost:8080/ajax_form_submit.htm
作者: beansoft@126.com?2006.12.25