`
hepu
  • 浏览: 54769 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
社区版块
存档分类
最新评论

关于Filter过滤器和Wrapper响应封装类的问题说明

    博客分类:
  • J2EE
阅读更多

在进行网站开发时需要对页面输出的信息内容进行过滤,于是编写了一个Filter过滤器由于拦截JSP和 Servlet的请求,并使用响应HttpServletResponseWrapper封装类现实响应数据的捕获。在实际应用过程中发现,在获取响应数 据并进行内容过滤后,要把数据写回response对象响应客户端时:

(1)使用tomcat5容器调用response.getOutputStream()方法即可实现,但调用 requonse.getWriter()方法时,输出二进制数据时(图片等内容无法显示)则出现“getWriter() has already been called for this response”异常。
(2)使用tomcat6容器调用response.getOutputStream()方法时有中文字符会发 生“java.io.CharConversionException:Not an ISO 8859-1 character:”异常,调用requonse.getWriter()方法时可实现文本字符串数据输出,调用 response.getOutputStream()方法可现实字节流数据的输出。
    就上述出现的问题进行分析研究,阅读了tomcat6的源代码发现,在调用response.getOutputStream()方法时会判断是否已调用 了requonse.getWriter()方法;相反在调用requonse.getWriter()方法时会判断是否已调用了 response.getOutputStream()方法。
    在tomcat5时并没有出现这个问题,使用response.getOutputStream()方法可现实两种数据输出,只是在使用 requonse.getWriter()时发生异常,而在tomcat6下则必须针对不同的数据类型选择相应输出流,这时为什么呢?仔细阅读 tomcat6源代码没有发现问题的根源,给出的参考时:在一次客户端请求的响应动作中,只能调用一种响应输出方法,要么是getWriter()要么是 getOutputStream(),且如果使用getOutputStream()方法输出字符串格式的数据时,中文无法正常通过将发生 “java.io.CharConversionException:Not an ISO 8859-1 character:”异常,在tomcat5下没有对getOutputStream()方法进行严格控制,中文字符串可正常通过。可见tomcat6 的安全机制比tomcat5要严格,对于字符串格式的数据要求使用getWriter()方法输出响应,如果使用了getOutputStream()方 法输出响应,则对输出的字符串数据进验证,要求高字节必须为0,显然中文是无法通过的。
 
源代码分析:
    //tomcat6中对用getOutputStream()方法的实现
    public ServletOutputStream getOutputStream()
        throws IOException {
 
        if (usingWriter)
            throw new IllegalStateException
                (sm.getString("coyoteResponse.getOutputStream.ise"));
 
        usingOutputStream = true;
        if (outputStream == null) {
            outputStream = new CoyoteOutputStream(outputBuffer);
        }
        return outputStream;
    }
 
 
    //tomcat6中对用getWriter ()方法的实现
    public PrintWriter getWriter()
        throws IOException {
 
        if (usingOutputStream)
            throw new IllegalStateException
                (sm.getString("coyoteResponse.getWriter.ise"));
 
        if (Globals.STRICT_SERVLET_COMPLIANCE) {    
            setCharacterEncoding(getCharacterEncoding());
        }
 
        usingWriter = true;
        outputBuffer.checkConverter();
        if (writer == null) {
            writer = new CoyoteWriter(outputBuffer);
        }
        return writer;
    }
 
 
//tomcat6中的ServletOutputStream对象的实现,其中print(String s)方法的实现
public void print(String s) throws IOException {
       if (s==null) s="null";
       int len = s.length();
       for (int i = 0; i < len; i++) {
           char c = s.charAt (i);
 
           if ((c & 0xff00) != 0) {  // high order byte must be zero
              String errMsg = lStrings.getString("err.not_iso8859_1");
              Object[] errArgs = new Object[1];
              errArgs[0] = new Character(c);
              errMsg = MessageFormat.format(errMsg, errArgs);
              throw new CharConversionException(errMsg);
           }
           write (c);
       }
}
 
    经过上述分析可知tomcat6的安全性提高了两个响应输出流是互锁关系,JSP或Servlet中到底调用了那个方法响应请求?能通过方式知道呢?难道因为提高了安全性就没有办法解决这个问题了吗?
    回过头再看看我们的过滤器和封装类的调用关系,在过滤器拦截下请求后,请求将暂时停留在过滤器的处理链中,为了获取响应数据使用了 HttpServletResponseWrapper封装类,目的是将响应的输出写入封装类中,而不是直接写入到 HttpServletResponse对象里返回给客户端,我们要对信息进行过滤,恰恰在获得响应数据后对响应的数据进行内容过滤,然后再送往客户端。
    在正常情况下tomcat容器connector处理一次客户端http请求时的处理流程是:创建HttpServletRequest请求、 HttpServletResponse响应和ClientJSPorServlet客户JSP或Servlet对象实例,然后调用service方法完 成处理。加入过滤器Filter和封装类Wrapper后情况有所改变,使调用过程发生了变化。容器在初始化时先创建Filter对象示例,当用客户端请 求时将先转给Filter进行处理(根据过滤器的url-pattern设置拦截那些请求),由Filter过滤器决定滞后的处理流程(或正常处理或中 断、转向等有代码控制);Filter将HttpServletRequest请求和封装后的HttpServletResponseWrapper响应 转给客户JSP或Servlet对象进行处理,处理结束后又回到过滤器,我们在这里加入信息过滤处理过程,然后再将数据写回 HttpServletResponse对象,响应客户端。
    由此可见,HttpServletResponseWrapper是一个封装后的“假”响应对象,我们真是使用假的响应对象获取了响应数据,只是在写回数 据调用输出流时发生了问题:在JSP或Servket中调用了getWriter()或getOutputStream()方法时,如果在Wrapper 中现实了(覆盖)这两个方法则调用Wrapper中的方法,如果没有实现,则相当于调用HttpServletResponse中方法;而我们在写回数据 时必须调用getWriter()或getOutputStream()方法。
    因此,tomcat6下:
(1)在Wrapper中现实getWriter()方法截获响应数据时,JSP或Servlet调用 getWriter()方法将数据写入Wrapper对象的缓存,处理后再调用HttpServletResponse的getWriter()方法写回 数据正常通过;但调用HttpServletResponse的getOutputStream()时由于数据是字符串流,因此中文无法通过。
(2)在Wrapper中现实getOutputStream ()方法截获响应数据时,JSP或Servlet调用getOutputStream ()方法将数据写入Wrapper对象的缓存,处理后再调用HttpServletResponse的getOutputStream ()方法写回数据正常通过。
 
    如何知道JSP或Servlet对用了那个方法呢?我们在Wrapper中实现getOutputStream()和getWriter()两个方法,并 使用标识变量标识那个方法被调用,在截获响应数据后,判断Wrapper中的那个方法被调用了,以决定何种方式读出响应数据,处理后调用 HttpServletResponse的对应输出流写回数据。
 
//实现了两种方式的HttpServletResponse封装类
public class FilterResponseWrapper
    extends HttpServletResponseWrapper {
   
    /**
     * 使用 OutputStream 输出数据
     */
    private boolean usingOutputStream=false;
   
    /**
     * 使用 PrintWriter 输出数据
     */
    private boolean usingWriter=false;
   
   
    /**
     * 输出字节流的对象,用于数据输出
     */
    private ByteArrayOutputStream outstream;
   
    /**
     * 输出字符串文本的对象,用与JSP页面的输出
     */
    private CharArrayWriter output;
   
    /**
     * 返回字符串格式数据
     * @return String
     */
    public String toString() {
        return output.toString();
    }
   
    /**
     * 返回字节数组格式的数据
     * @return byte[]
     */
    public byte[] toByteArray(){
        return outstream.toByteArray();
    }
   
    /**
     *
     * @param response HttpServletResponse
     */
    public FilterResponseWrapper(HttpServletResponse response) {
        super(response);
        output = new CharArrayWriter();
        outstream = new ByteArrayOutputStream();
    }
   
    /**
     *
     * @return PrintWriter
     */
    public PrintWriter getWriter() {
        this.usingWriter=true;
        return new PrintWriter(output);
    }
   
   
    /**
     *
     * @return ServletOutputStream
     */
    public ServletOutputStream getOutputStream(){    
        this.usingOutputStream=true;
        return new ByteOutputStream(outstream);
    }
   
   
    /**
     *
     * @return boolean
     */
    public boolean isUsingOutputStream(){
        return this.usingOutputStream;
    }
   
    /**
     *
     * @return boolean
     */
    public boolean isUsingWriter(){
        return this.usingWriter;
    }
   
}
 
//
public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {
 
    FilterResponseWrapper wrapper = new FilterResponseWrapper( (HttpServletResponse)
            response);
    chain.doFilter(request, wrapper);
 
    //使用 PrintWriter 对象输出数据
    //进行内容过滤
    if(wrapper.isUsingWriter()){
        String buffer = word.process(wrapper.toString(),fileName);
        PrintWriter output = response.getWriter();
        output.print(buffer);
        output.flush();
     }
    
     //使用 ServletOutputStream 对象输出数据
     if(wrapper.isUsingOutputStream()){
         ServletOutputStream output = response.getOutputStream();
         output.write(wrapper.toByteArray());
         output.flush();
     }
     return;
}
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics