get请求中文乱码,思考为什么要用两次的encodeURI()

get请求中文乱码,思考为什么要用两次的encodeURI()

思路

在修改系统的一个bug时遇到了中文乱码的问题,本来还很高兴,因为针对中文乱码一般只要前台进行encodeURI(),而后在后台进行 URLDecoder . decode()即可。但是今天似乎并不顺利,前台代码如下:
   
   
keyword=encodeURI(keyword);
setting.async.url='<%=request.getContextPath()%>/common.do?tblname=cfgdept&formaction=getDeptDeviceTree&roleUnid=<%=roleid%>&keyword='+keyword;
后台代码如下:
   
   
String keyword;
keyword = URLDecoder.decode(
StrUtil.checkNull(session.getRequest().getParameter(
"keyword")), "utf-8");
发现怎么折腾后台接受到的字符串还是乱码。按照解决思路就要跟踪这个请求的全过程,看看到底是在哪个位置产生了乱码。那我们看一下这个代码的逻辑,前台搜索框接受用户输入的搜索值,当用户出发搜索时。我们使用js来获取到用户输入的文本值。获取到值之后,将值进行编码(encodeURI)。并将编码后的值附在get请求上发送给后台进行处理。请求发送到后台时,会首先进入过滤器,而后进入拦截器(使用了struts框架),请求根据配置,找到对应的代码处理逻辑。后台代码针对接受到的请求获取搜索字段参数值。针对值进行解码。
    按照上面的一个处理逻辑,可能发生乱码的地方应该有以下几个位置:
         1:js获取用户前台输入。、
         2:编码后在GET请求时,由于长度超过浏览器规定长度,字符被截断。
         3:在进入后台过滤器的时候,被字符编码过滤器所改变。
         4:在进行解码时,解码的字符编码格式错误。
    针对以上四个问题进行验证:
         1:在js取得值后,进行打印。此时得到的字符是正确的。
         2:GET请求在IE浏览器4.0版本之后的最长的字节长度允许是2048字节,我的连接肯定没有那么长。
         3:进行debug,使用request.getParameter()获取值。发现在编码字符过滤器生效之前,字符已经乱码。
         4: encodeURI 的目的是给URI进行编码。ASCII 的字母、数字不编码,- _ . ! ~ * ' ( )也不编码,URI中具有特殊意义的字符也不编码; ; / ? : @ & = + $ , # 空格。 参数中的其他字符将转换成UTF-8编码方式的字符,并使用十六进制转义序列(%xx)生成替换。其中,ASCII字符使用一个%xx替换,在u0080与u07ff之间的编码的字符使用两个%xx替换,其它的16为Unicode字符使用三个%xx替换。 如果想对URI的分隔符? #编码,应该使用encodeURIComponent。(参考[1])
         通过以上分析,那么产生乱码的原因应该是发生在web服务器或者request.getParameter()这个方法时。 开源大法好,查看源码。这边要注意的是" javax.servlet.http.HttpServlet "。这个包并非引用的是jdk。jdk当中并没有这个包。这个包的是在tomcat下的lib文件夹中的servlet-api.jar中。查看tomcat的源码。

类:  org.apache.catalina.core. ApplicationHttpRequest.java  方法:getParameter
   
   
public String getParameter(String name) {
 
parseParameters();
 
Object value = parameters.get(name);
if (value == null)
return (null);
else if (value instanceof String[])
return (((String[]) value)[0]);
else if (value instanceof String)
return ((String) value);
else
return (value.toString());
 
}
类: ApplicationHttpRequest.java  方法: parseParameters
   
   
/**
* Parses the parameters of this request.
*
* If parameters are present in both the query string and the request
* content, they are merged.
*/
void parseParameters() {
 
if (parsedParams) {
return;
}
 
parameters = new HashMap();
parameters = copyMap(getRequest().getParameterMap());
mergeParameters();
parsedParams = true;
}
类: ApplicationHttpRequest.java  方法: mergeParameters
   
   
/**
* Merge the parameters from the saved query parameter string (if any), and
* the parameters already present on this request (if any), such that the
* parameter values from the query string show up first if there are
* duplicate parameter names.
*/
private void mergeParameters() {
 
if ((queryParamString == null) || (queryParamString.length() < 1))
return;
 
HashMap queryParameters = new HashMap();
String encoding = getCharacterEncoding();//从tomcat的配置文件server.xml当中获取
if (encoding == null)
encoding = "ISO-8859-1";
try {
RequestUtil.parseParameters
(queryParameters, queryParamString, encoding);
} catch (Exception e) {
;
}
Iterator keys = parameters.keySet().iterator();
while (keys.hasNext()) {
String key = (String) keys.next();
Object value = queryParameters.get(key);
if (value == null) {
queryParameters.put(key, parameters.get(key));
continue;
}
queryParameters.put
(key, mergeValues(value, parameters.get(key)));
}
parameters = queryParameters;
 
}
类: RequestUtil   方法: parseParameters
    
    
/**
* Append request parameters from the specified String to the specified
* Map. It is presumed that the specified Map is not accessed from any
* other thread, so no synchronization is performed.
* <p>
* <strong>IMPLEMENTATION NOTE</strong>: URL decoding is performed
* individually on the parsed name and value elements, rather than on
* the entire query string ahead of time, to properly deal with the case
* where the name or value includes an encoded "=" or "&" character
* that would otherwise be interpreted as a delimiter.
*
* NOTE: byte array data is modified by this method. Caller beware.
*
* @param map Map that accumulates the resulting parameters
* @param data Input string containing request parameters
* @param encoding Encoding to use for converting hex
*
* @exception UnsupportedEncodingException if the data is malformed
*/
public static void parseParameters(Map map, byte[] data, String encoding)
throws UnsupportedEncodingException {
 
if (data != null && data.length > 0) {
int ix = 0;
int ox = 0;
String key = null;
String value = null;
while (ix < data.length) {
byte c = data[ix++];
switch ((char) c) {
case '&':
value = new String(data, 0, ox, encoding);
if (key != null) {
putMapEntry(map, key, value);
key = null;
}
ox = 0;
break;
case '=':
if (key == null) {
key = new String(data, 0, ox, encoding);
ox = 0;
} else {
data[ox++] = c;
}
break;
case '+':
data[ox++] = (byte)' ';
break;
case '%':
data[ox++] = (byte)((convertHexDigit(data[ix++]) << 4)
+ convertHexDigit(data[ix++]));
break;
default:
data[ox++] = c;
}
}
//The last value does not end in '&'. So save it now.
if (key != null) {
value = new String(data, 0, ox, encoding);
putMapEntry(map, key, value);
}
}
 
}


从上面的源码我们可以看到在【 类: ApplicationHttpRequest.java  方法: mergeParameters】中,已经针对request当中的参数进行一次解码。这一次的解码编码如果没有设定的话,就使用ISO-8859-1来解码。那我们前台使用的是UTF-8进行编码。这样就产生了乱码。
所以第一种解决方案应该是修改tomcat的配置文件。增加或修改编码为UTF-8(默认是ISO-8859-1);
    
    
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="UTF-8" />

那说了第一种就肯定有第二种,第二种就是传一串不管使用什么编码格式解码都能返回正确结果的字符串给后台。那么满足这个条件的就是ASCII码。只要我们将我们需要传递的中文编码成ASCII码再传递给后台。那么web服务器不论使用何种编码解码都不会出现问题。所以可以在前台进行两次的编码。前台的两次编码第一次是将所有的中文转换成ASCII码。第二次针对ASCII字符串再次的编码。此时web服务器的自带解码,无论使用何种编码解出来的都是正确的一串ASCII码字符串。对于web服务器解码后的这一串字符串,我们后台只需要使用相应的正确编码格式进行解码,就可以得到正确的中文字符。(代码详见下方解决方案第二小点)

解决方案
1:配置web服务器(这边指的是tomcat)的配置文件server.xml,修改或增加编码格式。
   
   
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="UTF-8" />
2:在前台对需要传到后台的中文进行两次的编码。后台使用正确的编码格式进行解码。
前台:
   
   
keyword=encodeURI(encodeURI(keyword));//两次编码
setting.async.url='<%=request.getContextPath()%>/common.do?tblname=cfgdept&formaction=getDeptDeviceTree&roleUnid=<%=roleid%>&keyword='+keyword;
后台
    
    
keyword = URLDecoder.decode(
StrUtil.checkNull(session.getRequest().getParameter(
"keyword")), "utf-8");
拓展测试
    
    
@org.junit.Test
public void enCode() throws UnsupportedEncodingException {
String s = "请求中文";
String enc = "UTF-8";
// 第一次编码
String fEncode = URLEncoder.encode(s, enc);
 
// 第一次解码
String fDecode = URLDecoder.decode(fEncode, enc);
// 第二次解码
String sDecode = URLDecoder.decode(fDecode, "iso-8859-1");
 
System.out.println("第一次编码:" + fEncode + ";\n" + "第一次解码:" + fDecode
+ ";\n" + "第二次解码:" + sDecode + ";\n");
}
结果:第一次编码:%E8%AF%B7%E6%B1%82%E4%B8%AD%E6%96%87;
第一次解码:请求中文;
第二次解码:请求中文;
结论:解码只能进行一次,对于解码后的文字,在进行解码,并不会改变值。
附件
1:tomcat6.0.18源码
2:Servlet API

下载地址: http://download.csdn.net/detail/ozhuanchen/9080615

 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值