一次post提交中文造成乱码问题的分析

原创
2015/09/18 23:48
阅读数 1W

前提条件

在解决问题之前,web模块中配置了自定义的HttpEncodingFilter和GetHttpServletRequestWrapper,期待能够解决所有服务器上的中文乱码问题,很遗憾,让大家失望了。最后给出web.xml中过滤器和两个类的明细

在某个功能中,form表单通过post提交,表单中大部分参数通过struts1的form.vo(包含中文)封装,另外两个参数(包含中文)通过表单域直接提交,格式大概就是这样子了:

<form action="site.do" method="post">
    <input name="vo.name" value="黄金沙" /> //注意跟strtus form关联
    <input name="authValue" value="系统管理员" /> 
</form>

这样在SiteAction中,根据vo获取name的值不会乱码,通过request.getParameter("authValue")就可能出现乱码(随服务器而定),经过测试在jboss和weblogic下面,不出现乱码,在was下面乱码。

分析过程

由于代码使用了框架struts1,表单提交时,vo.name经过struts的处理,authValue是不经过处理的,这也就造成应用服务器对这两个参数的不同处理。

首先让我们看下HttpEncodingFilter这个类:

public class HttpEncodingFilter implements Filter {

	private String charset = "UTF-8";
	private String uriEncoding = "iso8859-1";
	private FilterConfig config;
	private Logger logger = Logger.getLogger(this.getClass());

	public void doFilter(javax.servlet.ServletRequest request,
			javax.servlet.ServletResponse response,
			javax.servlet.FilterChain chain) throws java.io.IOException,
			javax.servlet.ServletException {

		response.setCharacterEncoding(charset);
		// 新增加的代码 (仍然存在问题,当使用multipart提交时,如果参数写在表单中会出现中文乱码) TODO
		HttpServletRequest req = (HttpServletRequest) request;
		boolean hasEncoding = false;
		if (req.getCharacterEncoding() != null) {
			hasEncoding = true;
		}
		if (!hasEncoding) {
			req = new GetHttpServletRequestWrapper(req, charset, uriEncoding);
		}
		chain.doFilter(req, response);

	};

	public void init(FilterConfig config) throws ServletException {
		this.config = config;

		String charset = config.getInitParameter("charset");
		if (charset != null && charset.trim().length() != 0) {
			this.charset = charset;
		}

		String uriEncoding = config.getInitParameter("uriEncoding");
		if (uriEncoding != null && uriEncoding.trim().length() != 0) {
			this.uriEncoding = uriEncoding;
		}
	}

	public void destroy() {
		// TODO Auto-generated method stub

	}
}

在这个过滤器中,没有设置request的CharacterEncoding,设置了response的CharacterEncoding为GBK,但是用自定义的GetHttpServletRequestWrapper封装了ServletRequest,可以看下这个类怎么写的:

package com.excellence.exportal.base.common;
import java.io.UnsupportedEncodingException;  
import java.util.Map;
  
import javax.servlet.http.HttpServletRequest;  
import javax.servlet.http.HttpServletRequestWrapper;  

import org.apache.log4j.Logger;
  
public class GetHttpServletRequestWrapper extends HttpServletRequestWrapper {  
  
    private String charset = "UTF-8";  
    private String uriEncoding = "iso8859-1";
    private String Method = "GET";
    private boolean isAjax = false;
    private Logger logger = Logger.getLogger(this.getClass());
    
    public GetHttpServletRequestWrapper(HttpServletRequest request) { 
        super(request);  
    }  
  
    /** 
     * 获得被装饰对象的引用和采用的字符编码 
     * @param request 
     * @param charset 
     */  
    public GetHttpServletRequestWrapper(HttpServletRequest request,  
            String charset,String uriEncoding) {  
        super(request);  
        this.charset = charset;  
        this.uriEncoding = uriEncoding;
        
    }  
    
    /** 
     * 获得被装饰对象的引用和采用的字符编码 
     * @param request 
     * @param charset 
     */  
    public GetHttpServletRequestWrapper(HttpServletRequest request,  
    		String charset,String uriEncoding,String method,boolean isAjax) {  
    	super(request);  
    	this.charset = charset;  
    	this.uriEncoding = uriEncoding;
    	this.Method = method;
    	this.isAjax = isAjax;
    	
    }  
  
    /** 
     * 实际上就是调用被包装的请求对象的getParameter方法获得参数,然后再进行编码转换 
     */ 
    @Override
    public String getParameter(String name) {  
    	 String value = super.getParameter(name);  
         value = (value == null ? null : convert(value));  
         return value;  
    }  
    
    @Override
    public String[] getParameterValues(String name){
    	String[] resultArr = super.getParameterValues(name);
    	if(resultArr == null || resultArr.length <= 0){
    		return null;
    	}
    	for(int i=0;i<resultArr.length;i++){
    		String temp = resultArr[i];
    		if(temp!=null){
    			resultArr[i] = convert(temp);
    		}
    	}
    	return resultArr;
    }
    
    @Override
    public Map getParameterMap() { 
    	//在高版本的api中 map的value是string数组,而低版本的value是string,故使用'?'号占位
    	Map<String,?> paramMap = super.getParameterMap();
    	Object value = null;
    	for(Map.Entry entry:paramMap.entrySet()){
    		value = entry.getValue();
    		if(value!=null){
    			if(value instanceof String[]){
    				String[]  values = (String[])value;
    				for(int i=0;i<values.length;i++){
    					values[i] = convert(values[i]);
    				}
    				entry.setValue(values);
    			}else{
    				entry.setValue(convert(value.toString()));
    			}
    		}
    		
    	}
    	return paramMap;  
    }  
    
    public String convert(String target) { 
        try {          	
        	byte bytes[] = target.getBytes(this.uriEncoding);
        	String result = new String(bytes,this.charset);
            return result;
        } catch (UnsupportedEncodingException e) {  
            return target;  
        } 
        
    }    
}


 在这个类中,重写了getParameter、getParameterValues、getParameterMap这些方法,在这些方法里面都实现了将value按指定编码转码的逻辑。

继续回到表单提交的情况,上文提到了struts的表单提交与普通表单域处理中文时,不同表现(乱码和不乱吗),这块也反复提到跟应用服务器对请求参数的处理分不开,结合GetHttpServletRequestWrapper来讲。

其中struts中表单提交在jboss、was、weblogic中的表现一致,均不会出现乱码,具体原因的话,struts将浏览器端提交的参数,不经转码直接交给GetHttpServletRequestWrapper的getParameterValues方法处理,其中post提交的时候,在页面已经制定contextType是GBK的编码,这样getParameterValues拿到编码后的值,经过一层转码,得到正常的中文值,所以用struts提交始终不出现乱码的情况(前提post方式提交表单)。

但是authValue的值获取就不一样了,在action中通过getParameter API获取相应的值,由于GetHttpServletRequestWrapper封装了实际的request,这里调用的是GetHttpServletRequestWrapper中的getParameter方法,细细看这个方法发现,里面会对拿到的value进行一层转码。问题就出来了,如果转码后出现乱码,只能说明转码前的内容不是提交后浏览器编码的内容(有点绕口)。这里需要解释一个常识,post提交方式,浏览器会根据jsp页面的contextType的编码对表单值进行编码。经过进一步的对比分析,发现在was(8.5版本)下面运行这段代码,出现中文乱码,在weblogic(10.3版本)下面运行,正常。

简单描述这个问题:在浏览器提交post请求后,对于getParameter的API,weblogic不会根据处理编码;而was则会进行一次转码(可能是根据jvm的编码进行转码),这样GetHttpServletRequestWrapper的getParameter API必然会出现乱码了。

解决办法

1、针对应用服务器给出不同的解决方案,在代码层面做适配,缺点,过滤器显得不够通用。

2、从根本上解决乱码问题,不传中文,实在要传中文的地方,在提交请求前,将中文进行转码,常用的转码方案,利用javascript的encodeURIComponent编码,根据请求类型get 或者 post 多次使用encodeURIComponent。

这里给出我们用的一种解决方案,将中文用base64编码,提交后,在后台用base64解码,这样的好处,可以避免浏览器对表单值就行默认的编码处理。但是注意一个问题,注意base64解码时的程序的健壮性处理,因为某些情况写,由于解码出现异常,这是正常的。

参考文档

http://www.cnblogs.com/gywbg/archive/2012/04/13/2445634.html

http://www.ibm.com/developerworks/cn/java/j-lo-chinesecoding/

利用base64解决传参的编码问题,这个主意非我所想,感谢团队的小伙提供这个方法,我只是代劳整理了出来。

 

展开阅读全文
加载中

作者的其它热门文章

打赏
1
2 收藏
分享
打赏
3 评论
2 收藏
1
分享
返回顶部
顶部