Question

I try to implement a HttpServletResponseWrapper in order to read the content of a servlet response. I can successfully read to content in the filter. But the final packet which go out of the application has an empty body (check with browser and fiddler)

Implementation

class ReadableContentHttpServletResponse extends HttpServletResponseWrapper {
    private ByteArrayOutputStream outputStream;
    private ServletOutputStream servletOutputStream;
    
    
    public ReadableContentHttpServletResponse(HttpServletResponse response) {
        super(response);
        outputStream = new ByteArrayOutputStream();
        servletOutputStream = new ServletOutputStream() {
            private WriteListener writeListener = null;
            
            
            @Override
            public void write(int b) throws IOException {               
                outputStream.write(b);
                if (writeListener != null) {
                    writeListener.notify();
                }
            }
            
            @Override
            public void setWriteListener(WriteListener writeListener) {
                this.writeListener =  writeListener;
                
            }
            
            @Override
            public boolean isReady() {
                //ByteArrayOutputStream.close() has not effect 
                return true;
            }
        };
    }
    
    @Override
    public PrintWriter getWriter() throws IOException {
        return new PrintWriter(outputStream);
    }
    
    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        return servletOutputStream;     
    };
    
    //... Getters...    
}

The filter

...
ReadableContentHttpServletResponse wrappedResponse = new ReadableContentHttpServletResponse(httpResponse);
chain.doFilter(request,wrappedResponse);
log.debug("content = " + wrappedResponse.getContent()); //working
log.debug("content = " + wrappedResponse.getContent()); //working

I tried to read several time the ByteArrayOutputStream in the filter (which works) or not, but the final body's packet is still empty. I also tried with the ByteArrayOutputStream of apache.

Note that there is only one filter, and I removed everything that was coming after the dofilter.

EDIT: The stream used to generate the final packet is not the ServletOutputStream of the wrapper. It is the ServletOutputStream of the original HttpServletResponse response.

Why? Sincethe wrapper overloads the ServletResponsemethods and is given to dofilter(..), it should use the stream returned by the wrapper.

Was it helpful?

Solution 2

The outputStream used to generate the final packet is not the one in the the wrapper but the one in the original ServletResponse object (kept in super class ServletResponseWrapper). We just need to write to both stream.

This behavior looks very strange to me since dofilter return the wrapper's reference. You are welcome if you have any explanation.

The workaround:

class ReadableContentHttpServletResponse extends HttpServletResponseWrapper {
    private ByteArrayOutputStream outputStream;
    private ServletOutputStream servletOutputStream;



    public ReadableContentHttpServletResponse(HttpServletResponse response) throws IOException {
        super(response);

        outputStream = new ByteArrayOutputStream();
        final ServletOutputStream  responseOutputStream = response.getOutputStream();

        servletOutputStream = new ServletOutputStream() {
            private WriteListener writeListener = null;


            @Override
            public void write(int b) throws IOException {               
                outputStream.write(b);
                responseOutputStream.write(b);
                if (writeListener != null) {
                    writeListener.notify();
                }
            }


...

OTHER TIPS

It's always the ServletOutputStream of the original ServletResponse that is used by the container in order to actually send anything to the client.

Let's assume there are three Filters, F1, F2 and F3 in a particular FilterChain and that F2 wishes to modify the data produced by whatever resource is at the end of the chain. Following events would in that case occur:

  • F1's doFilter() is invoked with instance R as the ServletResponse argument, which in turn holds a reference to ServletOutputStream S. F1 calls FilterChain.doFilter(..., R).
  • F2's doFilter() is as well invoked with R and S. Because F2 wants to modify the response, it wraps R in an e.g. HttpServletResponseWrapper implementation R' providing a stand-in ServletOutputStream S'. F2 calls FilterChain.doFilter(..., R').
  • F3's doFilter() is thus invoked with arguments R' and S' and calls `FilterChain.doFilter(..., R').
  • The resource targeted by the client's request writes data to S'.
  • F3's doFilter() returns.
  • F2 flushes S', does something with its contents and writes the modified payload to S. It can't really do otherwise, unless it purposely wants F1 -and ultimately the client- to receive an empty response. F2's `doFilter() returns.
  • F1's doFilter() returns and the contents of S are written back to the client.
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top