How to read and copy the HTTP servlet response output stream content for logging

JavaServletsLoggingServlet Filters

Java Problem Overview


I've created a filter to in my java webserver (appengine actually) that logs the parameters of an incoming request. I'd also like to log the resulting response that my webserver writes. Although I have access to the response object, I'm not sure how to get the actual string/content response out of it.

Any ideas?

Java Solutions


Solution 1 - Java

You need to create a Filter wherein you wrap the ServletResponse argument with a custom HttpServletResponseWrapper implementation wherein you override the getOutputStream() and getWriter() to return a custom ServletOutputStream implementation wherein you copy the written byte(s) in the base abstract OutputStream#write(int b) method. Then, you pass the wrapped custom HttpServletResponseWrapper to the FilterChain#doFilter() call instead and finally you should be able to get the copied response after the the call.

In other words, the Filter:

@WebFilter("/*")
public class ResponseLogger implements Filter {

    @Override
    public void init(FilterConfig config) throws ServletException {
        // NOOP.
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
        if (response.getCharacterEncoding() == null) {
            response.setCharacterEncoding("UTF-8"); // Or whatever default. UTF-8 is good for World Domination.
        }

        HttpServletResponseCopier responseCopier = new HttpServletResponseCopier((HttpServletResponse) response);

        try {
            chain.doFilter(request, responseCopier);
            responseCopier.flushBuffer();
        } finally {
            byte[] copy = responseCopier.getCopy();
            System.out.println(new String(copy, response.getCharacterEncoding())); // Do your logging job here. This is just a basic example.
        }
    }

    @Override
    public void destroy() {
        // NOOP.
    }

}

The custom HttpServletResponseWrapper:

public class HttpServletResponseCopier extends HttpServletResponseWrapper {

    private ServletOutputStream outputStream;
    private PrintWriter writer;
    private ServletOutputStreamCopier copier;

    public HttpServletResponseCopier(HttpServletResponse response) throws IOException {
        super(response);
    }

    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        if (writer != null) {
            throw new IllegalStateException("getWriter() has already been called on this response.");
        }

        if (outputStream == null) {
            outputStream = getResponse().getOutputStream();
            copier = new ServletOutputStreamCopier(outputStream);
        }

        return copier;
    }

    @Override
    public PrintWriter getWriter() throws IOException {
        if (outputStream != null) {
            throw new IllegalStateException("getOutputStream() has already been called on this response.");
        }

        if (writer == null) {
            copier = new ServletOutputStreamCopier(getResponse().getOutputStream());
            writer = new PrintWriter(new OutputStreamWriter(copier, getResponse().getCharacterEncoding()), true);
        }

        return writer;
    }

    @Override
    public void flushBuffer() throws IOException {
        if (writer != null) {
            writer.flush();
        } else if (outputStream != null) {
            copier.flush();
        }
    }

    public byte[] getCopy() {
        if (copier != null) {
            return copier.getCopy();
        } else {
            return new byte[0];
        }
    }

}

The custom ServletOutputStream:

public class ServletOutputStreamCopier extends ServletOutputStream {

    private OutputStream outputStream;
    private ByteArrayOutputStream copy;

    public ServletOutputStreamCopier(OutputStream outputStream) {
        this.outputStream = outputStream;
        this.copy = new ByteArrayOutputStream(1024);
    }

    @Override
    public void write(int b) throws IOException {
        outputStream.write(b);
        copy.write(b);
    }

    public byte[] getCopy() {
        return copy.toByteArray();
    }

}

Solution 2 - Java

BalusC solution is ok, but little outdated. Spring now has feature for it . All you need to do is use [ContentCachingResponseWrapper], which has method public byte[] getContentAsByteArray() .

I Suggest to make WrapperFactory which will allow to make it configurable, whether to use default ResponseWrapper or ContentCachingResponseWrapper.

Solution 3 - Java

Instead of creating Custom HttpServletResponseWrapper.You can use ContentCachingResponseWrapper as it provide method getContentAsByteArray().

public void doFilterInternal(HttpServletRequest servletRequest, HttpServletResponse servletResponse,
			FilterChain filterChain) throws IOException, ServletException {
		
		HttpServletRequest request = servletRequest;
		HttpServletResponse response = servletResponse;
		ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
		ContentCachingResponseWrapper responseWrapper =new ContentCachingResponseWrapper(response);
		try {
			super.doFilterInternal(requestWrapper, responseWrapper, filterChain);
			
		} finally {

			byte[] responseArray=responseWrapper.getContentAsByteArray();
			String responseStr=new String(responseArray,responseWrapper.getCharacterEncoding());
			System.out.println("string"+responseStr);		
            /*It is important to copy cached reponse body back to response stream
            to see response */
            responseWrapper.copyBodyToResponse();
			
		}

	}

Solution 4 - Java

While BalusC's answer will work in most scenarios you have to be careful with the flush call - it commits response and no other writing to it is possible, eg. via following filters. We have found some problems with very simmilar approach in Websphere environment where the delivered response was only partial.

According to this question the flush should not be called at all and you should let it be called internally.

I have solved the flush problem by using TeeWriter (it splits stream into 2 streams) and using non-buffering streams in the "branched stream" for logging purpose. It is unneccessary to call the flush then.

private HttpServletResponse wrapResponseForLogging(HttpServletResponse response, final Writer branchedWriter) {
    return new HttpServletResponseWrapper(response) {
        PrintWriter writer;

        @Override
        public synchronized PrintWriter getWriter() throws IOException {
            if (writer == null) {
                writer = new PrintWriter(new TeeWriter(super.getWriter(), branchedWriter));
            }
            return writer;
        }
    };
}

Then you can use it this way:

protected void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException {
    //...
    StringBuilderWriter branchedWriter = new org.apache.commons.io.output.StringBuilderWriter();
    try {
        chain.doFilter(request, wrapResponseForLogging(response, branchedWriter));
    } finally {
        log.trace("Response: " + branchedWriter);
    }
}

The code is simplified for brewity.

Solution 5 - Java

I am not quite familiar with appengine but you need something Access Log Valve in Tomcat. Its attribute pattern ; a formatting layout identifying the various information fields from the request and response to be logged, or the word common or combined to select a standard format.

It looks appengine has built in functionality for log filtering.

apply a servlet filter

Solution 6 - Java

If you just want the response payload as as String, I would go for:

final ReadableHttpServletResponse httpResponse = (ReadableHttpServletResponse) response;
final byte[] data = httpResponse.readPayload();
System.out.println(new String(data));

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionalooView Question on Stackoverflow
Solution 1 - JavaBalusCView Answer on Stackoverflow
Solution 2 - JavaomilusView Answer on Stackoverflow
Solution 3 - JavaShrinivasanView Answer on Stackoverflow
Solution 4 - JavaPetr ÚjezdskýView Answer on Stackoverflow
Solution 5 - JavaMuhammad Imran TariqView Answer on Stackoverflow
Solution 6 - JavaLedzepp23View Answer on Stackoverflow