7

I've added a filter to my application which simply logs certain things about a request. Some of my servlets read from ServletRequest#getInputStream. Since adding this filter, those servlets that read from ServletRequest#getInputStream no longer work as the input stream is empty. Disabling the filter by simply commenting it out from my web.xml resolves the issue.

Why is this happening and is there a way to use a filter without it messing up the ServletRequest#getInputStream?

The filter is actually Tomcat's RequestDumperFilter included in one of its example web apps. I'll only include the doFilter method as that is the important part. If you want to see the entire thing, I've put it up on PasteBin.

/**
 * Time the processing that is performed by all subsequent filters in the
 * current filter stack, including the ultimately invoked servlet.
 *
 * @param request The servlet request we are processing
 * @param result The servlet response we are creating
 * @param chain The filter chain we are processing
 *
 * @exception IOException if an input/output error occurs
 * @exception ServletException if a servlet error occurs
 */
public void doFilter(ServletRequest request, ServletResponse response,
                     FilterChain chain)
throws IOException, ServletException {

    if (filterConfig == null)
    return;

// Render the generic servlet request properties
StringWriter sw = new StringWriter();
PrintWriter writer = new PrintWriter(sw);
writer.println("Request Received at " +
           (new Timestamp(System.currentTimeMillis())));
writer.println(" characterEncoding=" + request.getCharacterEncoding());
writer.println("     contentLength=" + request.getContentLength());
writer.println("       contentType=" + request.getContentType());
writer.println("            locale=" + request.getLocale());
writer.print("           locales=");
Enumeration locales = request.getLocales();
boolean first = true;
while (locales.hasMoreElements()) {
    Locale locale = (Locale) locales.nextElement();
    if (first)
        first = false;
    else
        writer.print(", ");
    writer.print(locale.toString());
}
writer.println();
Enumeration names = request.getParameterNames();
while (names.hasMoreElements()) {
    String name = (String) names.nextElement();
    writer.print("         parameter=" + name + "=");
    String values[] = request.getParameterValues(name);
    for (int i = 0; i < values.length; i++) {
        if (i > 0)
        writer.print(", ");
    writer.print(values[i]);
    }
    writer.println();
}
writer.println("          protocol=" + request.getProtocol());
writer.println("        remoteAddr=" + request.getRemoteAddr());
writer.println("        remoteHost=" + request.getRemoteHost());
writer.println("            scheme=" + request.getScheme());
writer.println("        serverName=" + request.getServerName());
writer.println("        serverPort=" + request.getServerPort());
writer.println("          isSecure=" + request.isSecure());

// Render the HTTP servlet request properties
if (request instanceof HttpServletRequest) {
    writer.println("---------------------------------------------");
    HttpServletRequest hrequest = (HttpServletRequest) request;
    writer.println("       contextPath=" + hrequest.getContextPath());
    Cookie cookies[] = hrequest.getCookies();
        if (cookies == null)
            cookies = new Cookie[0];
    for (int i = 0; i < cookies.length; i++) {
        writer.println("            cookie=" + cookies[i].getName() +
               "=" + cookies[i].getValue());
    }
    names = hrequest.getHeaderNames();
    while (names.hasMoreElements()) {
        String name = (String) names.nextElement();
    String value = hrequest.getHeader(name);
        writer.println("            header=" + name + "=" + value);
    }
    writer.println("            method=" + hrequest.getMethod());
    writer.println("          pathInfo=" + hrequest.getPathInfo());
    writer.println("       queryString=" + hrequest.getQueryString());
    writer.println("        remoteUser=" + hrequest.getRemoteUser());
    writer.println("requestedSessionId=" +
           hrequest.getRequestedSessionId());
    writer.println("        requestURI=" + hrequest.getRequestURI());
    writer.println("       servletPath=" + hrequest.getServletPath());
}
writer.println("=============================================");

// Log the resulting string
writer.flush();
filterConfig.getServletContext().log(sw.getBuffer().toString());

// Pass control on to the next filter
    chain.doFilter(request, response);

}

Conclusion

From what I've read by Googling, any of the following methods will render getInputStream empty if called first:

  • getParameter
  • getParameterNames
  • getParameterValues
  • getParameterMap

Thanks SimoneGianni for pointing me in the right direction:

Here are some sources

This person actually had a similar issue and created his own wrapper class as a work-around.

Community
  • 1
  • 1
John
  • 9,254
  • 12
  • 54
  • 75
  • 1
    Can you add some filter code code? – Pushkar Jul 21 '11 at 18:05
  • Sure thing. It's actually Tomcat's RequestDumperFilter that is included in one of it's example web applications. I'll pop it in the question above. – John Jul 21 '11 at 18:07

2 Answers2

3

If you call getParameters, getParameterNames and similar methods, you COULD interfere with getInputStream or getReader. This isn't stated clearly enough in servlet documentation, but there are some warnings about the opposite (getInputStream interfering with getParameter) in the official servlet javadocs (since 1.3, see http://download.oracle.com/javaee/1.3/api/javax/servlet/ServletRequest.html#getParameter(java.lang.String) )

Are you seeing this problem on POSTs? Since POST requests encode parameters as request body, to read the parameters you actually have to consume (the container does this for you) the input stream.

Simone Gianni
  • 11,426
  • 40
  • 49
  • You could get around it, probably, by calling chain.doFilter with a wrapped request on which you "rebuild" the input stream, using some kind of piped stream, or simply a big byte array, but be cautious of what happen if you then receive a POST with a very large body, cause it can exaust your RAM and expose to a DOS attack. – Simone Gianni Jul 21 '11 at 18:22
  • Awesome information! Thanks. Yes, this issue occurs on POST requests. So by intercepting the request (via the filter), the input stream is consumed or rather discarded by the end of the `doFilter` method? – John Jul 21 '11 at 18:23
  • Regarding "rebuilding," do you mean writing to the output stream with the contents of the input stream? – John Jul 21 '11 at 18:25
  • From what I've read by Googling, any of the following methods will render `getInputStream` empty if called first: `getParameter`, `getParameterNames`, `getParameterValues`, and `getParameterMap`. – John Jul 21 '11 at 18:54
  • @John yes, that is usually the case, but actually it depends on the container implementation. – Simone Gianni Jul 21 '11 at 19:43
  • @John it is technically possible to read from a stream, buffer the part you've read in a byte array, and the expose another stream that reads that buffered part and when finished falls back to the original stream. The JVM itself offers a number of such methods, like the mark/reset methods on streams, but they are rarely implemented. – Simone Gianni Jul 21 '11 at 19:44
  • @John the problem with buffering (and the reason for all stream stuff inside servlets and JVMin general) is RAM consumption. Suppose Tomcat reads all the input stream in a byte array, then parse it for parameters, then holds that byte array just in case someone down the chain needs to read the input stream again. If a user (eventually malicious user) starts a script with curl calls posting 5 megs each, it could exhaust your ram in minutes. That's why Tomcat and the servlet specs are so conservative on that. – Simone Gianni Jul 21 '11 at 19:47
-1

Wow. That's a good one. I'm betting that the RequestDumper has to parse the whole InputStream to, well, dump the request and that is why the InputStream is empty. I'm thinking that you can't RequestDumper in front of any Servlet that is planning on doing getInputStream. I am at a loss for what your options are from this point, though. Maybe dump the parameters you are interested in directly from the HttpRequest AFTER the chain.doFilter(request, response); call.

Bob Kuhar
  • 10,838
  • 11
  • 62
  • 115