Http Servlet Request Lose Params from Post Body After Read It Once

Http Servlet request lose params from POST body after read it once

As an aside, an alternative way to solve this problem is to not use the filter chain and instead build your own interceptor component, perhaps using aspects, which can operate on the parsed request body. It will also likely be more efficient as you are only converting the request InputStream into your own model object once.

However, I still think it's reasonable to want to read the request body more than once particularly as the request moves through the filter chain. I would typically use filter chains for certain operations that I want to keep at the HTTP layer, decoupled from the service components.

As suggested by Will Hartung I achieved this by extending HttpServletRequestWrapper, consuming the request InputStream and essentially caching the bytes.

public class MultiReadHttpServletRequest extends HttpServletRequestWrapper {
private ByteArrayOutputStream cachedBytes;

public MultiReadHttpServletRequest(HttpServletRequest request) {
super(request);
}

@Override
public ServletInputStream getInputStream() throws IOException {
if (cachedBytes == null)
cacheInputStream();

return new CachedServletInputStream();
}

@Override
public BufferedReader getReader() throws IOException{
return new BufferedReader(new InputStreamReader(getInputStream()));
}

private void cacheInputStream() throws IOException {
/* Cache the inputstream in order to read it multiple times. For
* convenience, I use apache.commons IOUtils
*/
cachedBytes = new ByteArrayOutputStream();
IOUtils.copy(super.getInputStream(), cachedBytes);
}


/* An input stream which reads the cached request body */
private static class CachedServletInputStream extends ServletInputStream {

private final ByteArrayInputStream buffer;

public CachedServletInputStream(byte[] contents) {
this.buffer = new ByteArrayInputStream(contents);
}

@Override
public int read() {
return buffer.read();
}

@Override
public boolean isFinished() {
return buffer.available() == 0;
}

@Override
public boolean isReady() {
return true;
}

@Override
public void setReadListener(ReadListener listener) {
throw new RuntimeException("Not implemented");
}
}
}

Now the request body can be read more than once by wrapping the original request before passing it through the filter chain:

public class MyFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {

/* wrap the request in order to read the inputstream multiple times */
MultiReadHttpServletRequest multiReadRequest = new MultiReadHttpServletRequest((HttpServletRequest) request);

/* here I read the inputstream and do my thing with it; when I pass the
* wrapped request through the filter chain, the rest of the filters, and
* request handlers may read the cached inputstream
*/
doMyThing(multiReadRequest.getInputStream());
//OR
anotherUsage(multiReadRequest.getReader());
chain.doFilter(multiReadRequest, response);
}
}

This solution will also allow you to read the request body multiple times via the getParameterXXX methods because the underlying call is getInputStream(), which will of course read the cached request InputStream.

Edit

For newer version of ServletInputStream interface. You need to provide implementation of few more methods like isReady, setReadListener etc. Refer this question as provided in comment below.

Why can the body of the HttpServletRequest not be read multiple times?

Why can the body of the HttpServletRequest not be read multiple times?

Because that would require the servlet stack to buffer the entire request body ... in case the servlet decides to re-read it. That is going to be a performance and/or memory utilization hit for all requests. And it won't work unless there is enough memory to buffer multiple instances (for multiple simultaneous requests) of the largest anticipated request body.

Note that there is no way to get the client to resend the data, short of failing the HTTP request. Even then, the client may not be able to resend it ... because it may not have been able to buffer the data itself.

In short: rereading the request body is not supported by the servlet APIs because it doesn't scale. If a servlet wants to reread the data, it needs to buffer it itself.

How can I read request body multiple times in Spring 'HandlerMethodArgumentResolver'?

You can add a filter, intercept the current HttpServletRequest and wrap it in a custom HttpServletRequestWrapper. In your custom HttpServletRequestWrapper, you read the request body and cache it and then implement getInputStream and getReader to read from the cached value. Since after wrapping the request, the cached value is always present, you can read the request body multiple times:

@Component
public class CachingRequestBodyFilter extends GenericFilterBean {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest currentRequest = (HttpServletRequest) servletRequest;
MultipleReadHttpRequest wrappedRequest = new MultipleReadHttpRequest(currentRequest);
chain.doFilter(wrappedRequest, servletResponse);
}
}

After this filter, everybody will see the wrappedRequest which has the capability of being read multiple times:

public class MultipleReadHttpRequest extends HttpServletRequestWrapper {
private ByteArrayOutputStream cachedContent;

public MultipleReadHttpRequest(HttpServletRequest request) throws IOException {
// Read the request body and populate the cachedContent
}

@Override
public ServletInputStream getInputStream() throws IOException {
// Create input stream from cachedContent
// and return it
}

@Override
public BufferedReader getReader() throws IOException {
// Create a reader from cachedContent
// and return it
}
}

For implementing MultipleReadHttpRequest, you can take a look at ContentCachingRequestWrapper from spring framework which is basically does the same thing.

This approach has its own disadvantages. First of all, it's somewhat inefficient, since for every request, request body is being read at least two times. The other important drawback is if your request body contains 10 GB worth of stream, you read that 10 GB data and even worse bring that into memory for further examination.



Related Topics



Leave a reply



Submit