Lazily Check the Body of an HttpServletRequest



This site utilizes Google Analytics, Google AdSense, as well as participates in affiliate partnerships with various companies including Amazon. Please view the privacy policy for more details.

Recently I was helping a junior developer analyze the incoming parameters of an HTTP request in a Spring/Java Servlet application.

The issue came down to reading the body of the request: the way an HttpServletRequest is designed, once the body is read, then the body is consumed. It’s gone. Any attempt to read the body again will result in reading an empty body.

Of course, that’s no good.

After a few attempts at caching the body to read it twice (either using custom wrappers or Spring’s ContentCachingRequestWrapper) I decided it might be better to lazily read the body. That is, only read the body when it’s requested, and then pass the body onto the requesting method.

I did this by creating a custom HttpServletRequest class (that extends HttpServletRequestWrapper).

And then, in a filter class, wrap the request and send in on down the chain

Finally, you’ll need to add the filter to your web.xml file:

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.util.stream.Collectors;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
public class LazyRequestBodyChecker extends HttpServletRequestWrapper {
public LazyRequestBodyChecker(final HttpServletRequest request) {
super(request);
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ServletInputStream inputSteam = super.getInputStream();
final String string = new String(inputSteam.readAllBytes());
// Analyze or even modify the string here.
final InputStream innerStream = new ByteArrayInputStream(string.getBytes());
return new ServletInputStream() {
@Override
public boolean isFinished() {
return inputSteam.isFinished();
}
@Override
public boolean isReady() {
return inputSteam.isReady();
}
@Override
public void setReadListener(final ReadListener listener) {
inputSteam.setReadListener(listener);
}
@Override
public int read() throws IOException {
return innerStream.read();
}
};
}
@Override
public BufferedReader getReader() throws IOException {
final BufferedReader reader = super.getReader();
final String string = reader.lines()
.map(s -> s) // You could analyze or modify
.filter(s -> true) // the string in the stream itself
.collect(Collectors.joining());
// Or analyze or modify it here afterwards.
return new BufferedReader(new StringReader(string));
}
}
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
public class LazyRequestBodyCheckerFilter implements Filter {
@Override
public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain)
throws IOException, ServletException {
if (request instanceof HttpServletRequest) {
final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
chain.doFilter(new LazyRequestBodyChecker(httpServletRequest), response);
}
}
}
<filter>
<filter-name>LazyRequestBodyCheckerFilter</filter-name>
<display-name>LazyRequestBodyCheckerFilter</display-name>
<!-- Don't forget to include the fully qualified name if included in a package! -->
<filter-class>LazyRequestBodyCheckerFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>LazyRequestBodyCheckerFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
view raw web.xml hosted with ❤ by GitHub

There are two caveats, though:

  1. If the body never happens to be read (i.e. neither the getInputStream or getReader methods get called) then it will never be examined.
  2. If the request gets unwrapped (via the getRequest method of ServletRequestWrapper inherited by my custom HttpServletRequest class) then my overrided method will not be called.

Leave a Reply

Note that comments won't appear until approved.