Sans Pareil Technologies, Inc.

Key To Your Business

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