JSP 中 AJAX 的表单提交中文问题的简单解决方案

 

首先必须要介绍一下 HTTP 协议和 GET, POST 的工作方式.

当用户在Web浏览器地址栏中输入一个带有http://前缀的URL并按下Enter后,或者在Web页面中某个以http://开头的超链接上单击鼠标,HTTP事务处理的第一个阶段--建立连接阶段就开始了.HTTP的默认端口是80.
随着连接的建立,HTTP就进入了客户向服务器发送请求的阶段.客户向服务器发送的请求是一个有特定格式的ASCII消息,其语法规则为:

6KB
< Method > < URL > < HTTP Version > </n>
{ <Header>:<Value> </n>}*
</n>
{ Entity Body }

请求消息的顶端是请求行,用于指定方法,URL和HTTP协议的版本,请求行的最后是回车换行.方法有GET,POST,HEAD,PUT,DELETE等.
在请求行之后是若干个报头(Header)行.每个报头行都是由一个报头和一个取值构成的二元对,报头和取值之间以":"分隔;报头行的最后是回车换行. 常见的报头有Accept(指定MIME媒体类型),Accept_Charset(响应消息的编码方式),Accept_Encoding(响应消息的字符集),User_Agent(用户的浏览器信息)等.
在请求消息的报头行之后是一个回车换行,表明请求消息的报头部分结束.在这个/n之后是请求消息的消息实体(Entity Body).
Web服务器在收到客户请求并作出处理之后,要向客户发送应答消息.与请求消息一样,应答消息的语法规则为:

 

< HTTP Version> <Status Code> [<Message>]</n>
{ <Header>:<Value> </n> } *
</n>
{ Entity Body }

应答消息的第一行为状态行,其中包括了HTTP版本号,状态码和对状态码进行简短解释的消息;状态行的最后是回车换行.状态码由3位数字组成,有5类:

  • 1XX 保留
  • 2XX 表示成功
  • 3XX 表示URL已经被移走
  • 4XX 表示客户错误
  • 5XX 表示服务器错误

例如:415,表示不支持改媒体类型;503,表示服务器不能访问.最常见的是200,表示成功.常见的报头有:Last_Modified(最后修改时间),Content_Type(消息内容的MIME类型),Content_Length(内容长度)等.
在报头行之后也是一个回车换行,用以表示应答消息的报头部分的结束,以及应答消息实体的开始.
下面是一个应答消息的例子:

HTTP/1.0 200 OK
Date: Moday,07-Apr-97 21:13:02 GMT
Server:NCSA/1.1
MIME_Version:1.0
Content_Type:text/html
Last_Modified:Thu Dec 5 09:28:01 1996
Coentent_Length:3107

<HTML><HEAD><TITLE>...</HTML>

那么 GET 和 POST 有什么区别? 区别就是一个在 URL 请求里面附带了表单参数和值, 一个是在 HTTP 请求的消息实体中. 用下面的例子可以很容易的看到同样的数据通过GET和POST来发送的区别, 发送的数据是 username=张三 :

 GET 方式, 浏览器键入 http://localhost?username=张三

GET /?username=%E5%BC%A0%E4%B8%89 HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-powerpoint, application/vnd.ms-excel, application/msword, */*
Accept-Language: zh-cn
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.1.4322)
Host: localhost
Connection: Keep-Alive

POST 方式:

POST / HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-powerpoint, application/vnd.ms-excel, application/msword, */*
Accept-Language: zh-cn
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.1.4322)
Host: localhost
Content-Length: 28
Connection: Keep-Alive

username=%E5%BC%A0%E4%B8%89

比较一下上面的两段文字, 您会发现 GET 方式把表单内容放在前面的请求头中, 而 POST 则把这些内容放在请求的主体中了, 同时 POST 中把请求的 Content-Type 头设置为 application/x-www-form-urlencoded. 而发送的正文都是一样的, 可以这样来构造一个表单提交正文:

encodeURIComponent(arg1)=encodeURIComponent(value1)&encodeURIComponent(arg2)=encodeURIComponent(value2)&.....

注: encodeURIComponent 返回一个包含了 charstring 内容的新的 String 对象(Unicode 格式), 所有空格、标点、重音符号以及其他非 ASCII 字符都用 %xx 编码代替,其中 xx 等于表示该字符的十六进制数。 例如,空格返回的是 "%20" 。 字符的值大于 255 的用 %uxxxx 格式存储。参见 JavaScript 的 encodeURIComponent() 方法.

下面就讨论一下如何在 JavaScript 中执行一个 GET 或者 POST 请求. 如果您用过 Java, 那么您可能熟悉下列的用 java.net.URLConnection 类进行 POST 操作的代码(参考 Java Tip 34: POSTing via Java ):
 

URL url;
URLConnection urlConn;
DataOutputStream printout;
// URL of CGI-Bin or jsp, asp script.
url = new URL ("somepage");
// URL connection channel.
urlConn = url.openConnection();
// ......
// No caching, we want the real thing.
urlConn.setUseCaches (false);
// Specify the content type.
urlConn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
// Send POST output.
printout = new DataOutputStream (urlConn.getOutputStream ());
String content = "name=" + URLEncoder.encode ("Buford Early") + "&email=" + URLEncoder.encode ("buford@known-space.com");
printout.writeBytes (content);
printout.flush ();
printout.close ();

以上的代码向 somepage 发送了一次 POST 请求, 数据为 name = Buford Early, email = buford@known-space.com.
JavaScript 来执行 POST/GET 请求是同样的原理, 下面的代码展示了分别用 XMLHttpRequest 对象向 somepage 用 GET 和 POST 两种方式发送和上例相同的数据的具体过程:
GET 方式

var postContent =
"name=" + encodeURIComponent("Buford Early") + "&email=" + encodeURIComponent("buford@known-space.com");
xmlhttp.open("GET", "somepage" + "?" + postContent, true);
xmlhttp.send(null);

POST 方式

var postContent =
"name=" + encodeURIComponent("Buford Early") + "&email=" + encodeURIComponent("buford@known-space.com");
xmlhttp.open("POST", "somepage", true);
xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xmlhttp.send(postContent);

至此希望你已经能够理解如何用 JavaScript 中的 XMLHttpRequest 对象来执行 GET/POST 操作, 剩下的工作就是您如何来构造这些提交的参数了, 最后我给出一个将现有的 form 提交代码修改为异步的 AJAX 提交的代码(注意目前作者还不知道如何让 file 上传表单域也能异步上传文件). 首先请看两个 JavaScript 函数:

// form - the form to submit
// resultDivId - the division of which to display result text in, in null, then
// create an element and add it to the end of the body
function ajaxSubmitForm(form, resultDivId) {
var elements = form.elements;
// Enumeration the form elements
var element;
var i;

var postContent = "";
// Form contents need to submit

for(i=0;i<elements.length;++i) {
var element=elements[i];

if(element.type=="text" || element.type=="textarea" || element.type=="hidden") {
postContent += encodeURIComponent(element.name) + "=" + encodeURIComponent(element.value) + "&";
}
else if(element.type=="select-one"||element.type=="select-multiple") {
var options=element.options,j,item;
for(j=0;j<options.length;++j){
item=options[j];
if(item.selected) {
postContent += encodeURIComponent(element.name) + "=" + encodeURIComponent(item.value) + "&";
}
}
} else if(element.type=="checkbox"||element.type=="radio") {
if(element.checked) {
postContent += encodeURIComponent(element.name) + "=" + encodeURIComponent(element.value) + "&";
}
} else if(element.type=="file") {
if(element.value != "") {
postContent += encodeURIComponent(element.name) + "=" + encodeURIComponent(element.value) + "&";
}
} else {
postContent += encodeURIComponent(element.name) + "=" + encodeURIComponent(element.value) + "&";
}
}

alert(postContent);

ajaxSubmit(form.action, form.method, postContent);
}

// url - the url to do submit
// method - "get" or "post"
// postContent - the string with values to be submited
// resultDivId - the division of which to display result text in, in null, then
// create an element and add it to the end of the body
function ajaxSubmit(url, method, postContent, resultDivId)
{
var loadingDiv = document.getElementById('loading');
// call in new thread to allow ui to update
window.setTimeout(function () {
loadingDiv.innerText = "Loading....";
loadingDiv.style.display = "";
}, 1);

// code for Mozilla, etc.
if (window.XMLHttpRequest)
{
xmlhttp=new XMLHttpRequest();
}
// code for IE
else if (window.ActiveXObject)
{
xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
}

if(xmlhttp) {
xmlhttp.onreadystatechange = function() {
// if xmlhttp shows "loaded"
if (xmlhttp.readyState==4)
{
if(resultDivId) {
document.getElementByID(resultDivId).innerHTML = xmlhttp.responseText;
} else {
var result = document.createElement("DIV");
result.style.border="1px solid #363636";
result.innerHTML = xmlhttp.responseText;
document.body.appendChild(result);
}

loadingDiv.innerHTML =
"Submit finnished!";
}

};

if(method.toLowerCase() == "get") {
xmlhttp.open("GET", url + "?" + postContent, true);
xmlhttp.send(null);
} else if(method.toLowerCase() == "post") {
xmlhttp.open("POST", url, true);
xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xmlhttp.send(postContent);
}
} else {
loadingDiv.innerHTML =
"Can't create XMLHttpRequest object, please check your web browser.";
}

}

函数 ajaxSubmitForm 将表单要提交的内容进行封装, 然后调用 ajaxSubmit 函数来执行真正的异步提交, 表单提交后所返回的结果则显示在给定的 DIV 容器中或者没有指定参数时用 DOM 对象动态生成一个 DIV 容器来显示结果并添加到页面末尾. 这样, 对原来的表单只需要改动一个地方就可以将原来的表单提交改为异步模式, 即在 form 标签里加入: onSubmit="ajaxSubmitForm(this);return false;" 即可, return false 确保表单不会被浏览器同步提交. 完整的例子请看这里.


----------------- 引用结束 -----------------

OK, 希望至此为止您已经理解了如何用 AJAX 来正确的执行 GET/POST. 如果这个问题您解决了, 可是说后台的乱码问题就和你直接通过表单提交几乎没有区别了. 这个方法的具体封装已经在附件的 ajax_common.js 中了.

至此也该贴出来我们的 GBK 编码的客户端页面的内容了:


<html>

<head>
<meta http-equiv="Content-Type" content="text/html; charset=gbk">
<title>AJAX Form Submit Test</title>
<script src='ajax_common.js'></script>

</head>

<body>
本页面的编码是中文.<br/>
&lt;meta http-equiv="Content-Type" content="text/html; charset=gbk"&gt;<br/>
<b>测试过的服务器:</b><br/>
Resin 3.0.18<br/>
Tomcat 5.5.20<br/>
Tomcat 5.0.30<br/>
<h3>AJAX Form Submit Test</h3>
Fill the form and then click submit<br>
提交方式: POST<br>
<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="text文本&amp;1">
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>

提交方式: GET<br>
<form method="GET" id="form2" name="form2"
action="form_action.jsp"
onSubmit="former2.ajaxSubmitForm();return false;">
<p><input type="hidden" name="hidden1" value="hiddenValue">
text:<input type="text" name="text文本&amp;2" size="20" value="text文本&amp;2">
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>

<div id="loading" style="display:none; position:absolute;
border:1px solid orange; height:20px; width:600; left: 93px; top: 112px;
background-color: #FFFFCC; cursor:pointer;" title="Click to hide" onClick="this.style.display='none';"></div>

<div id="resultDiv" style="border:1px solid orange; background-color: #FFFFCC; cursor:pointer;" title="Click to hide" onClick="this.style.display='none';">
Form 1 的提交结果将会显示在这里.
</div>


<script type="text/javascript">
var former = new AjaxFormer($('form1'), 'resultDiv');
var former2 = new AjaxFormer($('form2'));
</script>
</body>

</html>

可以看到我们的确使用的是 GBK 编码, 浏览器打开的时候自动选择的编码也是简体中文.

那么第二个关键点就是服务器端的表单数据读取了.这个问题跟具体的服务器有很大关系. 对于 Resin 服务器来说, 问题很少, 基本上不论是 POST 和 GET, 出乱码的概率都比较小. 但是 Tomcat 就不敢恭维了, 这大概也是开源产品和商业产品的区别, 缺乏前后一致性和兼容性, 因为开源的不需要提供技术支持. Tomcat 的 GET/POST 的编码处理方式不同的版本都不一样, 就像 Eclipse/Netbeans 新版本从来不需要兼容老版本的插件 API 一样, Hibernate/Struts/Spring 也是一样, 所以学 Java 的很累. 当然, 这就是免费/开源的代价. 跑题了. 因此我们的服务器端代码大部分都是对 Tomcat 的乱码问题的解决(POST的没有问题, 主要是 GET 方法的).

<%@ page contentType="text/html; charset=gbk" pageEncoding="gbk"%>
<html>
<%
//Send some headers to keep the user's browser from caching the response.
response.addHeader("Expires", "Mon, 26 Jul 1997 05:00:00 GMT" );
response.addHeader("Last-Modified", new java.util.Date().toGMTString());
response.addHeader("Cache-Control", "no-cache, must-revalidate" );
response.addHeader("Pragma", "no-cache" );
// This will emulate a network delay, for 2 sec.
//Thread.currentThread().sleep(2000);

request.setCharacterEncoding("utf-8");
%>

<%!

/**
* 转换字符串的内码.
*
* @param input
* 输入的字符串
* @param sourceEncoding
* 源字符集名称
* @param targetEncoding
* 目标字符集名称
* @return 转换结果, 如果有错误发生, 则返回原来的值
*/
public static String changeEncoding(String input, String sourceEncoding,
String targetEncoding) {
if (input == null || input.equals("")) {
return input;
}

try {
byte[] bytes = input.getBytes(sourceEncoding);
return new String(bytes, targetEncoding);
} catch (Exception ex) {
}
return input;
}

/**
* 一个类似于 JavaScript 的 escape 函数的功能, 确保乱码可以正确传输.
*/
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();
}
%>

<head>
<title>Test form action page</title>
</head>
<body>
这是 GBK 编码版本的后台表单提交页面.<br/>

<%
boolean isTomcat = application.getServerInfo().toLowerCase().indexOf("tomcat") != -1;
%>

Form submit method:<%=request.getMethod()%><br/>
The form content u send is:<br/>
<%
java.util.Enumeration e = request.getParameterNames();

while (e.hasMoreElements()) {
String name = (String)e.nextElement();
String value = request.getParameter(name);

if(isTomcat && request.getMethod().equalsIgnoreCase("GET")) {
name = changeEncoding(name, "ISO8859-1", "UTF-8");
value = changeEncoding(value, "ISO8859-1", "UTF-8");
}
out.println("<b>" + name + "</b> = " + value + "<br/>");
}

// 给前台返回一个可以执行的脚本
//response.addHeader("response_script", changeEncoding("alert('提交完成');", "ISO8859-1", "UTF-8"));
response.addHeader("response_script", escape("alert('提交完成');"));
%>

</body>
</html>

booleanisTomcat=application.getServerInfo().toLowerCase().indexOf("tomcat") !=-1; 这一句主要针对 Tomcat 进行处理, 如果是 GET 方法的话, 就需要将表单参数从 ISO8859-1 转换到 UTF-8 (注意不是 GBK, 貌似 Tomcat 很喜欢 UTF-8?). 其它的地方和原来的 UTF-8 版本的没有区别. 当然如果您的站点应该用过滤器来更方便的解决这个问题.

小结:

1. 使用一致的字符集很重要, 要么全是 GBK, 要么全是 UTF-8, 如果有条件, 就全部用 UTF-8, 那样工作量是最小的;

2. 用 AJAX 提交的时候一定要按照 HTTP 的规范来, 做到和浏览器尽量兼容, 尤其是 POST 的时候不要再往 URL 地址里加参数了, 你那样是违规! 后果就是有的服务器会不搭理你传递的这些参数! 还是如我所讲, 参数提交之前要用 encodeURIComponent() 来转化, 这也是为了符合浏览器的习惯做法.

3. 后台如果读取参数有乱码, 就尽量多在 ISO8859-1, GBK, UTF-8 中间多转换几次试试, 可以试试偶写的那个 changeEncoding() 方法, 把几个转换后的表单值都列出来, 一定有一个是正确的, 总是可以解决问题的. 这个本来不应该是偶们的任务, 但是写服务器的人是老美, 尤其是 Tomcat 作者, 只熟悉 ISO8859-1.

4. 鉴于 TOMCAT 读取 POST 参数的时候很少出问题, 因此建议AJAX提交表单的时候多用 POST 方法, 尽量不用 GET.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值