Catch-all servlet filter that should capture ALL HTML input content for manipulation, works only intermittently

核能气质少年 提交于 2019-12-03 08:04:00

Your initial approach failed because PrintWriter wraps the given ByteArrayOutputStream with a BufferedWriter which has an internal character buffer of 8192 characters, and you never flush() the buffer before getting the bytes from the ByteArrayOutputStream. In other words, when less than ~8KB of data is written to the getWriter() of the response, the wrapped ByteArrayOutputStream actually never get filled; namely everything is still in that internal character buffer, waiting to be flushed.

A fix would be to perform a flush() call before toByteArray() in your MyPrintWriter:

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

This way the internal character buffer will be flushed (i.e. it will actually write everything to the wrapped stream). This also totally explains why it works when you write to getOutputStream(), this step namely doesn't use the PrintWriter and nothing gets buffered in some internal buffer.


Unrelated to the concrete problem: this approach has some severe problems. It isn't respecting the response character encoding during construction of PrintWriter (you should actually wrap the ByteArrayOutputStream in an OutputStreamWriter instead which can take a character encoding) and relying on the platform default, in other words, any written Unicode characters may end up in Mojibake this way and thus this approach isn't ready for World Domination.

Also, this approach makes it possible to call both getWriter() and getOutputStream() on the same response, while that's considered an illegal state (precisely to avoid this kind of buffering and encoding trouble).


Update as per the comment, here's a full rewrite of the response wrapper, showing the right way, hopefully in a more self-explaining way than the code you've so far:

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());
    }

}

Here's how you're supposed to use it:

@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!
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!