0

TL;DR

I'm trying to find out if an included servlet should be able to change the headers of the response. I know1 that the included servlet cannot change the headers of the response using the HttpServletResponse object passed in its service. But it is inconsistent how different servlet containers behave if trying to change headers using the original response.

Background

To me, this is relevant because I'm debugging an issue in a system using a CMS that apparently does exactly that: its core servlet stores the original response in the request scope (for some complex caching and templating purposes), and in the included resource (jsp) it restores the original response and tries to set the header content type to fit its needs.

Notice that at the time of setting the contentType, the response has not yet been commited2 (in fact, no writing activity has taken place).

Also notice that I could also workaround this issue setting some undocumented property, but in this case, I would be giving up its caching functionality.

The problem arises if I add a certain Filter to the servlet filter chain: Tomcat uses reflection to calculate where it has to insert a ServletResponseWrapper (specifically, an ApplicationHttpResponse) in the (possibly chained) implicit response object. For instance, JavaMelody (a monitoring filter) happens to wrap the original response in one of the objects against which Tomcat compares to, breaking the (dubiously-correct implemented) existing functionality.

Tests

To find a practical answer and set some basis for potential new issues, I have written some tests and run them under WildFly Full 9.0.1.Final, Apache Tomcat/6.0.29, Apache Tomcat/7.0.47, and Jetty-9.3.3.v20150827. Tomcat seems the only one to allow this kind of "response hijacking".

The relevant code:

// TopServlet (extends HttpServlet)
protected void processRequest(...) throws Exception {

    // Wrapp to avoid Tomcat inserting the ApplicationHttpResponse 
    // in front of the top level response
    ServletResponse wRes = new HttpServletResponseWrapper(response);

    // Store the top level response to allow for changing headers later on
    request.setAttribute(TOPLEVEL_RES, response);

    // Include in a wrapped response, to not alter the top level response
    getServletContext().getRequestDispatcher("included").include(request, wRes);
}
// IncludedServlet (extends HttpServlet)
protected void processRequest(...) throws Exception {

    // Always ignored (OK)
    response.setContentType("html/csv;charset=UTF-8");
    response.addHeader("Content-Disposition", "attachment; filename=t.csv;");

    // XXX: This should affect the web server output, but current
    // behaviour depends on HttpServletResponse implementation!
    HttpServletResponse topResponse =
      (HttpServletResponse) request.getAttribute(TopServlet.TOPLEVEL_RES);
    topResponse.setContentType("html/csv;charset=UTF-8");
    topResponse.addHeader("Content-Disposition", "attachment; filename=t.csv;");

    // Should generate some values for a csv file
    try (PrintWriter out = response.getWriter()) {
        out.println("top11,top12,top13\n"
                + "top21,top22,top23");
    }
}

Notes

1 According to the specs (Chapter 9.3),

The target servlet of the include method has access to all aspects of the request object, but its use of the response object is more limited.

[...] It cannot set headers or call any method that affects the headers of the response[...]. Any attempt to set the headers must be ignored [...]

2 As mentioned in a comment to an answer to "Servlet include swallows HTTP headers in Tomcat", and to no surprise, the fact that the answer has been committed plays a role

Community
  • 1
  • 1
Alberto
  • 5,021
  • 4
  • 46
  • 69
  • Looks like you answered this yourself. Tomcat violates spec and allows response header manipulation in included servlets. – Joakim Erdfelt Sep 14 '15 at 15:24
  • I don't agree. That is exactly the point: the specs only disallow the header manipulation if using the response from the service parameter (or the implicit response object, if using jps). I'm exercising the _original_ response. To me, WildFly and Jetty go too far, and Tomcat implements the restriction just incorrectly. – Alberto Sep 14 '15 at 15:36
  • The spec is clear here, if in processing of a dispatched request/response during a dispatch of type [`include`](http://docs.oracle.com/javaee/7/api/javax/servlet/DispatcherType.html#INCLUDE), then there is no response header manipulation allowed. – Joakim Erdfelt Sep 14 '15 at 15:42
  • As I understand it, the target servlet can only be limited in what it can do with the response it has been passed... But the programmer should be in the position of overriding the original response, if storing the original response in the context. The `TopServlet` could as well set the headers after the include, so I don't see any contradiction. – Alberto Sep 14 '15 at 15:49
  • just wait till you discover Servlet 3.1 and Async I/O, then you'll get even more confused. The only role for the included servlet response is the response body content (if any), all other details are irrelevant. The top/parent that dispatches for the included servlet controls headers and whatnot. The parent/top could attempt to manipulate things after the include dispatch, having to deal with the `isCommitted` behavior on its own (which the included dispatch could have triggered) – Joakim Erdfelt Sep 14 '15 at 15:56

0 Answers0