JSP AJAX表单提交中文乱码问题的解决方案实例源码介绍(GBK版本)



JSP AJAX表单提交中文乱码问题的解决方案实例源码介绍(GBK版本)。 当AJAX遭遇GBK的尴尬 说当 AJAX 使用 GBK 编码后, 表单提交将出现乱码. 只要全部采用UTF-8 编码, 是没有任何问题的. 那么都用 GBK 呢?会继续出现乱码显现吗?

滤器等方法解决中文乱码的问题.

使用 GBK 编码到底有没有乱码问题呢?

第一个关键点就是 AJAX 的表单提交代码必须正确的按照 HTTP 规范实现, 即要保持原来的 GET/POST 方式不变,
也要保持里面的内容和浏览器提交的内容一摸一样. 以下内容摘自我编写的内部培训教材:

—————– 引用开始 —————–

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

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

< 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″  e
    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文本&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文本&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:300; left: 93px; top: 112px;
    background-color: #FFFFCC; cursor:pointer;”  title =”Click to hide”
    onClick =”this.style.display=’none’;” ></ div >

    < script  type =”text/javascript” >
    var  former  =   new  AjaxFormer($(‘form1′));
    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;
    }
    %>

    <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/>”);
    }
    %>

    </body>
    </html>

    boolean isTomcat = 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.

    运行截屏:

    其它的一些资料可以参考Blogjava上的一篇原创文章:  [原创]struts,ajax乱码解决方案

    欢迎发表建议和更好的观点. 谢谢! 重申本文无意代替您的 AJAX 框架, 不过在你抓狂的时候可以考虑看看他们表单提交的代码,
    改改它!

    本人翻译的 XMLHttpRequest 对象介绍:

    The XMLHttpRequest Object Reference XMLHttpRequest 对象参考

    Methods 方法

    Method 方法 Description 描述
    abort() Cancels the current request
    取消当前请求
    getAllResponseHeaders() Returns the complete set of http headers as a string
    将完整的 HTTP 头部做为一个字符串返回
    getResponseHeader(“headername”) Returns the value of the specified http header
    返回给定的 HTTP 头的值
    open(“method”,”URL”,async,”uname”,”pswd”) Specifies the method, URL, and other optional attributes of a requestThe method parameter can have a value of “GET”, “POST”, or “PUT” (use “GET” when requesting data and use “POST” when sending data (especially if the length of the data is greater than 512 bytes.

    The URL parameter may be either a relative or complete URL.

    The async parameter specifies whether the request should be handled asynchronously or not. true means that script processing carries on after the send() method, without waiting for a response. false means that the script waits for a response before continuing script processing
    指定表单提交方法, URL, 以及请求的可选属性

    method 参数可以是”GET”, “POST” 或者  “PUT” 这些值中之一(使用”GET”来请求数据, 特别的, 当发送的数据长度大于512字节时使用 “POST”).

    URL 参数可以为相对的和完整的 URL.

    async 参数指定请求是否为异步方式处理. true 意味着调用 send() 方法后脚本继续向下执行, 不需要等待响应. false 意味着脚本将等待响应之后才能继续执行

    send(content) Sends the request
    发送请求
    setRequestHeader(“label”,”value”) Adds a label/value pair to the http header to be sent
    在要发送的 HTTP 头中添加 标签/取值

    Properties 属性

    Property 属性 Description 描述
    onreadystatechange An event handler for an event that fires at every state change
    每次状态改变时除非的事件处理器
    readyState Returns the state of the object:0 = uninitialized
    1 = loading
    2 = loaded
    3 = interactive
    4 = complete
    返回对象的状态

    0 = 未初始化
    1 = 载入中
    2 = 已载入
    3 = 交互
    4 = 完成

    responseText Returns the response as a string
    将响应做为字符串返回
    responseXML Returns the response as XML. This property returns an XML document object, which can be examined and parsed using W3C DOM node tree methods and properties
    将响应做为XML返回. 这个属性返回一个 XML 文档对象, 可以用 W3C 的 DOM 节点树方法和属性进行检索分析
    status Returns the status as a number (e.g. 404 for “Not Found” or 200 for “OK”)
    将状态做为数字返回(例如 404 为”Not Found” 或者 200 为 “OK”)
    statusText Returns the status as a string (e.g. “Not Found” or “OK”)
    将状态做为字符串返回(例如 “Not Found” 或者 “OK”)