Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I'm trying to accessing two http request parameters in a Java Servlet filter, nothing new here, but was surprised to find that the parameters have already been consumed! Because of this, it is not available anymore in the filter chain.

It seems that this only occurs when the parameters comes in a POST request body (a form submit, for example).

Is there a way to read the parameters and NOT consume them?

So far I've found only this reference: Servlet Filter using request.getParameter loses Form data.

Thanks!

like image 455
amuniz Avatar asked Apr 18 '12 13:04

amuniz


2 Answers

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 inputstream which reads the cached request body */   public class CachedServletInputStream extends ServletInputStream {     private ByteArrayInputStream input;      public CachedServletInputStream() {       /* create a new input stream from the cached request body */       input = new ByteArrayInputStream(cachedBytes.toByteArray());     }      @Override     public int read() throws IOException {       return input.read();     }   } } 

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.

like image 135
pestrella Avatar answered Sep 29 '22 13:09

pestrella


I know I'm late, but this question was still relevant for me and this SO post was one of the top hits in Google. I'm going ahead and post my solution in the hopes that someone else might save couple of hours.

In my case I needed to log all requests and responses with their bodies. Using Spring Framework the answer is actually quite simple, just use ContentCachingRequestWrapper and ContentCachingResponseWrapper.

import org.springframework.web.util.ContentCachingRequestWrapper; import org.springframework.web.util.ContentCachingResponseWrapper;  import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException;  public class LoggingFilter implements Filter {      @Override     public void init(FilterConfig filterConfig) throws ServletException {      }      @Override     public void destroy() {      }      @Override     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)             throws IOException, ServletException {          ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper((HttpServletRequest) request);         ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper((HttpServletResponse) response);          try {             chain.doFilter(requestWrapper, responseWrapper);         } finally {              String requestBody = new String(requestWrapper.getContentAsByteArray());             String responseBody = new String(responseWrapper.getContentAsByteArray());             // Do not forget this line after reading response content or actual response will be empty!             responseWrapper.copyBodyToResponse();              // Write request and response body, headers, timestamps etc. to log files          }      }  } 
like image 37
Mikk Avatar answered Sep 29 '22 12:09

Mikk