乐者为王

Do one thing, and do it well.

如何解决含有中文的页面末尾内容会被SiteMesh截掉的问题

首先,我们要了解一下ServletResponse.setContentLength(int len)的含义。setContentLength是设置返回内容体长度的方法,len是内容体的长度。由于网络上传输内容是以字节(byte)为单位的,所以len就是指内容体有多少个字节。假设现在有长度为100个字节的数据,在输出数据到客户端前我们用setContentLength(90)设置内容体的长度为90个字节。那么在客户端接收到的数据长度就是90个字节而不是100。下面我们来做个试验:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class PageTruncateFilter implements Filter {

    public void init(FilterConfig config) throws ServletException {
    }

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
                        throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)req;
        HttpServletResponse response = (HttpServletResponse)res;

        String content = "<html><head><title>Truncated Page</title></head>";
               content += "<body>这个页面的内容will be truncated.</body></html>";
        response.setCharacterEncoding("gbk");
        response.setContentLength(content.length());
        PrintWriter out = response.getWriter();
        out.println(content);
        out.close();
    }

    public void destroy() {
    }
}

由于content字符串含有两个中文字符,每一个中文字符又是由两个字节组成,所以content.length()的值比content的字节数要少2。在客户端显示的内容也会因此少2个字节,即缺少“l>”这两个字符。

现在我们来浏览SiteMesh的源代码找出问题的原因。首先从PageFilter.java的doFilter方法开始。由于页面没有在decorators.xml中注册修饰,所以writeOriginal方法会被调用。下面是writeOriginal方法的源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/** Write the original page data to the response. */
private void writeOriginal(HttpServletRequest request, HttpServletResponse response,
                          Page page) throws IOException {
    response.setContentLength(page.getContentLength());

    if (request.getAttribute(USING_STREAM).equals(Boolean.TRUE)) {
        PrintWriter writer = new PrintWriter(response.getOutputStream());
        page.writePage(writer);
        // flush writer to underlying outputStream
        writer.flush();
        response.getOutputStream().flush();
    } else {
        page.writePage(response.getWriter());
        response.getWriter().flush();
    }
}

首行代码就是设置内容体长度的,让我们追到page.getContentLength()里面去看看(Page接口的getContentLength方法是由AbstractPage.java实现的):

1
2
3
public int getContentLength() {
    return pageData.length;
}

它返回pageData数组的长度。但是pageData数组是char类型的,假如pageData中有中文内容时,设置的内容体长度就会小于pageData的字节数。这时就会出现输出到客户端的数据截掉的问题。所以只要修改getContentLength方法返回的值就可以了,下面是修改后的getContentLength方法:

1
2
3
4
public int getContentLength() {
    String content = new String(pageData);
    return content.getBytes().length;
}

Comments