捕获所有HTML输入内容以进行操作的Catch-all servletfilter只能间歇性地工作

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

  Filter mybrokenCaptureHtml.TokenFilter   Filter /*  

 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)", ""); 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内容返回零字节,但应该有内容。

                   <input type="button" styleClass="input_small" width="80" style="WIDTH:80px" name="" value="" onclick="javascript:cancel();">    

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


工作的示例servlet( a ):

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

无法工作的示例servlet( b ):

 response.getWriter().println("data"); 

示例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()然后out.flush()out.close() …看起来最后的字节写得正确。 它不直观但看起来很有效。

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

修复是在MyPrintWriter toByteArray()之前执行flush()调用:

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

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


具体问题无关 :这种方法存在一些严重问题。 它在构造PrintWriter不考虑响应字符编码 (实际上你应该将ByteArrayOutputStream包装在一个可以接受字符编码的OutputStreamWriter )并依赖于平台默认值,换句话说,任何写入的Unicode字符都可能最终出现在Mojibake以这种方式,因此这种方法还没有为世界统治做好准备。

此外,这种方法可以在同一响应上调用getWriter()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()); } } 

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

 @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)", ""); response.getWriter().write(replacedContent); // Don't ever use String#getBytes() without specifying character encoding! } 
Interesting Posts