Sunday, January 11, 2009

Read Request Body in Filter

Is it possible to intercept HTTP Request in order to read HTTP Request Body (HTTP Request Payload) before it is served by target servlet or JSP?

The answer is yes. You can use Servlet Filter to read, "filter" or modify ServletRequest (HTTPServletRequest) object before the request object is sent to and serve by the final servlet.

What if I read a request body within a servlet filter, is the body still going to be available to be read again by the servlet, or can it be read only once?

Yes, but it is a little bit tricky.
Because of the fact that the request body can be read only once. If you read the body in a filter, the target servlet will not be able to re-read it and this will also cause IllegalStateException. You will need ServletRequestWrapper or its child: HttpServletRequestWrapper so that you can read HTTP request body and then the servlet can still read it later.

public class MyRequestWrapper extends HttpServletRequestWrapper {
private final String body;
public MyRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = null;
try {
InputStream inputStream = request.getInputStream();
if (inputStream != null) {
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
char[] charBuffer = new char[128];
int bytesRead = -1;
while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
stringBuilder.append(charBuffer, 0, bytesRead);
}
} else {
stringBuilder.append("");
}
} catch (IOException ex) {
throw ex;
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException ex) {
throw ex;
}
}
}
body = stringBuilder.toString();
}

@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
ServletInputStream servletInputStream = new ServletInputStream() {
public int read() throws IOException {
return byteArrayInputStream.read();
}
};
return servletInputStream;
}

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

public String getBody() {
return this.body;
}
}

The first step is to create a class that extends HttpServletRequestWrapper. Then, use the constructor to read HTTP Request body and store it in "body" variable. The final step is to override getInputStream() and getReader() so that the final servlet can read HTTP Request Body without causing IllegalStateException.

Here's how to use RequestWrapper in the filter.

public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {

Throwable problem = null;

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

String body = myRequestWrapper.getBody();
String clientIP = myRequestWrapper.getRemoteHost();
int clientPort = request.getRemotePort();
String uri = myRequestWrapper.getRequestURI();

System.out.println(body);
System.out.println(clientIP);
System.out.println(clientPort);
System.out.println(uri);

chain.doFilter(myRequestWrapper, response);
}

7 comments:

  1. Thank you for the code sample, I have been trying to do this myself. However, after adding the filter the downstream servlet loses all its http post parameters. The parameters re-appear only after the above filter is removed. Running OpenJDK 6 with Tomcat 6.0.16

    ReplyDelete
  2. There is a problem with this code...
    When I use POST request to submit my data and try to read some parameter with myRequestWrapper.getParameter("paramName") I get null. I suppose this wrapper should implement its own getParameter(String) method...

    ReplyDelete
  3. In actuality, the code above will not work if the call is made to the InputStream, and later you request any parameter. You must override all of the parameter methods for this to work.

    ReplyDelete
  4. We use this code and found it very useful.
    but we found a bug- when reading the body from the input stream, you use the default charset of the machine:
    bufferedReader = new BufferedReader(new InputStreamReader(inputStream));

    instead, you should probably use the request body charset.
    so you should change the above line to this line:
    bufferedReader = new BufferedReader(new InputStreamReader(inputStream, request.getCharacterEncoding()));

    ReplyDelete
  5. Thanks sir, but how can I get the request body from another class? I have a class that makes the md5 digest of that request body.

    Thanks a lot in advance.

    ReplyDelete
  6. Very good post. PRECISELY what I needed to know!

    ReplyDelete
  7. Under which class is the doFilter() Method? Do I insert it in the servlet class?

    ReplyDelete