Catch-all servlet过滤器应该捕获所有HTML输入内容以进行操作,只能间歇性地工作

时间:2014-04-22 21:31:26

标签: java servlets struts servlet-filters html-manipulation

我需要一个servlet过滤器来捕获所有输入,然后修改输入,在每个表单中插入一个特殊标记。想象一下,过滤器与所有请求相关联(例如url-pattern=*)。我有捕获内容的代码,但似乎RequestWrapper不足以捕获所有输入。某些输入返回零字节,然后我无法将该内容“流”回给用户。例如,我们仍在使用Struts 1.3.10,并且任何Struts代码都没有正确“捕获”,我们得到零字节内容。我相信这是因为Struts如何处理前锋。如果请求中涉及转发,我想知道下面的捕获代码是否有效。以下是所有代码,您是否有一种方法可以捕获用于流式传输给用户的任何类型的内容。

<filter>
   <filter-name>Filter</filter-name>
   <filter-class>mybrokenCaptureHtml.TokenFilter</filter-class>
</filter> 
<filter-mapping>
    <filter-name>Filter</filter-name>
    <url-pattern>/*</url-pattern>
 </filter-mapping>

package mybrokenCaptureHtml;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

public class TokenFilter implements Filter {    
    @Override
    public void destroy() {
    }

    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;               
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        try {                                                                                       
            final MyResponseWrapper responseWrapper = new MyResponseWrapper((HttpServletResponse) response);
            chain.doFilter(request, responseWrapper);                       

            // **HERE DEPENDING ON THE SERVLET OR APPLICATION CODE (STRUTS, WICKET), the response returns an empty string //
            // Especiall struts, is there something in their forwards that would cause an error?
            final byte [] bytes = responseWrapper.toByteArray(); 
                    // For some applications that hit this filter
                    // ZERO BYTE DATA is returned, this is bad, but SOME
                    // CODE, the data is captured.
            final String origHtml = new String(bytes);

            final String newHtml = origHtml.replaceAll("(?i)</(\\s)*form(\\s)*>", "<input type=\"hidden\" name=\"zval\" value=\"fromSiteZ123\"/></form>");          
            response.getOutputStream().write(newHtml.getBytes());

        } catch(final Exception e) {            
            e.printStackTrace();
        }
        return;
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {        
    }

    static class MyResponseWrapper extends HttpServletResponseWrapper {    
        private final MyPrintWriter pw = new MyPrintWriter();               
        public byte [] toByteArray() {            
            return pw.toByteArray();        
        }
        public MyResponseWrapper(HttpServletResponse response) {
            super(response);       
        }

        @Override
        public PrintWriter getWriter() {
            return pw.getWriter();
        }
        @Override
        public ServletOutputStream getOutputStream() {
            return pw.getStream();
        }       
        private static class MyPrintWriter {
            private ByteArrayOutputStream baos = new ByteArrayOutputStream();
            private PrintWriter pw = new PrintWriter(baos);
            private ServletOutputStream sos = new MyServletStream(baos);
            public PrintWriter getWriter() {
                return pw;
            }
            public ServletOutputStream getStream() {
                return sos;
            }
            byte[] toByteArray() {
                return baos.toByteArray();
            }
        }    
        private static class MyServletStream extends ServletOutputStream {
            ByteArrayOutputStream baos;
            MyServletStream(final ByteArrayOutputStream baos) {
                this.baos = baos;
            }
            @Override
            public void write(final int param) throws IOException {
                baos.write(param);
            }
        }
    }

}

这就是Struts应用程序的示例,对于某些应用程序(不是Struts),我们可能会捕获内容。但对于像下面这样的应用程序,HTML内容会返回零字节,但应该有内容。

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>
<%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %>
<%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles" %>
<%@ taglib uri="/WEB-INF/struts-nested.tld" prefix="nested"%>
<html:html>
<head>
<title><bean:message key="myApp.customization.title" /></title>
<LINK rel="stylesheet" type="text/css" href="../theme/styles.css">
</head>
<body>
<html:form styleId="customizemyAppForm" method="post" action="/customizemyApp.do?step=submit">
<html:submit onclick="javascript:finish(this.form);" styleClass="input_small">&nbsp;&nbsp;<bean:message key="myApp.customization.submit" />&nbsp;</html:submit> 
<input type="button" styleClass="input_small" width="80" style="WIDTH:80px" name="<bean:message key="myApp.customization.cancel" />" value="<bean:message key="myApp.customization.cancel" />" onclick="javascript:cancel();">

</html:form>
</body>
</html:html>

我怀疑MyResponseWrapperMyPrintWriter不够强大,无法捕获所有类型的内容。


可行的示例servlet( a ):

response.getOutputStream().write(str.getBytes());

不起作用的示例servlet( b ):

response.getWriter().println("<html>data</html>");

示例 a 会获得捕获,示例 b 则不会。

这是一个改进的包装类,大多数应用程序都可以工作但是现在有些struts应用程序,只有一些响应被发送到浏览器。

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

public class ByteArrayResponseWrapper extends HttpServletResponseWrapper {
    private PrintWriter output = null;
    private ServletOutputStream outStream = null;
    private static final String NL = System.getProperty("line.separator");

    public ByteArrayResponseWrapper(final HttpServletResponse response) {
        super(response);
    }

    public String getDocument() {        
        InputStream in = null;
        try {            
            in = this.getInputStream();            
            if (in != null) {             
                return getDocument(in);
            }           
        } catch(final Exception ee) {
            // ee.print;StackTrace();
        } finally {                  
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    //e.prin;tStackTrace();
                }
            }
        }
        return "";    
    }

    protected String getDocument(final InputStream in) {
        final StringBuffer buf = new StringBuffer();
        BufferedReader br = null;
        try {
            String line = "";
            br = new BufferedReader(new InputStreamReader(getInputStream(), this.getCharacterEncoding()));            
            while ((line = br.readLine()) != null) {
                buf.append(line).append(NL);                
            }
        } catch(final IOException e) {
            //e.print;StackTrace();
        } finally {
            try {
                if (br != null) {
                    br.close();
                }
            } catch (IOException ex) {             
            }
        }
        return buf.toString();
    }

    @Override
    public PrintWriter getWriter() throws IOException {
        if (output == null) {
            output = new PrintWriter(new OutputStreamWriter(getOutputStream(), this.getCharacterEncoding()));
        }
        return output;
    }

    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        if (outStream == null) {
            outStream = new BufferingServletOutputStream();
        }
        return outStream;
    }

    public InputStream getInputStream() throws IOException {
        final BufferingServletOutputStream out = (BufferingServletOutputStream) getOutputStream();        
        return new ByteArrayInputStream(out.getBuffer().toByteArray());
    }

    /**
     * Implementation of ServletOutputStream that handles the in-memory
     * buffering of the response content
     */
    public static class BufferingServletOutputStream extends ServletOutputStream {
        ByteArrayOutputStream out = null;

        public BufferingServletOutputStream() {
            this.out = new ByteArrayOutputStream();
        }

        public ByteArrayOutputStream getBuffer() {
            return out;
        }

        public void write(int b) throws IOException {
            out.write(b);
        }

        public void write(byte[] b) throws IOException {
            out.write(b);
        }

        public void write(byte[] b, int off, int len) throws IOException {
            out.write(b, off, len);
        }
        @Override
        public void close() throws IOException {
            out.close();
            super.close();
        }
        @Override
        public void flush() throws IOException {
            out.flush();
            super.flush();
        }
    }
}

我找到了一个可能的解决方案,在getInputStream方法中,看起来我在所有对象上调用'close',例如outStream.flush()outStream.close()然后{{1 }和out.flush() ...看起来最后的字节写得正确。它不直观但看起来很有效。

1 个答案:

答案 0 :(得分:5)

您的初始方法失败,因为PrintWriter wraps给定的ByteArrayOutputStream BufferedWriterinternal character buffer of 8192 characters,您永远不会flush()ByteArrayOutputStream获取字节之前的缓冲区。换句话说,当少于约8KB的数据写入响应的getWriter()时,包裹的ByteArrayOutputStream实际上永远不会被填充;即一切仍然在那个内部字符缓冲区中,等待被刷新。

修复方法是在flush()toByteArray()之前执行MyPrintWriter来电:

byte[] toByteArray() {
    pw.flush();
    return baos.toByteArray();
}

这样,内部字符缓冲区将被刷新(即实际将所有内容写入包装流)。这也完全解释了为什么它在写getOutputStream()时起作用,这一步即没有使用PrintWriter,并且没有任何东西在某个内部缓冲区中被缓冲。


无关具体问题:这种方法存在一些严重问题。在构造PrintWriter期间,它并不尊重response character encoding(您实际上应该将ByteArrayOutputStream包裹在OutputStreamWriter中而不是可以进行字符编码)并依赖于getWriter()平台默认,换句话说,任何书面的Unicode字符都可能以Mojibake这种方式结束,因此这种方法不适合世界统治。

此外,这种方法可以在同一个响应中同时调用getOutputStream()public class CapturingResponseWrapper extends HttpServletResponseWrapper { private final ByteArrayOutputStream capture; private ServletOutputStream output; private PrintWriter writer; public CapturingResponseWrapper(HttpServletResponse response) { super(response); capture = new ByteArrayOutputStream(response.getBufferSize()); } @Override public ServletOutputStream getOutputStream() { if (writer != null) { throw new IllegalStateException("getWriter() has already been called on this response."); } if (output == null) { output = new ServletOutputStream() { @Override public void write(int b) throws IOException { capture.write(b); } @Override public void flush() throws IOException { capture.flush(); } @Override public void close() throws IOException { capture.close(); } }; } return output; } @Override public PrintWriter getWriter() throws IOException { if (output != null) { throw new IllegalStateException("getOutputStream() has already been called on this response."); } if (writer == null) { writer = new PrintWriter(new OutputStreamWriter(capture, getCharacterEncoding())); } return writer; } @Override public void flushBuffer() throws IOException { super.flushBuffer(); if (writer != null) { writer.flush(); } else if (output != null) { output.flush(); } } public byte[] getCaptureAsBytes() throws IOException { if (writer != null) { writer.close(); } else if (output != null) { output.close(); } return capture.toByteArray(); } public String getCaptureAsString() throws IOException { return new String(getCaptureAsBytes(), getCharacterEncoding()); } } ,同时considered an illegal state(正是为了避免这种缓冲和编码)麻烦)。


根据评论

更新,这里是对响应包装器的完全重写,显示正确的方式,希望以比您自己编写的代码更自我解释的方式远:

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    CapturingResponseWrapper capturingResponseWrapper = new CapturingResponseWrapper((HttpServletResponse) response);
    chain.doFilter(request, capturingResponseWrapper);
    String content = capturingResponseWrapper.getCaptureAsString(); // This uses response character encoding.
    String replacedContent = content.replaceAll("(?i)</form(\\s)*>", "<input type=\"hidden\" name=\"zval\" value=\"fromSiteZ123\"/></form>");
    response.getWriter().write(replacedContent); // Don't ever use String#getBytes() without specifying character encoding!
}

以下是您应该如何使用它:

{{1}}