案例:
1、 环境介绍:
项目采用的是 SSH 框架技术,模板视图用的是 FreeMarker ,对于编码问题做了以下的配
置:
① tomcat 服务器没配置 URIEncoding 参数。
② struts2 配置文件配置了如下的参数:
<!-- 编码 -->
< constant name = "struts.i18n.encoding" value = "UTF-8" />
③ web.xml 进行了如下配置:
<!-- 编码处理过滤器 -->
< filter >
< filter-name > encodingFilter </ filter-name >
< filter-class >
org.springframework.web.filter.CharacterEncodingFilter
</ filter-class >
< init-param >
< param-name > encoding </ param-name >
< param-value > utf-8 </ param-value >
</ init-param >
< init-param >
< param-name > forceEncoding </ param-name >
< param-value > true </ param-value >
</ init-param >
</ filter >
现在来看一下这三者分别的用途,
A 、 URIEncoding 的作用是什么呢?
解析请求的 URL 是在 org.apache.coyote.HTTP11.InternalInputBuffer 的parseRequestLine 方法中,这个方法把传过来的 URL 的 byte[] 设置到org.apache.coyote.Request 的相应的属性中。这里的 URL 仍然是 byte 格式,转成char 是在 org.apache.catalina.connector.CoyoteAdapter 的 convertURI 方法中完成的: protected void convertURI(MessageBytes uri, Request request) throws Exception { ByteChunk bc = uri.getByteChunk(); int length = bc.getLength(); CharChunk cc = uri.getCharChunk(); cc.allocate(length, -1); String enc = connector.getURIEncoding(); if (enc != null ) { B2CConverter conv = request.getURIConverter(); try { if (conv == null ) { conv = new B2CConverter(enc); request.setURIConverter(conv); } } catch (IOException e){...} if (conv != null ) { try { conv.convert(bc, cc, cc.getBuffer().length - cc.getEnd()); uri.setChars(cc.getBuffer(), cc.getStart(), cc.getLength()); return ; } catch (IOException e) {...} } } // Default encoding: fast conversion byte [] bbuf = bc.getBuffer(); char [] cbuf = cc.getBuffer(); int start = bc.getStart(); for ( int i = 0; i < length; i++) { cbuf [ i ] = ( char ) ( bbuf [ i + start ] & 0xff); } uri.setChars(cbuf, 0, length); }
|
从上面的代码中可以知道对 URL 的 URI 部分 ( 也就是具体的请求资源部分不包括?后面的参数 ) 进行解码的字符集是在 connector 的 <Connector URIEncoding=”UTF-8”/> 中定义的,如果没有定义,那么将以默认编码 ISO-8859-1 解析。所以如果有中文 URL 时最好把 URIEncoding 设置成 UTF-8 编码。
QueryString 又如何解析? GET 方式 HTTP 请求的 QueryString 与 POST 方式 HTTP 请求的表单参数都是作为Parameters 保存,都是通过 request.getParameter 获取参数值。对它们的解码是在 request.getParameter 方法第一次被调用时进行的。 request.getParameter 方法被调用时将会调用 org.apache.catalina.connector.Request的 parseParameters 方法。这个方法将会对 GET 和 POST 方式传递的参数进行解码,但是它们的解码字符集有可能不一样。 POST 表单的解码将在后面介绍, QueryString 的解码字符集是在哪定义的呢?它本身是通过HTTP 的 Header 传到服务端的,并且也在 URL 中,是否和 URI 的解码字符集一样呢?从前面浏览器对 PathInfo和 QueryString 的编码采取不同的编码格式不同可以猜测到解码字符集肯定也不会是一致的。的确是这样QueryString 的解码字符集要么是 Header 中 ContentType 中定义的 Charset 要么就是默认的 ISO-8859-1 ,要使用 ContentType 中定义的编码就要设 connector 的 <Connector URIEncoding=”UTF-8” useBodyEncodingForURI=”true”/> 中的 useBodyEncodingForURI 设置为 true 。这个配置项的名字有点让人产生混淆,它并不是对整个 URI 都采用 BodyEncoding 进行解码而仅仅是对 QueryString 使用 BodyEncoding 解码,这一点还要特别注意。
从上面的 URL 编码和解码过程来看,比较复杂,而且编码和解码并不是我们在应用程序中能完全控制的,所以在我们的应用程序中应该尽量避免在 URL 中使用非 ASCII 字符,不然很可能会碰到乱码问题,当然在我们的服务器端最好设置 <Connector/> 中的 URIEncoding 和 useBodyEncodingForURI 两个参数。
也就是说对请求资源的解码和对后面所带参数的解码采用的字符集可能是不同的, URIEncoding 的设置只会告诉服务器如何对请求资源解码,而不会告诉服务器如何对请求参数解码,配置了 useBodyEncodingForURI 则告诉服务器使用 bodyEncoding 进行解码。
根据以上分析,可以知道项目没有配置 URIEncoding 和 useBodyEncodingForURI 两个参数。则对请求资源以及请求参数都会采用默认的 ISO8859-1 ,进行解码。
不过要说一点,虽然这里如果进行了指定,但是由于不同浏览器对 URL 进行编码的方式不同,也会出现乱码,以下是分析 :
浏览器: 1 、 GET 方式提交,浏览器会对 URL 进行 URL encode ,然后发送给服务器。 (1) 对于中文 IE, 如果在高级选项中取消总以 UTF-8 发送,则 PathInfo 和 QueryString 是URL encode 按照 GBK 编码。 (3) 对于中文 firefox ,则 pathInfo 和 queryString 都是 URL encode 按照 GBK 编码。 很显然,不同的浏览器以及同一浏览器的不同设置,会影响最终 URL 中 PathInfo 的编码。对于中文的 IE 和 FIREFOX 都是采用 GBK 编码 QueryString 。 小结:解决方案: 2 、我们建议 URL 中 PathInfo 和 QueryString 采用相同的编码,这样对服务器端处理的时候会更加简单。 |
由于我什么都没有设置,所以可以猜到,如果有中文路径或者是中文参数的 URL ,一定会出现中文乱码。假设我进行了设置,但是如果我在页面中没对中文路径和中文参数 URL 进行编码处理,那么由于不同浏览器的编码方式不同也会造成中文乱码问题,也就说终极解决方案是:所以最好不要在 URL 中直接使用非 ASCII 字符,而采用 URL Encode 编码过的字符串 %.
B 、 < constant name = "struts.i18n.encoding" value = "UTF-8" />
这个参数有什么作用呢?
关于这个参数有什么作用,网上也进行过讨论,参考这篇文档 http://cgl198617.iteye.com/blog/1066401 ,从这篇文档可以看出这个参数的设置对于解码和页面显示有很大的作用,对于请求阶段来说相当于执行了HttpServletRequest 的 setCharacterEncoding 这个方法,但是对于这个方法,之前一直都没认真去理解,是不是设置了这个参数 url 中文乱码问题就解决了呢,答案是否定的, HttpServletRequest.setCharacterEncoding() 方法仅仅只适用于设置 post 提交的 request body 的编码而不是设置 get 方法提交的 queryString 的编码。该方法告诉应用服务器应该采用什么编码解析 post 传过来的内容。很多文章并没有说明这一点。看看 servlet 的 api 也可以知道这一点:
Overrides the name of the character encoding used in the body of this request 。
这个方法用来设置网页 body 的编码方式,可以在页面中设置,也可以在获取参数之前,通过 HttpSevletRequest对象设置,现在来看 POST 提交:
POST 表单的编解码 在前面提到了 POST 表单提交的参数的解码是在第一次调用 request.getParameter 发生的, POST 表单参数传递方式与 QueryString 不同,它是通过 HTTP 的 BODY 传递到服务端的。当我们在页面上点击 submit 按钮时浏览器首先将根据 ContentType 的 Charset 编码格式对表单填的参数进行编码然后提交到服务器端,在服务器端同样也是用 ContentType 中字符集进行解码。所以通过 POST 表单提交的参数一般不会出现问题,而且这个字符集编码是我们自己设置的,可以通过 request.setCharacterEncoding(charset) 来设置。 另外针对 multipart/form-data 类型的参数,也就是上传的文件编码同样也是使用ContentType( 这 里所说的 ContentType 是指 http 头的 ContentType ,而不是在网页中meta 中的 ContentType ) 定义的字符集编码,值得注意的地方是上传文件是用字节流的方式传输到服务器的本地临时目录,这个过程并没有涉及到字符编码,而真正编码是在将文件内容添加 到 parameters 中,如果用这个编码不能编码时将会用默认编码 ISO-8859-1 来编码。 |
通过上面的分析我们可以知道, post 提交的编码解码跟服务器端的设置没有任何关系,而且也别妄想通过使用 request.setCharacterEncoding() 方法来解决 url 传递参数中文乱码的问题。
C 、 web.xml 中配置了解决中文编码问题的过滤器,我们查看源码发现这个:
if ( encoding != null && ( forceEncoding || request.getCharacterEncoding() == null )) { request.setCharacterEncoding( encoding ); if ( forceEncoding && responseSetCharacterEncodingAvailable ) response.setCharacterEncoding( encoding ); } filterChain.doFilter(request, response); |
看到这个方法没有:
request.setCharacterEncoding( encoding );
现在就知道为什么我配置了中文编码过滤器还是出错的原因了嘛,这个过滤器只会对 post 提交有效,这是防止你在页面中没有设置 ContentType 的 charset 而导致中文乱码。
经过上面的分析总结一下终极解决办法:
1、 在 Tomcat 中设置 URIEncoding 和 useBodyEncodingForURI 这两个参数。
2、 页面中或 js 中有中文路径或从参数,先进行编码,编码字符集和上面 tomcat 中配置的一样。