Primefaces Push 导致 IllegalStateException

Primefaces Push causes IllegalStateException

目前我正在研究 primefaces 推送功能。实际上,推送功能工作正常。

这是我的推送流程: 我有两个共享同一个数据库的应用程序,admin dashboard(primefaces + spring) 和 webapi (spring mvc)。当有数据通过 webapi 插入数据库时​​,web api 将调用 admin url 将触发为所有登录用户推送通知。正如预期的那样,它工作正常。

我的问题是,当有推送通知时,然后用户同时注销(至少通知咆哮没有消失)它会抛出 java.lang.IllegalStateException: Cannot call sendRedirect() after the response has been committed 并且有时会抛出 Cannot create a session after the response has been committed

我已经尝试将登录控制器更改为 ViewScope,并将此上下文参数放入 Web xml,但仍然无法正常工作。

<context-param>
    <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
    <param-value>client</param-value>
</context-param>

请帮忙,

这是完整的堆栈跟踪:

SEVERE:Servlet.service() for servlet [facesServlet] in context with path [/WebAdmin] throw exception [java.lang.IllegalStateException: Cannot call sendRedirect() after the response has been committed] 根本原因
java.lang.IllegalStateException: 响应提交后无法调用 sendRedirect()
    在 org.apache.catalina.connector.ResponseFacade.sendRedirect(ResponseFacade.java:482)
    在 com.sun.faces.context.ExternalContextImpl.redirect(ExternalContextImpl.java:678)
    在 org.omnifaces.util.FacesLocal.redirect(FacesLocal.java:882)
    在 org.omnifaces.util.Faces.redirect(Faces.java:1170)
    在 com.sepakbole.web.controller.LoginController.doLogout(LoginController.java:84)
    在 sun.reflect.NativeMethodAccessorImpl.invoke0(本机方法)
    在 sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    在 sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    在 java.lang.reflect.Method.invoke(Method.java:606)
    在 org.apache.el.parser.AstValue.invoke(AstValue.java:279)
    在 org.apache.el.MethodExpressionImpl.invoke(MethodExpressionImpl.java:273)
    在 com.sun.faces.facelets.el.TagMethodExpression.invoke(TagMethodExpression.java:105)
    在 javax.faces.component.MethodBindingMethodExpressionAdapter.invoke(MethodBindingMethodExpressionAdapter.java:87)
    在 com.sun.faces.application.ActionListenerImpl.processAction(ActionListenerImpl.java:102)
    在 javax.faces.component.UICommand.broadcast(UICommand.java:315)
    在 javax.faces.component.UIViewRoot.broadcastEvents(UIViewRoot.java:790)
    在 javax.faces.component.UIViewRoot.processApplication(UIViewRoot.java:1282)
    在 com.sun.faces.lifecycle.InvokeApplicationPhase.execute(InvokeApplicationPhase.java:81)
    在 com.sun.faces.lifecycle.Phase.doPhase(Phase.java:101)
    在 com.sun.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:198)
    在 javax.faces.webapp.FacesServlet.service(FacesServlet.java:658)
    在 org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)
    在 org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
    在 org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
    在 org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
    在 org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
    在 com.sepakbole.web.filter.AuthorizationFilter.doFilter(AuthorizationFilter.java:51)
    在 org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
    在 org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
    在 org.apache.logging.log4j.web.Log4jServletFilter.doFilter(Log4jServletFilter.java:71)
    在 org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
    在 org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
    在 org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:218)
    在 org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
    在 org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:505)
    在 org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:169)
    在 org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
    在 org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:956)
    在 org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
    在 org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:442)
    在 org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1082)
    在 org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:623)
    在 org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:316)
    在 java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    在 java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    在 org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    在 java.lang.Thread.run(Thread.java:745)

更新: 这是我的控制器:

import java.io.Serializable;

import javax.faces.bean.ApplicationScoped;
import javax.faces.bean.ManagedBean;

import org.primefaces.push.EventBus;
import org.primefaces.push.EventBusFactory;

@ManagedBean
@ApplicationScoped
public class PushMessageController implements Serializable {
    private static final long serialVersionUID = 1L;
    private String data;

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }

    public void execute(){
        EventBus eventBus = EventBusFactory.getDefault().eventBus();
        eventBus.publish("/receive-message", data);
    }
}

下面是 PushEndPoint: 导入 org.primefaces.push.annotation.OnMessage; 导入 org.primefaces.push.annotation.PushEndpoint; 导入 org.primefaces.push.annotation.Singleton; 导入 org.primefaces.push.impl.JSONEncoder;

@PushEndpoint("/receive-message")
@Singleton
public class MessageResource {

    @OnMessage(encoders = {JSONEncoder.class})
    public String onMessage(String data) {
        return data;
    }
}

这是放在我的模板上的:

<p:socket channel="/receive-message" onMessage="handleMessage"></p:socket>
    <p:growl widgetVar="growl-msg" globalOnly="true" id="growl-msg" life="2000" />
    <script type="text/javascript">
        function handleMessage(facesmessage) {
               PF('growl-msg').renderMessage({"summary":"New Message",
                   "detail":facesmessage,
                   "severity":"info"})
        }
    </script>

触发通知,我称之为url。其中数据参数是将显示为咆哮的动态消息。

http://localhost:5050/WebAdmin/primepushpage/receive-message.jsf?data=Testing123

这是我收到的-message.xhtml

<ui:composition xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://xmlns.jcp.org/jsf/html"
    xmlns:f="http://xmlns.jcp.org/jsf/core"
    xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
    xmlns:p="http://primefaces.org/ui"
    template="../pages/template.xhtml">

    <ui:define name="content">
        <f:metadata>
            <f:viewParam name="data" value="#{pushMessageController.data}" />
        </f:metadata>

        <h:form>
        <p:remoteCommand name="remoteCommand" actionListener="#{pushMessageController.execute}" autoRun="true" />
        </h:form>
    </ui:define>

</ui:composition>

这是我的授权过滤器上的 doFilter 方法:

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws ServletException, IOException {    
            HttpServletRequest request = (HttpServletRequest) req;
            HttpServletResponse response = (HttpServletResponse) res;
            HttpSession session = request.getSession(false);
            String loginURL = request.getContextPath() + "/pages/index.jsf";

            boolean loggedIn = (session != null) && (session.getAttribute("user") != null);
            boolean loginRequest = request.getRequestURI().equals(loginURL);
            boolean resourceRequest = request.getRequestURI().startsWith(request.getContextPath() + ResourceHandler.RESOURCE_IDENTIFIER + "/");
            boolean ajaxRequest = "partial/ajax".equals(request.getHeader("Faces-Request"));
            boolean pushRequest = request.getRequestURI().contains("primepush");

            if (loggedIn || loginRequest || resourceRequest || pushRequest) {
                if (!resourceRequest) { // Prevent browser from caching restricted resources. See also 
                    response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); // HTTP 1.1.
                    response.setHeader("Pragma", "no-cache"); // HTTP 1.0.
                    response.setDateHeader("Expires", 0); // Proxies.
                }

                chain.doFilter(request, response); // So, just continue request.
            }
            else if (ajaxRequest) {
                response.setContentType("text/xml");
                response.setCharacterEncoding("UTF-8");
                response.getWriter().printf(AJAX_REDIRECT_XML, loginURL); // So, return special XML response instructing JSF ajax to send a redirect.
            }
            else {
                if(!response.isCommitted()){
                    response.sendRedirect(loginURL); // So, just perform standard synchronous redirect.
                    return;
                }
            }
        }

并且我在 web.xml 上添加了推送 servlet 映射,而且我的 pom.xml 已经具有大气运行时依赖性。我使用 Tomcat 7 作为容器。

我遇到了同样的问题。通过 ajax 刷新页面并且用户手动刷新页面后,我从 ocpsoft 重写了以下警告:
响应已经提交,不允许进一步的写操作。这可能会导致基础应用程序触发 IllegalStateException。

之后页面处于无效状态,需要第二次刷新。我花了很多时间试图解决这个问题,最后我尝试使用不同版本的大气运行时(起初我使用的是最新的 2.4.9)并发现它在 2.4.0 到 2.4 版本中都能正常工作。 2.

Primefaces 手册说您可以使用 2.3.RC6 和最新版本,但是对于低于 2.4.0 的版本,我在应用程序启动期间遇到了讨厌的异常。

我使用 Primefaces 6.0,应用程序部署在 Tomcat 8.0.39