6

I am trying to log every incoming request and outgoing response in my application. I am using jee 6 so I do not have ContainerRequestFilter and ContainerResponseFilter classes. So I decided to use Filter.

I annotated a class with @WebFilter("/*") and implemented Filter interface. I successfully read the request headers and request body. With some difficulty I also read the response headers and response body. Below is a code snippet

MyHttpServletResponseWrapper wrapper = new MyHttpServletResponseWrapper((HttpServletResponse) response);
chain.doFilter(request, wrapper);

MyHttpServletResponseWrapper class

public class MyHttpServletResponseWrapper extends HttpServletResponseWrapper {
    private StringWriter sw = new StringWriter();
    public MyHttpServletResponseWrapper(HttpServletResponse response) { super(response); }

    public PrintWriter getWriter() { return new PrintWriter(sw); }

    public ServletOutputStream getOutputStream() {
        return new ServletOutputStream (){
            public void write(int B) { sw.append((char) B); }
        };
    }

    public String getCopy() { sw.toString(); }
}

After logging the response I am writing the response back to the output stream so that the client can receive the response. Below is the code

logResponse(wrapper);
response.getOutputStream().write(wrapper.getCopy().getBytes());

What I am not able to understand is, how to put back the request body in the input stream after reading it.

In standard APIs like the Jersey, there is a convenient way to put it back using setEntityInputStream(inputStream)

How do I do it with the standard Java ServletOutputStream api.

I am not trying to reinvent the wheel. I am trying to avoid using Jersey so that my code can be easily migrated to new jee versions. Also I would like to understand how APIs like Jersey does it.

To read the response from the body I read the below link but it did not work for me. Container threw an exception saying writer already obtained.

Capture and log the response body

P.S.

I think I figured out how to set the input stream back. Below is some code

MyRequestWrapper requestwrapper = new MyRequestWrapper((HttpServletRequest) request);

chain.doFilter(requestwrapper, wrapper);

MyRequestWrapper class

public class MyRequestWrapper extends HttpServletRequestWrapper {
    String body; 
    int counter;
    public MyRequestWrapper(HttpServletRequest request) { super(request); }

    public void setBody(String body) { this.body = body;}

    public ServletInputStream getInputStream() {
        return new ServletInputStream() {
            public interest read() throws IOException {
                if(counter >=body.length()) return -1;
                return body.charAt(counter++);
            }
        };
    }
}

I know that the quality of the code in overrided getInputStream and getOutputStream are not so great. Right now I am not worried about that. I want to know if this is a right idea? If then then I would like to concentrate on the code quality.

Community
  • 1
  • 1
Krishna Chaitanya
  • 2,533
  • 4
  • 40
  • 74

1 Answers1

11
/** I know its too late but it can be helpfull for others as well. **//
import java.io.IOException;
import java.io.UnsupportedEncodingException;

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.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;

public class ApiIOLoggerFilter implements Filter {
@Override 
public void init(FilterConfig filterConfig) {}
@Override 
public void destroy() {}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    if (request instanceof HttpServletRequest && response instanceof HttpServletResponse) {
        ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper((HttpServletRequest) request);
        ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper((HttpServletResponse) response);
        try {
            chain.doFilter(requestWrapper, responseWrapper);
        } finally {
            performRequestAudit(requestWrapper);
            performResponseAudit(responseWrapper);
        }
    } else {
        chain.doFilter(request, response);
    }
}

private void performRequestAudit(ContentCachingRequestWrapper requestWrapper) {
    if (requestWrapper != null && requestWrapper.getContentAsByteArray() != null && requestWrapper.getContentAsByteArray().length > 0) {
        logger.info("\n Headers:: {} \n Request Body:: {}", new ServletServerHttpRequest((HttpServletRequest)requestWrapper.getRequest()).getHeaders(),
                getPayLoadFromByteArray(requestWrapper.getContentAsByteArray(), requestWrapper.getCharacterEncoding()));
    }
}

private void performResponseAudit(ContentCachingResponseWrapper responseWrapper)
        throws IOException {
    if (responseWrapper != null && responseWrapper.getContentAsByteArray() != null
            && responseWrapper.getContentAsByteArray().length > 0) {
        logger.info("Response Body:: {}", getPayLoadFromByteArray(responseWrapper.getContentAsByteArray(),
                responseWrapper.getCharacterEncoding()));
    } else {
        performErrorResponseAudit(responseWrapper);
    }
    responseWrapper.copyBodyToResponse();
}

private void performErrorResponseAudit(ContentCachingResponseWrapper responseWrapper) {
    logger.warn("HTTP Error Status Code::" + responseWrapper.getStatus());
}

private String getPayLoadFromByteArray(byte[] requestBuffer, String charEncoding) {
    String payLoad = "";
    try {
        payLoad = new String(requestBuffer, charEncoding);
    } catch (UnsupportedEncodingException unex) {
        payLoad = "Unsupported-Encoding";
    }
    return payLoad;
}

}
Craig P. Motlin
  • 26,452
  • 17
  • 99
  • 126
sanjeet
  • 121
  • 2
  • 9