中文乱码问题案例分析

 

案例:

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);

    }

也就是说步骤是大致是这样的 ( 因为没仔细研究 tomcat 源码,所以顺序可能不会很正确 )

从上面的代码中可以知道对 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 URL Encode 是按照 UTF-8 编码 ,QueryString 是按照 GBK 编码。
http://localhost:8080/example/
中国 ?name= 中国
实际上提交是:
GET /example/%E4%B8%AD%E5%9B%BD?name=%D6%D0%B9%FA

(1) 对于中文 IE, 如果在高级选项中取消总以 UTF-8 发送,则 PathInfo QueryString URL encode 按照 GBK 编码。
实际上提交是:
GET /example/%D6%D0%B9%FA?name=%D6%D0%B9%FA

(3) 对于中文 firefox ,则 pathInfo queryString 都是 URL encode 按照 GBK 编码。
实际上提交是:
GET /example/%D6%D0%B9%FA?name=%D6%D0%B9%FA

很显然,不同的浏览器以及同一浏览器的不同设置,会影响最终 URL PathInfo 的编码。对于中文的 IE FIREFOX 都是采用 GBK 编码 QueryString

小结:解决方案:
1
URL 中如果含有中文等非 ASCII 字符,则浏览器会对它们进行 URLEncode 。为了避免浏览器采用了我们不希望的编码,所以最好不要在 URL 中直接使用非 ASCII 字符,而采用 URL Encode 编码过的字符串 %.
比如:
URL
http://localhost:8080/example/ 中国 ?name= 中国
建议:
URL
http://localhost:8080/example/%D6%D0%B9%FA?name=%D6%D0%B9%FA

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 ,从这篇文档可以看出这个参数的设置对于解码和页面显示有很大的作用,对于请求阶段来说相当于执行了 HttpServletRequestsetCharacterEncoding 这个方法,但是对于这个方法,之前一直都没认真去理解,是不是设置了这个参数 url 中文乱码问题就解决了呢,答案是否定的, HttpServletRequest.setCharacterEncoding() 方法仅仅只适用于设置 post 提交的 request body 的编码而不是设置 get 方法提交的 queryString 的编码。该方法告诉应用服务器应该采用什么编码解析 post 传过来的内容。很多文章并没有说明这一点。看看 servletapi 也可以知道这一点:

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 提交有效,这是防止你在页面中没有设置 ContentTypecharset 而导致中文乱码。

 

   经过上面的分析总结一下终极解决办法:

1、  Tomcat 中设置 URIEncoding useBodyEncodingForURI 这两个参数。

2、  页面中或 js 中有中文路径或从参数,先进行编码,编码字符集和上面 tomcat 中配置的一样。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值