views:

438

answers:

3

I'm trying to log the raw body of HTTP POST requests in our application based on Struts, running on Tomcat 6. I've found one previous post on SO that was somewhat helpful, but the accepted solution doesn't work properly in my case. The problem is, I want to log the POST body only in certain cases, and let Struts parse the parameters from the body after logging. Currently, in the Filter I wrote I can read and log the body from the HttpServletRequestWrapper object, but after that Struts can't find any parameters to parse, so the DispatchAction call (which depends on one of the parameters from the request) fails.

I did some digging through Struts and Tomcat source code, and found that it doesn't matter if I store the POST body into a byte array, and expose a Stream and a Reader based on that array; when the parameters need to get parsed, Tomcat's Request object accesses its internal InputStream, which has already been read by that time.

Does anyone have an idea how to implement this kind of logging correctly?

A: 

What you have to do in your filter is read the post content in its entirety then when you go to pass the request on to the chain; back the input stream with your own. For example you read the post to file on disk, then when you call:

chain.doFilter(new ServletRequest() {}, response);

You can delegate most methods invocations of your class to the original request, but when it comes time to opening the input stream you need to read from your file on disk.

You need to make sure you don't leak resources as this will be invoked quite frequently and can hurt if done incorrectly.

Raymond
The in the question linked code does almost exactly that.
BalusC
As BalusC said, the code does that already. Problem is in overridden methods. Tomcat's RequestFacade allows me to override getInputStream() method, but that's not good enough since the Request (in its parseParameters() method) uses protected getStream() method.
Ivan Vrtarić
A: 

The in the question linked filter example looks good and ought to work. Maybe you're defining it in the web.xml after the Struts dispatcher filter. It would then indeed be too late to parse and log the request body and still make it available for Struts. You need to declare this filter before the Struts dispatcher filter. The filter ordering matters, they are invoked in the order as they're definied in web.xml.

BalusC
The filter is defined as the first filter in the chain. And as soon as the filter runs, the stream gets emptied, and the parameters aren't available even to the requested servlet, not to mention Struts.
Ivan Vrtarić
+1  A: 

In fact, Struts doesn't parse the parameters, it relies on the Servlet container to do that. And once the container has read the inputStream to create the parameters Map, of course there is nothing left to read. And in the Tomcat implementation, if you read the inputStream first, then the getParameter* family of methods has nothing left to work on, since, as you correctly note, it doesn't use getInputStream or getReader but accesses internally its optimized reader. So your only solution in your ServletRequestWrapper is to override getInputStream, getReader, AND the getParameter* family on which Struts relies to read the parameters. Maybe you can have a look at org.apache.catalina.util.RequestUtil to not duplicate the POST body parsing part.

Damien B
I was hoping I wouldn't have to do that, but it looks like it's the only option.
Ivan Vrtarić