随笔-314  评论-209  文章-0  trackbacks-0
 

早起的国内互联网都使用GBK编码,久之,所有项目都以GBK来编码了。对于J2EE项目,为了减少编码的干扰通常都是设置一个编码的Filter,强制将Request/Response编码改为GBK。例如一个Spring的常见配置如下:

 

<filter>
    <filter-name>encodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>GBK</param-value>
    </init-param>
    <init-param>
        <param-name>forceEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>

毫无疑问,这在GBK编码的页面访问、提交数据时是没有乱码问题的。但是遇到Ajax就不一样了。Ajax强制将中文内容进行UTF-8编码,这样导致进入后端后使用GBK进行解码时发生乱码。网上的所谓的终极解决方案都是扯淡或者过于复杂,例如下面的文章:

这样的文章很多,显然:

  • 使用VB进行UTF-8转换成GBK提交是完全不可行(多浏览器、多平台完全不可用)
  • 使用复杂的js函数进行一次、多次编码,后端进行一次、多次解码也是不靠谱的,成本太高,无法重复使用

如果提交数据的时候能够告诉后端传输的编码信息是否就可以避免这种问题?比如Ajax请求告诉后端是UTF-8,其它请求告诉后端是GBK,这样后端分别根据指定的编码进行解码是不是就解决问题了。

有两个问题:

  1. 如何通过Ajax告诉后端的编码?Header过于复杂,Cookie成本太高,使用参数最方便。
  2. 后端何时进行解码?每一个请求进行解码,过于繁琐;获取参数时解码,此时已经乱码;在Filter里面动态设置编码是最完善的方案。
  3. 如何从参数中获取编码?如果是POST的body显然无法获取,因此在获取之前所有参数就已经按照某种编码解码过了,无法还原。所以通过URL传递编码最有效。支持GET/POST,同时成本很低。

解决了上述问题,来看具体实现方案。 列一段Java代码:

import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.util.ClassUtils;
import org.springframework.web.filter.OncePerRequestFilter;

/** 自定义编码过滤器
 * @author imxylz (imxylz#gmail.com)
 * @sine 2011-6-9
 */
public class MutilCharacterEncodingFilter extends OncePerRequestFilter {

    static final Pattern inputPattern = Pattern.compile(".*_input_encode=([\\w-]+).*");

    static final Pattern outputPattern = Pattern.compile(".*_output_encode=([\\w-]+).*");

    // Determine whether the Servlet 2.4 HttpServletResponse.setCharacterEncoding(String)
    // method is available, for use in the "doFilterInternal" implementation.
    private final static boolean responseSetCharacterEncodingAvailable = ClassUtils.hasMethod(HttpServletResponse.class,
          "setCharacterEncoding", new Class[] { String.class });

    private String encoding;

    private boolean forceEncoding = false;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
 throws ServletException, IOException {
        String url = request.getQueryString();
        Matcher m = null;
        if (url != null && (m = inputPattern.matcher(url)).matches()) {//输入编码
            String inputEncoding = m.group(1);
            request.setCharacterEncoding(inputEncoding);
            m = outputPattern.matcher(url);
            if (m.matches()) {//输出编码
                response.setCharacterEncoding(m.group(1));
            } else {
                if (this.forceEncoding && responseSetCharacterEncodingAvailable) {
                    response.setCharacterEncoding(this.encoding);
                }
            }
        } else {
            if (this.encoding != null && (this.forceEncoding || request.getCharacterEncoding() == null)) {
                request.setCharacterEncoding(this.encoding);
                if (this.forceEncoding && responseSetCharacterEncodingAvailable) {
                    response.setCharacterEncoding(this.encoding);
                }
            }
        }
        filterChain.doFilter(request, response);
    }

    public void setEncoding(String encoding) {
        this.encoding = encoding;
    }

    public void setForceEncoding(boolean forceEncoding) {
        this.forceEncoding = forceEncoding;
    }
}

解释下:

  • 如果URL的QueryString中包含_input_encode就使用此编码进行设置Request编码,以后参数按照此编码进行解析,例如如果是Ajax就传入UTF-8,如果是普通的GBK请求则无视此参数。
  • 如果无视此参数,则按照web.xml中配置的编码规则进行反编码,如果是GBK就按照GBK规则解析。
  • 对于输出编码同样使用上述规则。需要输出编码依赖输入编码,也就是说如果有一个_output_encode的输出编码,则同时需要有一个_input_encode编码来指定输入编码。当然你可以改造成不依赖输入编码。
  • 完全兼容Spring的org.springframework.web.filter.CharacterEncodingFilter编码规则,只需要替换类即可。
  • 没有继承org.springframework.web.filter.CharacterEncodingFilter类的原因是,org.springframework.web.filter.CharacterEncodingFilter里面的encoding参数和forceEncoding参数是private,子类无法使用。在有_input_encode而无_output_encode的时候想依然保持Spring的Response解析规则的话无法做到,所以将里面的代码拷贝过来使用。(为了展示方便,注释都删掉了)
  • 正则表达式可以改进成只需要匹配一次,从而可以提高一点点效率。
  • 所有后端请求将无视编码的存在,前端Ajax的GET/POST请求也将无视编码的存在,只是在URL地址中加上一个_input_encode=UTF-8的参数。仅此而已。
  • 如果想输出的编码也是UTF-8,比如手机端请求、跨站请求等,则需要URL地址参数_input_encode=UTF-8&_output_encode=UTF-8。
  • 对于POST请求,编码参数不能写在body里面,否则无法解析。
  • 显然,这种终极解决方案,在任何编码中都可以解决,GBK/UTF-8/ISO8859-1等编码任何组合都可以实现。
  • 唯一局限性是,此解决方案限制在J2EE项目中,其它平台不知是否有类似Filter这样的组件能够设置编码的概念。

posted on 2011-06-21 11:54 xzc 阅读(2102) 评论(1)  编辑  收藏 所属分类: XMLWeb

评论:
# re: 当Ajax遭遇GBK编码 (完全解决方案) 2013-08-11 11:37 | 松岛枫
纯属扯淡  回复  更多评论
  

只有注册用户登录后才能发表评论。


网站导航: