I upgraded my application from 7.0 to 7.1 over the last couple days and the actual upgrade was painless as 7.1 appears to be completely backward compatible. Here’s what I learned in the process, including enabling push:
I switched from ProgressIndicator to ProgressBar as ProgressIndicator is now deprecated. This required me to call UI.setPollInterval to get server side updates (before I enabled push described below). One issue I ran into was that different ProgressIndicators had different polling intervals. If I know that a server side computation would be done in a second or two, I used a short poll interval. For longer computations, I used a long poll interval. That isn’t really an option now because there is a single poll interval. You’ll have to just pick some compromise value.
I then switched all my manual UI locking in background threads to using UI.access(). While the access() method is nice and less error prone than manual locking/unlocking, I did find it a bit tricky to write a utility class that will let me easily execute tasks that can be cancelled because you now have two threads of background execute: one doing the work, and one doing the UI update in the UI.access() call. I have some tricky threading code that I believe does what I need, but I really had to think through the various race conditions. It would be nice if Vaadin provided a utility class like a BackgroundUITask that had a doWork and doUpdate method, the former being called in an unlocked thread and the latter called with the UI lock. I could then do something like UI.workAndAccess(new MyBackgroundTask()) and it would handle running the two methods and support MyBackgroundTask.cancel() logically. I can share some code on how I did this if anyone is interested.
I enabled push and with Websockets and everything looked good with Jetty 8 server side, FF, Chrome, and Safari client side. I just added jetty-websockets and vaadin-push to my dependencies and then followed the configuration directions in the Vaadin Wiki.
I then tried HTTP Streaming and everything broke. I spent more time than I’d like to admit tracking down the issue. In the end, I found that I had the GzipFilter configured in my web.xml for Jetty. Apparently this filter buffers responses until the response is closed (or based on some other configuration parameters) and it therefore wasn’t sending the partial Atmosphere JSON responses to the client. I was able to add a
filter init parameter
to the filter to ignore the Vaadin push path:
<filter>
<filter-name>GzipFilter</filter-name>
<filter-class>org.eclipse.jetty.servlets.GzipFilter</filter-class>
<init-param>
<param-name>mimeTypes</param-name>
<param-value>text/html,text/plain,text/xml,application/json,application/xhtml+xml,text/css,application/javascript,image/svg+xml</param-value>
</init-param>
<init-param>
<param-name>excludePathPatterns</param-name>
<param-value>.*/PUSH/</param-value>
</init-param>
</filter>
The next issue I ran into was that Websockets were working locally, but would break when I deployed to Amazon EC2. At first I thought this was an EC2 security group issue but I tracked it down to a custom HTTP proxy that I was using for my remote deployments. My proxy, similar to the ProxyServlet in Jetty, doesn’t proxy Websockets. The good news is that Vaadin falls back to HTTP Streaming and things work (aside from the Gzip issue above). The bad news is that I get this exception in my log:
[2013-08-01 12:06:52,303]
[WARN ]
[org.eclipse.jetty.servlet.ServletHandler ]
[qtp904581127-24 ]
: /portalui/app/PUSH/
com.vaadin.server.ServiceException: java.lang.NullPointerException
at com.vaadin.server.VaadinService.handleExceptionDuringRequest(VaadinService.java:1410)
at com.vaadin.server.VaadinService.handleRequest(VaadinService.java:1364)
at com.vaadin.server.VaadinServlet.service(VaadinServlet.java:238)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:848)
at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:684)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1448)
at org.eclipse.jetty.servlets.UserAgentFilter.doFilter(UserAgentFilter.java:82)
at org.eclipse.jetty.servlets.GzipFilter.doFilter(GzipFilter.java:256)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1419)
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:455)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:137)
at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:557)
at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:231)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1075)
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:384)
at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:193)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1009)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:135)
at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:255)
at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:154)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:116)
at org.eclipse.jetty.server.Server.handle(Server.java:370)
at org.eclipse.jetty.server.AbstractHttpConnection.handleRequest(AbstractHttpConnection.java:489)
at org.eclipse.jetty.server.AbstractHttpConnection.headerComplete(AbstractHttpConnection.java:949)
at org.eclipse.jetty.server.AbstractHttpConnection$RequestHandler.headerComplete(AbstractHttpConnection.java:1011)
at org.eclipse.jetty.http.HttpParser.parseNext(HttpParser.java:644)
at org.eclipse.jetty.http.HttpParser.parseAvailable(HttpParser.java:235)
at org.eclipse.jetty.server.AsyncHttpConnection.handle(AsyncHttpConnection.java:82)
at org.eclipse.jetty.io.nio.SelectChannelEndPoint.handle(SelectChannelEndPoint.java:668)
at org.eclipse.jetty.io.nio.SelectChannelEndPoint$1.run(SelectChannelEndPoint.java:52)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:608)
at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:543)
at java.lang.Thread.run(Thread.java:722)
Caused by: java.lang.NullPointerException
at org.atmosphere.client.TrackMessageSizeInterceptor.inspect(TrackMessageSizeInterceptor.java:96)
at org.atmosphere.cpr.AsynchronousProcessor.invokeInterceptors(AsynchronousProcessor.java:286)
at org.atmosphere.cpr.AsynchronousProcessor.action(AsynchronousProcessor.java:243)
at org.atmosphere.cpr.AsynchronousProcessor.suspended(AsynchronousProcessor.java:166)
at org.atmosphere.container.Jetty7CometSupport.service(Jetty7CometSupport.java:96)
at org.atmosphere.container.JettyAsyncSupportWithWebSocket.service(JettyAsyncSupportWithWebSocket.java:70)
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:1352)
...
The issue appears to be that the proxy strips the “Upgrade: websocket” header when proxing the request and this causes a NPE in Atmosphere. It seems like this should be handled more gracefully by Vaadin or Atmosphere to avoid the exception. A workaround I found was to modify my proxy to return a 200 (or anything other than 101) when it sees an “Upgrade: websocket” header because it will never be able to upgrade the protocol.
I use Shiro for all my authz/authc and I immediately ran into issues because of the way async servlets work with push. You can read my full solution
here
but the gist of it is that Shiro’s SecurityUtils can’t be used.
In the end, I’ve found that the combination of UI.access() and push have made my application feel much more responsive. My next step is to test all this out behind my real production load balancers but so far everything is looking good.