GlassFish issue with filter chain: java.lang.IllegalStateException: PWC3990: getWriter() has already been called for this response

We need to upgrade an elderly web application to run under GlassFish 3 instead of Tomcat in order to get EAR deployments (Glassfish was chosen as it is the reference JEE 6 implementation)

Unfortunately it very quickly turned out that the mechanism that ensures that a user is logged in does not work properly and complains that getWriter() has already been called (which is most likely correct) and I cannot figure out why.

The approach is that we have a filter on the complete set of JSP-files which checks that the user is logged in, and if not, redirects to the login page using filterChain.doFilter(servletRequest, servletResponse);. The user state (including credentials) is stored in a so called controller object in session scope which is set from the login validation java code.


Stack trace from Glassfish:

java.lang.IllegalStateException: PWC3990: getWriter() has already been called for this response
    at org.apache.catalina.connector.Response.getOutputStream(Response.java:676)
    at org.apache.catalina.connector.ResponseFacade.getOutputStream(ResponseFacade.java:205)
    at org.apache.myfaces.webapp.filter.ExtensionsFilter.doFilter(ExtensionsFilter.java:176)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:256)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:215)
    at com.XXX.LoggedInToXXXFilter.doFilter(LoggedInToXXXFilter.java:61)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:256)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:215)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:277)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:188)
....

web.xml snippet

<?xml version="1.0"?>
<web-app id="WebApp_ID" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<description>
    XXX provides a web interface for a given user.
</description>
<display-name>
XXX
</display-name>
<context-param>
    <param-name>javax.faces.CONFIG_FILES</param-name> 
    <param-value>/WEB-INF/online-faces-config.xml</param-value>
</context-param>
<context-param>
    <param-name>org.apache.myfaces.ALLOW_JAVASCRIPT</param-name>
    <param-value>true</param-value>
</context-param> 

<listener>
    <listener-class>
        org.apache.myfaces.webapp.StartupServletContextListener
    </listener-class>
</listener>
<servlet>
    <servlet-name>Faces Servlet</servlet-name>
    <servlet-class>
    javax.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>*.jsf</url-pattern>
</servlet-mapping>

<session-config>
    <!-- idle time in minutes before user is automatically logged out by the container -->
    <session-timeout>30</session-timeout>
</session-config>
<welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<filter>
    <filter-name>MyFacesExtensionsFilter</filter-name>
    <filter-class>
        org.apache.myfaces.webapp.filter.ExtensionsFilter
    </filter-class>
    <init-param>
        <param-name>maxFileSize</param-name>
        <param-value>1m</param-value>
        <!-- description>Set the size limit for uploaded files.
            Format: 10 - 10 bytes
            10k - 10 KB
            10m - 10 MB
            1g - 1 GB
            </description-->
    </init-param>
</filter>

<!-- extension mapping for adding <script/>, <link/>, and other resource tags to JSF-pages  -->
<filter-mapping>
    <filter-name>MyFacesExtensionsFilter</filter-name>
    <!-- servlet-name must match the name of your javax.faces.webapp.FacesServlet entry -->
    <servlet-name>Faces Servlet</servlet-name>
</filter-mapping>

<!-- extension mapping for serving page-independent resources (javascript, stylesheets, images, etc.)  -->
<filter-mapping>
    <filter-name>MyFacesExtensionsFilter</filter-name>
    <url-pattern>/faces/myFacesExtensionResource/*</url-pattern>
</filter-mapping>

<filter>
    <description>Ensure user is logged in</description>
    <filter-name>LoggedInToXXXFilter</filter-name>
    <filter-class>
        com.XXX.servlet.filters.LoggedInToXXXFilter
    </filter-class>
    <init-param>
        <param-name>signon_page</param-name>
        <param-value>/login.jsf</param-value>
    </init-param>
    <init-param>
        <param-name>autologout_page</param-name>
        <param-value>/autologout.jsp</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>LoggedInToXXXFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<!-- filter>
    <filter-name>extensionsFilter</filter-name>
    <filter-class>org.apache.myfaces.component.html.util.ExtensionsFilter</filter-class>
    <init-param>
    <param-name>uploadMaxFileSize</param-name>
    <param-value>100m</param-value>
    </init-param>
    <init-param>
    <param-name>uploadThresholdSize</param-name>
    <param-value>100k</param-value>
    </init-param>
    </filter-->
<!-- filter-mapping>
    <filter-name>extensionsFilter</filter-name>
    <url-pattern>*.jsf</url-pattern>
    </filter-mapping>
    <filter-mapping>
    <filter-name>extensionsFilter</filter-name>
    <url-pattern>/faces/*</url-pattern>
    </filter-mapping-->
<!-- error-page>
    <exception-type>java.lang.IllegalArgumentException</exception-type>
    <location>/WEB-INF/jsp/IllegalArgumentException.jsp</location>
    </error-page-->
<error-page>
    <exception-type>java.lang.RuntimeException</exception-type>
    <location>/WEB-INF/jsp/RuntimeException.jsp</location>
</error-page>
<!-- error-page>
    <exception-type>com.transaxiom.axsWHSweb.struts.action.UserIsNotLoggedInException</exception-type>
    <location>/WEB-INF/jsp/UserIsNotLoggedInException.jsp</location>
    </error-page-->
<error-page>
    <exception-type>
        com.XXX.struts.action.SecurityViolationException
    </exception-type>
    <location>/WEB-INF/jsp/SecurityViolationException.jsp</location>
</error-page>
<error-page>
    <exception-type>
        com.XXX.logic.UncheckedCommunicationException
    </exception-type>
    <location>/WEB-INF/jsp/CommunicationException.jsp</location>
</error-page>
<error-page>
    <exception-type>
        com.XXX.logic.ConnectionNotCreatedException
    </exception-type>
    <location>
        /WEB-INF/jsp/ConnectionNotCreatedException.jsp
    </location>
</error-page>
<!-- error-page>
    <exception-type>com.XXX.logic.UncheckedConnectionNotCreatedException</exception-type>
    <location>/WEB-INF/jsp/ConnectionNotCreatedException.jsp</location>
    </error-page-->
<!-- filter>
    <filter-name>MyFacesExtensionsFilter</filter-name>
    <filter-class>org.apache.myfaces.component.html.util.ExtensionsFilter</filter-class>
    <init-param>
    <param-name>maxFileSize</param-name>
    <param-value>20m</param-value>
    </init-param>
    </filter>
    <filter-mapping>
    <filter-name>MyFacesExtensionsFilter</filter-name>
    <url-pattern>*.faces</url-pattern>
    </filter-mapping-->
</web-app>

Filter code from LoggedInToXXXFilter.java:

(The stacktrace happens in the filterChain.doFilter(servletRequest, servletResponse) line.

public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse,
        final FilterChain filterChain) throws IOException, ServletException {
    boolean ok = false;
    if (servletRequest instanceof HttpServletRequest) {
        HttpServletRequest request = (HttpServletRequest) servletRequest;

        String servletPath = request.getServletPath();
        if ((servletPath.equals(signOnPage) == true) || servletPath.endsWith(".css") || servletPath.equals(autologoutPage)) {
            ok = true;
        } else {
            Controller controller = Controller.getControllerFromSession(request.getSession(false));
            if ((controller != null) && controller.isSignedOn()) {
                ok = true;
            }

        }
        if (ok) {
            filterChain.doFilter(servletRequest, servletResponse);
        } else {
            // Hop to the sign on page.
            // http://forum.java.sun.com/thread.jspa?threadID=548967&messageID=2676856
            ServletContext servletContext = filterConfig.getServletContext();

            URL url = new URL(new URL(request.getRequestURL().toString()), (request.getContextPath() + signOnPage));
            ((HttpServletResponse) servletResponse).sendRedirect(url.toString());
        }
    } else {
        // Only for http requests
        filterChain.doFilter(servletRequest, servletResponse);
    }
}

Could a possible reason be that we still bring our own JSF libraries (MyFaces 1.1.4 with Tomahawk)?


EDIT: Updated question with complete (but anonymized) web.xml. Note there is a lot of commented out stuff. I left it in as not to accidentally delete important information


EDIT: Experimented with the sun-web-app configuration file, and found it didn't make a difference. What is interesting is that after logging in, the login page throws the exception but I can manually navigate to the main page (also JSF) and see two more pages with functionality fine. There are three pages in addition to the login page which throws the exception.

My initial thought was that the separating feature would be the t-taglib (for Tomahawk) but that after a quick investigation does not seem to be the case as some of the working pages use Tomahawk and some doesn't.


EDIT: Comparing two jsp-pages, one which failed, another one which didn't did not reveal any obvious difference which should cause this. As it was pointed out that there has been reported this very bug with Tomahawk 1.1 and we were using 1.1.3, I have now upgraded to the latest Apache Myfaces Tomahawk 1.1.9, which appears to have resolved the issue (with no sun-web-app at all).

Upvotes: 6

Views: 6625

Answers (5)

E3G
E3G

Reputation: 487

If using JSP, double check that the original request is not redirect by the JSP page. JSP already calls getWriter behind the scenes, so this will conflict with your own custom filter using the default version of Catalina packaged with Glassfish v.3. Also refer to this post:

Upvotes: 1

Bozho
Bozho

Reputation: 597274

Try defining your filter before all other filters in web.xml.

If that doesn't work, here's how I'd proceed debugging this:

Option 1:

  1. Download Glassfish sources (or the sources of the appropriate version of Tomcat, actually)
  2. Include these sources in your sources lookup directory (for the debugger)
  3. Put a breakpoint in getWriter() of org.apache.catalina.connector.Response
  4. Check the stack to see who is calling getWriter()

Option 2:

  1. Define a new filter ontop of every other filter in web.xml
  2. Create a wrapper around the supplied HttpServletResponse, and put a breakpoint in getWriter() or new Exception().printStackTrace();

Both options have essentially the same idea. In both cases give feedback, so that we can proceed brainstorming.

Upvotes: 2

Pascal Thivent
Pascal Thivent

Reputation: 570545

I don't have a full explanation (i.e. I don't know where getWriter gets called) but this might be a bug in Tomahawk 1.1.3 / MyFaces 1.1.4 as reported in Jira issues like TOMAHAWK-579 or MYFACES-1310 (with the same IllegalStateException as per Servlet specification). Note that this bug seems to be container dependent, as you are experiencing.

So, either try with more recent versions of Tomahawk / MyFaces (see the compatibility matrix) or get the patch corresponding to the fix in r442340 and apply it to the branch 1.1.3 of Tomahawk. The later option is maybe the easiest one. At least, this is what I would try.

Upvotes: 5

BalusC
BalusC

Reputation: 1109292

This can have two causes:

  1. There's another Filter in the chain before ExtensionsFilter which is (indirectly) calling getWriter().
  2. This request was been forwarded from inside a JSP file instead of a Servlet class.

In this particular case, it look like that both sendRedirect() and doFilter() has been called in the same request-response chain (because sendRedirect() may implicitly call getWriter()). When a Filter calls sendRedirect(), it should not be doing doFilter() afterwards. The posted code doesn't prove this, but maybe there are some lines been removed from it for sanitization, or there's another filter before in the chain which does exactly that.

Update: after thinking once more about this and looking in the ExtensionsFilter's source, the ExtensionsFilter actually obtains the OutputStream after filtering the request/response. So, the page, servlet or any other Javacode which has been called/executed by the URL in question has (implicitly) called the getWriter().

Update 2: Glassfish v3 ships by default with Sun Mojarra JSF 2.0 reference implementation. It might have collided somehow with the MyFaces 1.x implementation shipped in the webproject. You can instruct Glassfish v3 that you prefer to use MyFaces by setting useMyFaces or (the newer) useBundledJsf property to true in /WEB-INF/sun-web.xml. Have you used it? Give it a try.

<sun-web-app>
    <class-loader delegate="false"/>
    <property name="useBundledJsf" value="true"/>
</sun-web-app>

Also see Alternative JSF implementations on GlassFish - MyFaces and Tomahawk.

Upvotes: 5

Sean Owen
Sean Owen

Reputation: 66896

One initial question -- if you're running this on GlassFish why does the stack trace have references to Catalina? I could be mistaken but Catalina is Tomcat's core, Grizzly is GlassFish's core.

You probably already know this but the problem is getWriter() and getOutputStream() can't be both called on one response. If you leave that sort of thing to the container, it should get it right.

So one question is, is any of your code calling getWriter()? This code isn't. I don't see anything about this that looks suspicious so I'd dig into any code upstream from this filter, if there is any?

Upvotes: 2

Related Questions