Fall-back to Comet never works in Tomcat with Vaadin 7.1 Push applications

I’ve been struggling with getting push working in my Vaadin application on Tomcat for a while now. And it appears that Tomcat’s Comet support is never used.

In Tomcat 6 (using the latest 6.0.37), Atmosphere correctly first attempts to use TomcatCometSupport. But because the request attribute “CometEvent” is never set, it always fails at line 105 in that class, resulting in this error message:

org.atmosphere.cpr.AtmosphereFramework doCometSupport WARNING: Failed using comet support: org.atmosphere.container.TomcatCometSupport, error: Tomcat failed to detect this is a Comet application because context.xml is missing or the Http11NioProtocol Connector is not enabled.
If that's not the case, you can also remove META-INF/context.xml and WEB-INF/lib/atmosphere-compat-tomcat.jar Is the Nio or Apr Connector enabled?
org.atmosphere.cpr.AtmosphereFramework doCometSupport WARNING: Using org.atmosphere.container.BlockingIOCometSupport

It also results in Atmosphere falling back to the generic class BlockingIOCometSupport. Further, any Tomcat Comet-specific code in that class is not executed because, again, the request attribute “CometEvent” is not set.

A similar situation happens with Tomcat 7 (using the latest 7.0.42). While the latest browsers use WebSockets, any browser that doesn’t support it (e.g., IE9) falls back to use streaming. Tomcat7AsyncSupportWithWebSocket is first tried, and the same error occurs for the same reason, resulting in a fallback to Tomcat7BIOSupportWithWebSocket.

In Atmosphere’s code, the “CometEvent” request attribute is set in the class AtmosphereServlet. The equivalent code is not present in Vaadin 7.1.

So for everyone who has spent hours messing with context.xml and nio/bio, it was a huge waste of your time. AFAICT you will always get this error message if you’re using Vaddin (as of 7.1.7) in Tomcat and the browser doesn’t support WebSockets (or with any browser if you’re using @Push(transport=STREAMING)).

In a trivial Vaadin test app running on Tomcat, it’s exactly the same as I described above–the fallback to Comet fails. However the generic fallback classes seem to work ok with that simple application. However, in my application, the generic classes BlockingIOCometSupport and Tomcat7BIOSupportWithWebSocket do not work. For example with IE9, the initial view loads just fine, but as soon as I interact with the application, it just hangs forever. It looks like it’s waiting for a response from the server, but it never updates the UI. Maybe it has to do with the fact that my application uses https, I’m not sure. I still have not been able to figure out exactly why this is happening.

But I needed to come up with a solution. So I was able to make Comet work in Tomcat 7 by making my servlet implement org.apache.catalina.comet.CometProcessor, adapting code found in org.atmosphere.cpr.AtmosphereServlet:

@Override
public void event(CometEvent cometEvent) throws IOException, ServletException {
    HttpServletRequest request = cometEvent.getHttpServletRequest();
    HttpServletResponse response = cometEvent.getHttpServletResponse();
    
    //Set the request attribute that Atmophere expects in order to use Comet
    request.setAttribute(Tomcat7CometSupport.COMET_EVENT, cometEvent);
    
    RequestUtils.checkAndSetLocale(request);
    RequestUtils.checkAndSetSecurityContext(request, response);
    
    EventType eventType = cometEvent.getEventType();
    if (eventType==EventType.BEGIN) {
        //Delegate to Servlet's service method
        service(request, response);
        response.flushBuffer();
    }
    
    if (eventType==EventType.ERROR || eventType==EventType.END) {
        log.trace("Closing. eventType={}", eventType);
        cometEvent.close();
    }

    if (webSocketSupported(cometEvent)){
        cometEvent.close();
    }
}

private boolean webSocketSupported(CometEvent cometEvent) {
    // https://github.com/Atmosphere/atmosphere/issues/920
    String transport = cometEvent.getHttpServletRequest().getParameter(HeaderConfig.X_ATMOSPHERE_TRANSPORT);
    boolean webSocketSupported = (transport != null && transport.equalsIgnoreCase(HeaderConfig.WEBSOCKET_TRANSPORT));
    if (!webSocketSupported) {
        try {
            Enumeration<String> connection = cometEvent.getHttpServletRequest().getHeaders("Connection");
            if (connection != null && connection.hasMoreElements()) {
                String e = connection.nextElement().toString().split(",");
                for (String upgrade : e) {
                    if (upgrade.trim().equalsIgnoreCase(WEBSOCKET_UPGRADE)) {
                        webSocketSupported = true;
                        break;
                    }
                }
            }
        } catch (Exception ex) {
            log.trace("", ex);
        }
    }
    return webSocketSupported;
}

Perhaps I’ve missed something obvious, but if I’m right, this at minimum needs to be better documented. I doubt I’m the only one who’s been banging his head against the wall over this.

Has anyone seen Vaadin/Atmosphere successfully fall back to Comet in Tomcat?

–Darren Evenson

Thanks for investigating this! Yeah, it seems obvious in retrospect that async Comet communication is not possible without implementing the container-specific interfaces. Unfortunately, this is probably not something we can do in VaadinServlet due to the extra dependencies introduced.

However, a set of server-specific VaadinServlet subclasses might not be a bad idea. It could be an addon or even included in the core - in the vaadin-push package to keep dependencies sorted.

Johannes, thank you for the response.

I’ve done some more research and experimenting, and I found another option for Tomcat 7 and nio that does not involve implementing Tomcat’s proprietary CometProcessor interface.

Again, maybe this seems obvious in retrospect, but if I set the atmosphere property

org.atmosphere.useWebSocketAndServlet3=true

then Atmosphere selects the asynchronous processor Tomcat7Servlet30SupportWithWebSocket. With this option, newer browsers use WebSockets and IE9 falls back to streaming, but this time does not attempt to look for the CometEvent in the request, and uses a streaming implementation that works for my application. Maybe the difference is blocking vs. non-blocking? I’m not totally sure.

As far as I can tell, the following configuration is required to make this option work (in addition to enabling push in your application, etc.):

  • Tomcat 7 (latest version is currently 7.0.42)
  • nio connector. If compression is enabled, make sure the compressableMimeType property does not include “text/plain”. (This caused random hangs for me)
  • Web.xml uses the 3.0 doctype
  • Your servlet has true
  • All web filters have true

This is the only Tomcat configuration I know of that works for my application (without implementing Tomcat proprietary interfaces).

I hope this helps other people trying to implement push in Tomcat.

– Darren Evenson

so where exactly do you put the value useWebSocketAndServlet3=true?

As a servlet init parameter, either in web.xml or in the @WebServlet annotation. The param name should be “org.atmosphere.useWebSocketAndServlet3” and the value “true”.

The init parameter for which servlet exactly?

Your Vaadin application’s VaadinServlet.

I tried it according to the tips above:

web.xml (3.0):

    <servlet>
        <servlet-name>ServiceContainerServlet</servlet-name>
        <servlet-class>com.vaadin.server.VaadinServlet</servlet-class>
        <init-param>
            <param-name>UI</param-name>
            <param-value>ch.ergon.medusa.webapp.view.ServiceContainerUI</param-value>
        </init-param>
        <init-param>
            <param-name>org.atmosphere.useWebSocketAndServlet3</param-name>
            <param-value>true</param-value>
        </init-param>
        <async-supported>true</async-supported>
    </servlet>

Additional connector within server.xml:

<Connector connectionTimeout="20000" port="8081" protocol="org.apache.coyote.http11.Http11NioProtocol" redirectPort="8443"/>

My UI uses push with streaming:

@Push(transport = Transport.STREAMING)

Unfortunately when I access my web application, I get this error:

févr. 04, 2014 4:36:21 PM org.atmosphere.cpr.AtmosphereFramework doCometSupport
SEVERE: AtmosphereFramework exception
java.lang.IllegalStateException: Not supported.

What did I configure wrong?

Could you post the whole exception stacktrace, if any?

Yes, sorry stupid question. I wasn’t thinking :<

Thank you for getting back to me again. Of course:

févr. 05, 2014 8:08:56 AM org.atmosphere.cpr.AtmosphereFramework addAtmosphereHandler
INFO: Installed AtmosphereHandler com.vaadin.server.communication.PushHandler mapped to context-path: /*
févr. 05, 2014 8:08:56 AM org.atmosphere.cpr.DefaultBroadcaster <init>
INFO: /* support Out Of Order Broadcast: false
févr. 05, 2014 8:08:56 AM org.atmosphere.cpr.AtmosphereFramework autoDetectWebSocketHandler
INFO: Auto detecting WebSocketHandler in /WEB-INF/classes/
févr. 05, 2014 8:08:56 AM org.atmosphere.cpr.AtmosphereFramework initWebSocket
INFO: Installed WebSocketProtocol org.atmosphere.websocket.protocol.SimpleHttpProtocol 
févr. 05, 2014 8:08:56 AM org.atmosphere.cpr.AtmosphereFramework autoDetectContainer
INFO: Atmosphere is using async support: org.atmosphere.container.Tomcat7Servlet30SupportWithWebSocket running under container: Apache Tomcat/7.0.47 using javax.servlet/3.0
févr. 05, 2014 8:08:56 AM org.atmosphere.cpr.AtmosphereFramework configureAtmosphereInterceptor
INFO: Installed Default AtmosphereInterceptor [Android Interceptor Support, SSE Interceptor Support, JSONP Interceptor Support, Atmosphere JavaScript Protocol, Browser disconnection detection]
. Set org.atmosphere.cpr.AtmosphereInterceptor.disableDefaults in your xml to disable them.
févr. 05, 2014 8:08:56 AM org.atmosphere.cpr.AtmosphereFramework init
WARNING: No BroadcasterCache configured. Broadcasted message between client reconnection will be LOST. It is recommended to configure the org.atmosphere.cache.UUIDBroadcasterCache
févr. 05, 2014 8:08:56 AM org.atmosphere.cpr.AtmosphereFramework init
INFO: Shared ExecutorService supported: true
févr. 05, 2014 8:08:56 AM org.atmosphere.cpr.AtmosphereFramework init
INFO: HttpSession supported: true
févr. 05, 2014 8:08:56 AM org.atmosphere.cpr.AtmosphereFramework init
INFO: Using BroadcasterFactory: org.atmosphere.cpr.DefaultBroadcasterFactory
févr. 05, 2014 8:08:56 AM org.atmosphere.cpr.AtmosphereFramework init
INFO: Using WebSocketProcessor: org.atmosphere.websocket.DefaultWebSocketProcessor
févr. 05, 2014 8:08:56 AM org.atmosphere.cpr.AtmosphereFramework init
INFO: Using Broadcaster: org.atmosphere.cpr.DefaultBroadcaster
févr. 05, 2014 8:08:56 AM org.atmosphere.cpr.AtmosphereFramework init
INFO: Atmosphere Framework 1.0.14.vaadin4 started.
févr. 05, 2014 8:08:56 AM org.atmosphere.cpr.AtmosphereFramework interceptor
INFO: Installed AtmosphereInterceptor  Track Message Size Interceptor using |. 
févr. 05, 2014 8:08:56 AM org.atmosphere.cpr.AtmosphereFramework doCometSupport
Grave: AtmosphereFramework exception
java.lang.IllegalStateException: Not supported.
    at org.apache.catalina.connector.Request.startAsync(Request.java:1676)
    at org.apache.catalina.connector.RequestFacade.startAsync(RequestFacade.java:1031)
    at javax.servlet.ServletRequestWrapper.startAsync(ServletRequestWrapper.java:379)
    at org.atmosphere.cpr.AtmosphereRequest.startAsync(AtmosphereRequest.java:565)
    at org.atmosphere.container.Servlet30CometSupport.suspend(Servlet30CometSupport.java:137)
    at org.atmosphere.container.Servlet30CometSupport.service(Servlet30CometSupport.java:103)
    at org.atmosphere.container.Tomcat7Servlet30SupportWithWebSocket.doService(Tomcat7Servlet30SupportWithWebSocket.java:65)
    at org.atmosphere.container.TomcatWebSocketUtil.doService(TomcatWebSocketUtil.java:87)
    at org.atmosphere.container.Tomcat7Servlet30SupportWithWebSocket.service(Tomcat7Servlet30SupportWithWebSocket.java:61)
    at org.atmosphere.cpr.AtmosphereFramework.doCometSupport(AtmosphereFramework.java:1448)
    at com.vaadin.server.communication.PushRequestHandler.handleRequest(PushRequestHandler.java:109)
    at com.vaadin.server.VaadinService.handleRequest(VaadinService.java:1371)
    at com.vaadin.server.VaadinServlet.service(VaadinServlet.java:238)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:728)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    at ch.ergon.medusa.util.tomcat.RequestCleanupFilter.doFilter(RequestCleanupFilter.java:48)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:100)
    at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:953)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1041)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:603)
    at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:312)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:724)

févr. 05, 2014 8:08:56 AM com.vaadin.server.DefaultErrorHandler doDefault
Grave: 
java.lang.IllegalStateException: Not supported.
    at org.apache.catalina.connector.Request.startAsync(Request.java:1676)
    at org.apache.catalina.connector.RequestFacade.startAsync(RequestFacade.java:1031)
    at javax.servlet.ServletRequestWrapper.startAsync(ServletRequestWrapper.java:379)
    at org.atmosphere.cpr.AtmosphereRequest.startAsync(AtmosphereRequest.java:565)
    at org.atmosphere.container.Servlet30CometSupport.suspend(Servlet30CometSupport.java:137)
    at org.atmosphere.container.Servlet30CometSupport.service(Servlet30CometSupport.java:103)
    at org.atmosphere.container.Tomcat7Servlet30SupportWithWebSocket.doService(Tomcat7Servlet30SupportWithWebSocket.java:65)
    at org.atmosphere.container.TomcatWebSocketUtil.doService(TomcatWebSocketUtil.java:87)
    at org.atmosphere.container.Tomcat7Servlet30SupportWithWebSocket.service(Tomcat7Servlet30SupportWithWebSocket.java:61)
    at org.atmosphere.cpr.AtmosphereFramework.doCometSupport(AtmosphereFramework.java:1448)
    at com.vaadin.server.communication.PushRequestHandler.handleRequest(PushRequestHandler.java:109)
    at com.vaadin.server.VaadinService.handleRequest(VaadinService.java:1371)
    at com.vaadin.server.VaadinServlet.service(VaadinServlet.java:238)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:728)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    at ch.ergon.medusa.util.tomcat.RequestCleanupFilter.doFilter(RequestCleanupFilter.java:48)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:100)
    at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:953)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1041)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:603)
    at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:312)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:724)

févr. 05, 2014 8:08:56 AM org.apache.catalina.core.StandardWrapperValve invoke
Grave: Servlet.service() for servlet [ServiceContainerServlet]
 in context with path 
[/wtptest-servicecontainer] threw exception [com.vaadin.server.ServiceException: java.lang.IllegalStateException: Not supported.]
 with root cause
java.lang.IllegalStateException: Not supported.
    at org.apache.catalina.connector.Request.startAsync(Request.java:1676)
    at org.apache.catalina.connector.RequestFacade.startAsync(RequestFacade.java:1031)
    at javax.servlet.ServletRequestWrapper.startAsync(ServletRequestWrapper.java:379)
    at org.atmosphere.cpr.AtmosphereRequest.startAsync(AtmosphereRequest.java:565)
    at org.atmosphere.container.Servlet30CometSupport.suspend(Servlet30CometSupport.java:137)
    at org.atmosphere.container.Servlet30CometSupport.service(Servlet30CometSupport.java:103)
    at org.atmosphere.container.Tomcat7Servlet30SupportWithWebSocket.doService(Tomcat7Servlet30SupportWithWebSocket.java:65)
    at org.atmosphere.container.TomcatWebSocketUtil.doService(TomcatWebSocketUtil.java:87)
    at org.atmosphere.container.Tomcat7Servlet30SupportWithWebSocket.service(Tomcat7Servlet30SupportWithWebSocket.java:61)
    at org.atmosphere.cpr.AtmosphereFramework.doCometSupport(AtmosphereFramework.java:1448)
    at com.vaadin.server.communication.PushRequestHandler.handleRequest(PushRequestHandler.java:109)
    at com.vaadin.server.VaadinService.handleRequest(VaadinService.java:1371)
    at com.vaadin.server.VaadinServlet.service(VaadinServlet.java:238)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:728)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    at ch.ergon.medusa.util.tomcat.RequestCleanupFilter.doFilter(RequestCleanupFilter.java:48)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:100)
    at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:953)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1041)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:603)
    at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:312)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:724)

Sorry for this wall of text ;/

I have almost exactly the same configuration but I’m getting a different error:

java.lang.ClassCastException: org.atmosphere.cpr.AtmosphereInterceptorWriter cannot be cast to org.atmosphere.websocket.WebSocket

Any ideas?

Google comes up with nothing.

Which Tomcat version are you using? Some sources on the internet imply that it might simply be a Tomcat bug…

Also,
this StackOverflow question
might be helpful.

Could you post the stack trace as well? It seems for some reason something is getting confused about whether websocket is supported or not.