解决使用struts2标签s:url以及s:a时中文产生乱码的问题

    在平常使用web框架进行web开发时,经常讨论的一个问题就是一个中文的乱码问题。一般情况下,包括get乱码和post乱码,都能很好的解决,只需要通过配置tomcat以及增加相应的filter即可。但如果,本身tomcat并不参与解析编码时,即就不能很好地解决了。

    通常在使用一些链接时,我们偶尔会显式地传递一些中文的参数,用于显式查询。如下面的一段url地址(使用源代码查看时的链接地址):

/admin/calc/do/calcSellGoodsbyBatch.j?1=1&operation=销售&startDate=2011-07-01&endDate=2011-07-13

    这个地址在参数中直接带了一个中文的参数"销售",那么这个参数传递在服务器端时,采用的何种编码进行传递的呢。如果使用firefox,我们会看到firefox将其转化为gbk编码进行传输,如下所示:

http://localhost:4444/admin/calc/do/calcSellGoodsbyBatch.j?1=1&operation=%CF%FA%CA%DB
&startDate=2011-07-01&endDate=2011-07-13

    值得注意的是,这个转换是由firefox在界面上直接进行转换的,即在源代码中查看为"销售"的中文,在界面上转换时就自动转换成了%CF%FA%CA%DB。
    而使用ie时,则不会发生转换,也就是说,ie直接发送中文信息到服务器端,地址信息即会变为:

/admin/calc/do/calcSellGoodsbyBatch.j?1=1&operation=销售&startDate=2011-07-01&endDate=2011-07-13

    这个和在源代码中看到的是一样的。

    正常情况下,这两种情况都不会有问题,对于firefox,因为已经转换为gbk的url编码格式,在由tomcat进行转码时,会自动转换为CFFA CADB(即销售的gbk编码形式);对于ie,由于是直接发送,传递到tomcat时就是CFFA CADB的编码,经由gbk转换,自然就变成了正确的形式。即在底层发送时,对于"销售"这个中文参数值,firefox发送为%CF%FA%CA%DB,而ie发送为CFFACADB(字节流方式),两者在经由tomcat的gbk编码转换时,都没有问题。

    问题出现在哪儿,当需要在浏览器上,重现当前请求的地址时,即将刚才请求的地址信息,重新显示在界面上(通常这种应用在分页上用得多,即整个请求地址不变,只替换其中的分布参数信息)。这时候,在使用struts2的<s:url/>标签时以及<s:a/>标签时,就会出问题了。
    如在分页应用时,ie在地址栏出现的中文参数,在使用分页重新进行地址引用时,就会出现如下的结果:
    问题就出现在struts2对于url标签,在进行地址重现时的处理手段上,在解析地址信息时(通常是get形式),它并没有使用标签的getParameter形式,而是使用了getQueryString形式。

    对于通常的解析当前地址信息,通常的使用手段即将当前请求的scheme,uri地址,以及端口,参数信息,直接拼接成一个url地址即可。这其中最主要的还是拼接参数的问题,即要将参数从请求中取出来,并重新拼装在url地址上,以保证和刚才的请求地址一致。在struts2中,对于参数信息,它处理了3种参数:

  1. 直接追加在uri地址中的参数,称之为get参数
  2. 使用<s:param/>显式传递的参数
  3. 请求body中的参数,称之为post参数

   对于第二种和第三种参数,struts2在解析上都没有问题,但对于第1种,在解析上就有问题了。在ie中,使用<s:url/>显式的当前地址信息为:

http://192.168.10.22:4444/admin/calc/do/calcSellGoodsbyBatch.j?1=1&operation=%3F%A8%B2%3F%3F
&startDate=2011-07-01&endDate=2011-07-13&page.currentPage=2

    其中,'销售'参数值变成了%3F%A8%B2%3F%3F,这显然是一个错误的码值,它和正常的GBK编码%CF%FA%CA%DB差距太远了,其中3F表示不能识别的编码。

    struts2的url标签参数拼接过程

    对于这种情况,我们首先还是得看struts2是如何解析get参数信息,以及如何将参数重新拼接在url上的。这个拼接过程是由类ServletUrlRenderer(基于struts2 2.1.8.1版本)来完成的。

    首先看其中的beforeRenderUrl方法,这里面对于使用了includeGetParameters(urlComponent);方法来进行get参数处理,来看其中的处理过程:

    private void includeGetParameters(UrlProvider urlComponent) {
    	String query = extractQueryString(urlComponent);
    	mergeRequestParameters(urlComponent.getValue(), urlComponent.getParameters(), UrlHelper.parseQueryString(query));
    }

    这里面关注两个信息,一是取得参数字符串,第二是根据参数字符串取得参数信息。在取得参数字符串中(方法为extractQueryString,使用了以下代码来取得参数字符串:

String query = urlComponent.getHttpServletRequest().getQueryString();

    即直接使用了request的queryString,需要注意的是,使用该方法取得的queryString是没有经过编码和解码的,即取得的信息是直接来自于浏览器所发送的请求信息。当然对于struts2来说,肯定不会直接使用里面未经解码的信息,而是会再一步处理,将这个参数字符串进行分离(即根据&符号分离,并进行解码),整个过程如下所示:

    public static Map parseQueryString(String queryString, boolean forceValueArray) {
        Map queryParams = new LinkedHashMap();
        if (queryString != null) {
            String[] params = queryString.split("&");
            for (int a=0; a< params.length; a++) {
                    String paramName = null;
                    String paramValue = "";
......
                        String translatedParamValue = translateAndDecode(paramValue);
......
                            queryParams.put(paramName, translatedParamValue);
        return queryParams;
    }

    这个过程中,对参数值进行了解码,但这种解码过程使用的是标准的解码过程,即:

URLDecoder.decode(translatedInput, encoding)

    直接使用URLDecoder进行解码,这种解码要求,编码值是以%XX%XX的方式进行编码的。对于firefox,因为是%CF%FA,在进行解码时使用gbk解码自然不会错,但对于IE,因为是CFFA,解码时自然就出错了,出错之后,直接返回原来的数据,即还是 CF FA。这种解码相对于tomcat来说,简单了一点,tomcat在解析中,同时处理了带%格式和不带%格式。

    在这次解析之后,'销售'的参数值被解析中 char[4]{CF FA CA DB}的形式(其中的CF为byte的16进制值)。即包含4个字符的数据信息。

    参数再次编码
    在取得参数之后,传递到客户端时,参数会被再次编码,即将刚才的参数信息,按照客户端可以识别的方式,再次进行编码,以确保传递给客户端时都是编码的url编码形式。此过程在方法renderUrl(Writer writer, UrlProvider urlComponent)中调用方法UrlHelper.buildUrl(_value, urlComponent.getHttpServletRequest(), urlComponent.getHttpServletResponse(), urlComponent.getParameters(), scheme, urlComponent.isIncludeContext(), urlComponent.isEncode(), urlComponent.isForceAddSchemeHostAndPort(), urlComponent.isEscapeAmp())来实现的。
    在方法UrlHelper.buildUrl中,会对参数值进行拼接,拼接过程由方法buildParametersString(params, link)来进行实现,在此实现中,针对每一个参数,通过方法buildParameterSubstring(String name, String value)来进行实现,我们查看其中的实现情况:

    private static String buildParameterSubstring(String name, String value) {
        StringBuilder builder = new StringBuilder();
        builder.append(name);
        builder.append('=');
        builder.append(translateAndEncode(value));

        return builder.toString();
    }

    在这个实现中就会进行编码了,我们刚才取得的参数值 CF FA CA DB,写成字符串就是 ÏúÊÛ,其中第二个字符ú表示汉语中的韵母,可以进行gbk编码,另外3个就不能进行编码了,不能编码的字符被编码成3F,所以整个字符串即被编码成

  • Ï->%3F
  • ú->%A8%B2
  • Ê->%3F
  • Û->%3F

的形式,这就是我们在ie浏览器中显示的数据信息了。

解决问题的方法

    解决问题最简单的方法,就是不要让ie浏览器直接传递未经url编码的参数,但我们又需要传递中文参数,这时可以在传递参数前,先对参数进行编码,编码成url形式,这样在struts2处理中,就能正确的进行解码和识别了。如使用struts2标签进行处理,即可以这样使用:

<a href="<%=request.getContextPath()%>/admin/calc/do/calcSellGoodsbyBatch.j?1=1
&operation=<s:property value="@java.net.URLEncoder@encode(f_operation,'GBK')"/>
&startDate=<s:date name="startDate" format="yyyy-MM-dd"/>
&endDate=<s:date name="endDate" format="yyyy-MM-dd"/>>xxx</a>

    中间使用URLEncoder对中文进行编码,即可。

    当然,另外一种方法,就是修改这个struts2对于url及urlRender的实现,其实就是get参数来说,使用struts2进行编码,还不如直接使用request.getParameter来进行处理。这时request.getParamter中的参数信息其实就是queryString的参数信息。在使用request.getParameter,在进行取值时,相应的数据已经进行了正确的解码,取得的数据就和业务中处理的数据一致了。不懂为什么struts2还单独实现一次解码操作,是否未免多余?

转载请标明出处:i flym
本文地址:https://www.iflym.com/index.php/code/resolve-error-codec-problem-while-use-s-url-or-s-a-on-struts2.html

相关文章:

作者: flym

I am flym,the master of the site:)

《解决使用struts2标签s:url以及s:a时中文产生乱码的问题》有2个想法

  1. 敢问,如何让分页的参数变化,其他的参数不变?除了把一个个参数读取出来。或者直接replace

发表评论

电子邮件地址不会被公开。 必填项已用*标注