Lab11: Wrapping Request and Response
The Servlet API provides the ability to wrap the request and response objects that are part of a single request lifecycle. This allows applications to perform custom actions such as decompress an incoming request, log request parameters/body etc, compress response, cache response etc.
The usual workflow involved is to use filter(s) to configure the paths in the application context that are to use the wrappers. The filters would then create the appropriate instances of the request or response wrapper, and then pass the wrappers instead of the original request/response down the filter chain.
For this exercise, we will modify the application developed in lab9/10 to log the request and response body content.
Input Stream
Create a new Java class mis283.container.LoggingInputStream. This class extends ServletInputStream and writes the next byte read off the stream into a buffer that we will print at the end of the read cycle.
package mis283.container; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import java.io.IOException; public class LoggingInputStream extends ServletInputStream { private final ServletInputStream inputStream; private final StringBuilder builder = new StringBuilder(1024); public LoggingInputStream(final ServletInputStream inputStream) { this.inputStream = inputStream; builder.append("Request body - "); } @Override public void close() throws IOException { System.out.println(builder); super.close(); } @Override public boolean isFinished() { return inputStream.isFinished(); } @Override public boolean isReady() { return inputStream.isReady(); } @Override public void setReadListener(final ReadListener readListener) { inputStream.setReadListener(readListener); } @Override public int read() throws IOException { final int value = inputStream.read(); if (value != -1) builder.append((char)value); return value; } }
Input Wrapper
Create a new class mis283.container.LoggingRequestWrapper. This class extends HttpServletRequestWrapper and will return instances of our custom input stream when the caller tries to retrieve it.
package mis283.container; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public class LoggingRequestWrapper extends HttpServletRequestWrapper { private LoggingInputStream inputStream; private BufferedReader reader; public LoggingRequestWrapper(HttpServletRequest request) { super(request); } @Override public ServletInputStream getInputStream() throws IOException { if (reader != null) throw new IllegalStateException( "getReader() has already been called for this request" ); if (inputStream != null) return inputStream; inputStream = new LoggingInputStream(getRequest().getInputStream()); return inputStream; } @Override public BufferedReader getReader() throws IOException { if (inputStream != null) throw new IllegalStateException( "getInputStream() has already been called for this request" ); if (reader != null) return reader; reader = new BufferedReader(new InputStreamReader(getInputStream())); return reader; } }
Output Stream
Create a new java class mis283.container.LoggingOutputStream. This class extends ServletOutputStream and prints out the current character being written to the socket stream to the console. Note that we are not able to accumulate and write since Jersey does not immediately call the close or flush methods on the output stream.
package mis283.container; import javax.servlet.ServletOutputStream; import javax.servlet.WriteListener; import java.io.IOException; public class LoggingOutputStream extends ServletOutputStream { private final ServletOutputStream outputStream; public LoggingOutputStream(final ServletOutputStream outputStream) { this.outputStream = outputStream; System.out.print("Response body - "); } @Override public boolean isReady() { return outputStream.isReady(); } @Override public void setWriteListener(final WriteListener writeListener) { outputStream.setWriteListener(writeListener); } @Override public void write(int b) throws IOException { System.out.print((char)b); outputStream.write(b); } }
Response Wrapper
Create a new class mis283.container.LoggingResponseWrapper. This class extends HttpServletResponseWrapper and creates a new instance of our custom logging output stream which is then passed down the filter chain.
package mis283.container; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; import java.io.IOException; import java.io.PrintWriter; public class LoggingResponseWrapper extends HttpServletResponseWrapper { private LoggingOutputStream outputStream; private PrintWriter writer; public LoggingResponseWrapper(final HttpServletResponse response) { super(response); } @Override public ServletOutputStream getOutputStream() throws IOException { if (writer != null) throw new IllegalStateException( "getWriter() has already been called for this request" ); if (outputStream != null) return outputStream; outputStream = new LoggingOutputStream(getResponse().getOutputStream()); return outputStream; } @Override public PrintWriter getWriter() throws IOException { if (outputStream != null) throw new IllegalStateException( "getOutputStream() has already been called for this request" ); if (writer != null) return writer; writer = new PrintWriter(getOutputStream()); return writer; } }
Filter
Create a new class mis283.container.LoggingFilter. Our filter class that logs the request and response bodies for requests coming to the services in the application.
package mis283.container; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @WebFilter(urlPatterns = "/*") public class LoggingFilter implements Filter { private FilterConfig filterConfig; @Override public void init(FilterConfig filterConfig) throws ServletException { this.filterConfig = filterConfig; } @Override public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException { final LoggingRequestWrapper requestWrapper = new LoggingRequestWrapper((HttpServletRequest) request); final LoggingResponseWrapper responseWrapper = new LoggingResponseWrapper((HttpServletResponse) response); chain.doFilter(requestWrapper, responseWrapper); } @Override public void destroy() { this.filterConfig = null; } }