10

I am in the situation where my application needs to inspect the content/data/body/payload of a POST request without changing the results of subsequent getParameter calls.

Reading the body from the inputStream:

The body can be read using the InputStream from request.getInputStream or BufferedReader from request.getReader.

Reading POST parameters:

POST requests typically include request parameters in the body of the request. These can be retrieved using getParameter.

The Problem:

the first getParameter call internally parses the inputStream and inserts all parameters into a parameter HashMap. It requires the inputStream to still contain the contents for parsing. Thus one cannot inspect the content and still have a working getParameter call.

Proposed (but not sufficient) Solution

Create a request wrapper that caches the inputstream and returns the cache for getInputStream.

I've seen that solution suggested all over the web, but it doesn't work, because getParameter doesn't actually call getInputStream, but refers to the original inputBuffer buried in the request object. I've tried it, both from within the Servlet and by using a filter

The only solution I can think of involves rewriting getParameter to actually parse the cached inputstream manually. But this feels like a bad idea.

Does anybody have any alternative that works? (This is Tomcat 5.5) This feels like it should be a common use-case; I can't believe how difficult it is.

rewolf
  • 5,561
  • 4
  • 40
  • 51
  • 1
    The answer to that question is not a solution. I've implemented it and it failed to fix the problem. Since then I read the tomcat source and realised that getParameter does not call getInputStream, as I stated above, and so would not read from the cached version of the stream. see http://grepcode.com/file/repo1.maven.org/maven2/tomcat/catalina/5.5.23/org/apache/catalina/connector/RequestFacade.java?av=f#342 I'm not sure why people seem to have found it to be a solution. – rewolf Aug 28 '13 at 22:17
  • The method `getParameter` call `getInputStream`. First, `getParameter` call `parseParameters`, then `parseParameters` call `readPostBody`, then `readPostBody` call `getInputStream`. – peakmuma Oct 24 '16 at 13:27

2 Answers2

1

(That's a rather old tomcat, I'm assuming upgrading to a more modern one isn't an option.)

What you want to do will require intercepting the construction of the concrete HttpServletResponse object wrapping the underlying InputStream. Wrapping that InputStream in a push-back input stream (or equivalent) is necessary.

Tomcat 5.5 is so old I can't even think how that would be accomplished 'normally', but perhaps you could write a filter that uses reflection to reach in and swap the InputStream object inside the concrete request object.

caskey
  • 12,305
  • 2
  • 26
  • 27
  • Yeah. You're pretty spot on with the reflection idea. We also came up with it. I've opted to go with a different approach though - of creating a request wrapper in a filter that supports replaying the inputstream. I've then overridden the getParameter*() methods to read from a map that's been created by parsing the inputstream/payload. – rewolf Sep 03 '13 at 07:28
1

As suggested by @caskey, a possible solution would be to use reflection to replace the inputBuffer with a replayable inputbuffer. But I did not use that approach as it felt naughty.

Instead I created a request wrapper in a filter which reads the inputstream into a byte array and returns a new InputStream that internally uses a ByteArrayInputStream around that array for all getInputStream calls.

After reading the inputstream to the byte array, I create a parameter map by parsing the payload. I merged the superclass' parameter map in to support GET cases with query parameters. I've overridden all the getParameter*() methods to use this parameter map.

I used apache.axis.utils.IOUtils.readFully to easily read the stream in to the byte array. And I'm currently using javax.servlet.http.HttpUtils.parsePostData to parse the data into parameter map. HttpUtils.parsePostData is actually deprecated, so I'll likely replace it with a better version when I find it.

But this works, yay!

rewolf
  • 5,561
  • 4
  • 40
  • 51