【问题标题】:GlassFish issue with filter chain: java.lang.IllegalStateException: PWC3990: getWriter() has already been called for this response过滤器链的 GlassFish 问题:java.lang.IllegalStateException:PWC3990:getWriter() 已为此响应调用
【发布时间】:2011-01-15 04:29:19
【问题描述】:

我们需要升级一个旧的 Web 应用程序以在 GlassFish 3 而不是 Tomcat 下运行,以便获得 EAR 部署(选择 Glassfish 是因为它是参考 JEE 6 实现)

不幸的是,很快发现确保用户登录的机制无法正常工作,并抱怨 getWriter() 已被调用(这很可能是正确的),我无法弄清楚原因。

方法是我们在完整的 JSP 文件集上有一个过滤器,它检查用户是否已登录,如果没有,则使用 filterChain.doFilter(servletRequest, servletResponse); 重定向到登录页面。用户状态(包括凭据)存储在会话范围内的所谓控制器对象中,该对象由登录验证 java 代码设置。


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 sn-p

<?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>

从 LoggedInToXXXFilter.java 过滤代码:

(堆栈跟踪发生在filterChain.doFilter(servletRequest, servletResponse) 行中。

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);
    }
}

可能的原因是我们仍然自带 JSF 库(MyFaces 1.1.4 with Tomahawk)?


编辑:使用完整(但匿名)web.xml 更新问题。请注意,有很多注释掉的东西。我把它留了,以免不小心删除重要信息


编辑:试验了 sun-web-app 配置文件,发现它没有任何区别。有趣的是,登录后,登录页面抛出异常,但我可以手动导航到主页(也是 JSF)并看到另外两个功能正常的页面。除了登录页面之外还有三个页面会抛出异常。

我最初的想法是分离功能将是 t-taglib(用于 Tomahawk),但经过快速调查后似乎并非如此,因为一些工作页面使用 Tomahawk 而有些则没有。


编辑:比较两个 jsp 页面,一个失败,另一个没有显示任何明显的差异,这应该导致这种情况。正如有人指出的那样,已经报告了 Tomahawk 1.1 的这个错误并且我们使用的是 1.1.3,我现在已经升级到最新的 Apache Myfaces Tomahawk 1.1.9,它似乎已经解决了这个问题(没有 sun-网络应用程序)。

【问题讨论】:

  • 您看到我对 sun-web-app 条目的编辑了吗?我今天没有时间做更多的实验,但我希望明天能得到一个明确的“THIS is the difference”。

标签: java tomcat servlets jakarta-ee glassfish


【解决方案1】:

如果使用 JSP,请仔细检查原始请求是否被 JSP 页面重定向。 JSP 已经在后台调用了 getWriter,因此这将与您使用 Glassfish v.3 打包的默认版本的 Catalina 自定义过滤器发生冲突。 也可以参考这个post

【讨论】:

    【解决方案2】:

    尝试在web.xml 中的所有其他过滤器之前定义您的过滤器。

    如果这不起作用,我将继续调试它:

    选项 1:

    1. 下载 Glassfish 源代码(实际上是相应版本的 Tomcat 的源代码)
    2. 在源代码查找目录中包含这些源代码(用于调试器)
    3. getWriter() of org.apache.catalina.connector.Response 中放置断点
    4. 检查堆栈以查看谁在调用getWriter()

    选项 2:

    1. web.xml 中的所有其他过滤器之上定义一个新过滤器
    2. 围绕提供的HttpServletResponse 创建一个包装器,并在getWriter()new Exception().printStackTrace(); 中放置一个断点

    这两个选项的想法基本相同。在这两种情况下都提供反馈,以便我们进行头脑风暴。

    【讨论】:

      【解决方案3】:

      我没有完整的解释(即我不知道在哪里调用 getWriter)但这可能是 Tomahawk 1.1.3 / MyFaces 1.1.4 中的一个错误,如 TOMAHAWK-579 等 Jira 问题中所述或 MYFACES-1310(根据 Servlet 规范使用相同的 IllegalStateException)。请注意,正如您所遇到的,此错误似乎与容器相关。

      所以,要么尝试更新版本的 Tomahawk / MyFaces(参见 compatibility matrix),要么获取与 r442340 中的修复相对应的 patch,并将其应用于 Tomahawk 的分支 1.1.3。后一种选择可能是最简单的选择。至少,这是我会尝试的。

      【讨论】:

      • 就是这样。升级 tomahawk 库解决了这个问题。谢谢!
      【解决方案4】:

      这可能有两个原因:

      1. ExtensionsFilter 之前的链中还有另一个Filter,它(间接)调用getWriter()
      2. 此请求是从 JSP 文件而不是 Servlet 类内部转发的。

      在这种特殊情况下,看起来 both sendRedirect()doFilter() 在同一个请求-响应链中被调用(因为 sendRedirect() 可能隐式调用getWriter())。当Filter 调用sendRedirect() 时,它应该在之后执行doFilter()。发布的代码并不能证明这一点,但可能有一些行被删除以进行清理,或者链中之前有另一个过滤器可以做到这一点。

      更新:再想一想,查看ExtensionsFilter的源码后,ExtensionsFilter实际上得到了OutputStream经过过滤请求/回复。因此,已由相关 URL 调用/执行的页面、servlet 或任何其他 Javacode(隐式)称为getWriter()

      更新 2: Glassfish v3 默认附带 Sun Mojarra JSF 2.0 参考实现。它可能与 webproject 中的 MyFaces 1.x 实现发生了某种冲突。您可以通过在/WEB-INF/sun-web.xml 中将useMyFaces 或(较新的)useBundledJsf 属性设置为true 来指示Glassfish v3 您更喜欢使用MyFaces。你用过吗?试试看吧。

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

      另见Alternative JSF implementations on GlassFish - MyFaces and Tomahawk

      【讨论】:

      • 我已经添加了完整的 web.xml。我只能将 MyFaces 视为过滤器,因此可能是 MyFaces 不是 100% 正确的。版本仍然是 1.1.3,但我希望在迁移到 JSF 2.0 之前我可以让应用程序正常工作。
      • 发布的方法是一个完整的副本 - 仅匿名。
      • 你查看我的更新了吗? ExtensionsFilter 在处理请求和响应后得到OutputStream。 IE。 response.getOutputStream() 是在 chain.doFilter(request, response) 之后完成的。因此,Writer 一定是在特定请求已执行的“正常”Servlet/Java/JSP 代码中的某处调用的。
      • 请求是针对 .jsf 页面的,所以 MyFaces 过滤器必须首先启动。如果在 web.xml 中首先列出“检查登录”过滤器会有所不同吗?
      • 一些初始离线实验表明“useBundledJsf”有帮助。你关于碰撞的假设听起来很可能。
      【解决方案5】:

      第一个问题——如果您在 GlassFish 上运行此程序,为什么堆栈跟踪会引用 Catalina?我可能弄错了,但 Catalina 是 Tomcat 的核心,Grizzly 是 GlassFish 的核心。

      您可能已经知道这一点,但问题是 getWriter() 和 getOutputStream() 不能在一个响应上同时调用。如果你把这种东西留给容器,它应该会做对的。

      所以一个问题是,您的任何代码是否调用 getWriter()?这段代码不是。我没有看到任何看起来可疑的东西,所以我会深入研究这个过滤器上游的任何代码,如果有的话?

      【讨论】:

      • 这直接来自 Glassfish 3 的全新安装 - 底线(我删掉了)指的是灰熊 - catalina 的引用也让我感到困惑。我会看看在哪里(如果在任何地方)我实际上调用了 getWriter 和 getOutputStream(),但我不这么认为。这不是火箭科学应用程序。但是如果 Catalina 抛出异常,也许我也应该在最新的 Tomcat 中看到它?
      • Glassfish 建立在 Tomcat 之上,基本上只有 HTTP 连接器更改为 Grizzly。 Grizzly 不是 servlet 容器。这是一个 HTTP 连接器。
      • 快速浏览了灰熊的主页,他们根本没有解释任何内容。究竟什么是 HTTP 连接器?
      • @Thorbjørn 这是 HTTP 连接器文档tomcat.apache.org/tomcat-6.0-doc/config/http.html。 Grizzly 是一个基于 NIO 的 HTTP 连接器。查找有关 Grizzly 信息的最佳位置是 Jean-Francois 的博客:weblogs.java.net/blog/jfarcand/archive/2005/06/…
      • 确实如此。检查Tomcat链接,那里描述的“Nio implementation”实际上是Grizzly。只需将protocol 更改为protocol="org.apache.coyote.http11.Http11NioProtocol" 即可。另请参阅 balusc.blogspot.com/2009/09/… 了解好处。
      猜你喜欢
      • 1970-01-01
      • 2015-09-22
      • 1970-01-01
      • 1970-01-01
      • 2012-04-13
      • 1970-01-01
      • 2013-01-07
      • 1970-01-01
      • 2017-06-16
      相关资源
      最近更新 更多