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> |
There are two caveats, though:
- If the body never happens to be read (i.e. neither the
getInputStream
orgetReader
methods get called) then it will never be examined. - If the request gets unwrapped (via the
getRequest
method ofServletRequestWrapper
inherited by my custom HttpServletRequest class) then my overrided method will not be called.
Leave a Reply
