Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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).

like image 486
Thorbjørn Ravn Andersen Avatar asked Feb 10 '10 09:02

Thorbjørn Ravn Andersen


2 Answers

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.

like image 66
Pascal Thivent Avatar answered Nov 14 '22 21:11

Pascal Thivent


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.

like image 45
BalusC Avatar answered Nov 14 '22 23:11

BalusC