As mentioned on page 682 of HFSJ there are 3 steps in the implementation of doFilter method:
1. request filtering, before call to chain.doFilter
2. call to chain.doFilter(), which will call any other filters and the servlet.
3. response filtering after call to the servlet. At this point, we have already written something to the OutputStream of the response object passed to the service method (The OutputStream returned by response.getOutputStream). If this is the actual ServletOutputStream, then whatever we wrote is already sent back to the client. (As described on page 683, HFSJ). In this case, the response is already sent to the client, and there is no use filtering it now.
To avoid this, we ensure that the OutputStream available to the service method is not the ServletOutputStream of the response. We pass a wrapper object (HttpServletResponseWrapper) implementing HttpServletResponse to the call to chain.doFilter(), not the original HttpServletResponse. The getOutputSTream() method on this wrapper will return a different OutputStream - (may be a ByteArrayOutputStream??), not the ServletOutputStream. That means when the OutputStream received from the response wrapper is flushed, the response is not sent to the client. And when the flow of control returns to filter1, we have the data in the wrapper's OutputStream, and the actual response object to which we want to write this data. The compression / filtering is done, and then the data is written to the actual response OutputStream.