Hi all,

I am trying to add websocket support to my existing REST application and I
would like to do it leveraging the Atmosphere framework API.

My application currently runs inside a Jetty 8 standalone container and it
is deployed as an exploded WAR.
The application itself is RESTful and uses CXF 3.0.5 and Spring 3.2.14 to
configure all REST endpoints, providers, etc.
I also use Guice for dependency injection and GuiceServlet to add some
Guice-friendly servlet filters to my environment.

In order to add support for websockets I tried several things but in the
end I always end up getting the same exception:

> java.lang.NullPointerException
> at
> org.eclipse.jetty.websocket.WebSocketFactory.upgrade(WebSocketFactory.java:238)
> at
> org.eclipse.jetty.websocket.WebSocketFactory.acceptWebSocket(WebSocketFactory.java:396)
> at
> org.atmosphere.container.JettyWebSocketUtil.doService(JettyWebSocketUtil.java:62)
> at
> org.atmosphere.container.JettyAsyncSupportWithWebSocket.service(JettyAsyncSupportWithWebSocket.java:66)
> at
> org.atmosphere.cpr.AtmosphereFramework.doCometSupport(AtmosphereFramework.java:2262)
> at org.atmosphere.cpr.AtmosphereServlet.doPost(AtmosphereServlet.java:190)
> at org.atmosphere.cpr.AtmosphereServlet.doGet(AtmosphereServlet.java:176)
> at javax.servlet.http.HttpServlet.service(HttpServlet.java:735)
> 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:1496)
> at
> org.eclipse.jetty.servlets.UserAgentFilter.doFilter(UserAgentFilter.java:82)
> at org.eclipse.jetty.servlets.GzipFilter.doFilter(GzipFilter.java:294)
> at
> org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1484)
> at
> com.google.inject.servlet.FilterChainInvocation.doFilter(FilterChainInvocation.java:66)
> at
> com.google.inject.servlet.ManagedFilterPipeline.dispatch(ManagedFilterPipeline.java:118)
> at com.google.inject.servlet.GuiceFilter.doFilter(GuiceFilter.java:113)
> at
> org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1476)
> at
> org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:501)
> 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:1086)
> at
> org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:429)
> at
> org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:193)
> at
> org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1020)
> 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:494)
> at
> org.eclipse.jetty.server.AbstractHttpConnection.headerComplete(AbstractHttpConnection.java:971)
> at
> org.eclipse.jetty.server.AbstractHttpConnection$RequestHandler.headerComplete(AbstractHttpConnection.java:1033)
> 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:696)
> at
> org.eclipse.jetty.io.nio.SelectChannelEndPoint$1.run(SelectChannelEndPoint.java:53)
> 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:745)


These are the things I tried:

I added jetty-websocket, cxf-rt-transports-websocket, and
atmosphere-annotations to my maven dependencies
I added the ChatService to my services and configured it in the same way I
initialize the others using Spring

Chat Service was modified like this because this is how we handle services
in our current application:

@WebService
> @Path("/chat")
> @Produces("application/json")
> @AtmosphereService(broadcaster = DefaultBroadcaster.class)
> @ImplementedBy(ChatServiceImpl.class)
> public interface ChatService {
>     @Suspend
>     @GET
>     public String connect();
>     @Broadcast(writeEntity = false)
>     @POST
>     public String broadcast(final String message);
> }
>
> public class ChatServiceImpl implements ChatService {
>
>     @Override
>     public String connect() {
>         return "";
>     }
>     @Override
>     public String broadcast(final String message) {
>         return message;
>     }
> }




I modified my jaxrs.xml file to enable websocket transport in CXF over
/websocket/* endpoints

<jaxrs:server id="websocketService" address="/websocket"
>               transportId="http://cxf.apache.org/transports/websocket";>
>     <jaxrs:serviceBeans>
>         <ref bean="chatServiceImpl" />     <!-- Atmosphere example -->
>         <ref bean="customerServiceImpl" /> <!-- CXF example -->
>     </jaxrs:serviceBeans>
>     <jaxrs:extensionMappings>
>         <entry key="json" value="application/json" />
>     </jaxrs:extensionMappings>
>     <jaxrs:providers>
>         <ref bean="jsonProvider" />
>         <ref bean="jsonMappingExceptionMapperProvider" />
>         <ref bean="jsonParseExceptionMapperProvider" />
>         <ref bean="exceptionMapperProvider" />
>     </jaxrs:providers>
> </jaxrs:server>




I added the client HTML file + all the JS files needed on the root of my
exploded WAR directory and I opened the chat page from Chrome.

This threw the NPE on
org.eclipse.jetty.websocket.WebSocketFactory.upgrade() you see above,
because the ThreadLocal AbstractHttpConnection is null.

Then I tried to switch my CXFServlet in web.xml with the AtmosphereServlet
because I read that I need to run CXF over Atmosphere and not viceversa.
My web.xml file now looks like:

<?xml version="1.0" encoding="ISO-8859-1"?>
> <web-app
>         xmlns="http://java.sun.com/xml/ns/javaee";
>         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
>         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
> http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd";
>         metadata-complete="true"
>         version="3.0">
>     <display-name>xxxx</display-name>
>     <context-param>
>         <param-name>contextConfigLocation</param-name>
>         <param-value>classpath:/cxf/ws.xml</param-value>
>     </context-param>
>     <listener>
>         <listener-class>org.atmosphere.cpr.SessionSupport</listener-class>
>     </listener>
>     <context-param>
>         <param-name>org.atmosphere.cpr.sessionSupport</param-name>
>         <param-value>true</param-value>
>     </context-param>
>     <!--<servlet>-->
>         <!--<servlet-name>CXFServlet</servlet-name>-->
>
> <!--<servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>-->
>         <!--<load-on-startup>1</load-on-startup>-->
>     <!--</servlet>-->
>     <!--<servlet-mapping>-->
>         <!--<servlet-name>CXFServlet</servlet-name>-->
>         <!--<url-pattern>/api/*</url-pattern>-->
>     <!--</servlet-mapping>-->
>     <servlet>
>         <servlet-name>AtmosphereServlet</servlet-name>
>         <servlet-class>org.atmosphere.cpr.AtmosphereServlet</servlet-class>
>         <init-param>
>
> <param-name>org.atmosphere.websocket.messageContentType</param-name>
>             <param-value>application/json</param-value>
>         </init-param>
>         <init-param>
>             <param-name>org.atmosphere.cpr.asyncSupport</param-name>
>
> <param-value>org.atmosphere.container.JettyAsyncSupportWithWebSocket</param-value>
>         </init-param>
>         <load-on-startup>1</load-on-startup>
>         <async-supported>true</async-supported>
>     </servlet>
>     <servlet-mapping>
>         <servlet-name>AtmosphereServlet</servlet-name>
>         <url-pattern>/api/*</url-pattern>
>     </servlet-mapping>
>     <!--<filter>-->
>         <!--<filter-name>AtmosphereFilter</filter-name>-->
>
> <!--<filter-class>org.atmosphere.cpr.AtmosphereFilter</filter-class>-->
>     <!--</filter>-->
>     <!--<filter-mapping>-->
>         <!--<filter-name>AtmosphereFilter</filter-name>-->
>         <!--<url-pattern>/api/*</url-pattern>-->
>     <!--</filter-mapping>-->
>     <filter>
>         <filter-name>GuiceFilter</filter-name>
>         <filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
>     </filter>
>     <filter-mapping>
>         <filter-name>GuiceFilter</filter-name>
>         <url-pattern>/*</url-pattern>
>     </filter-mapping>
>     <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/xhtml+xml,text/css,application/javascript,application/json,application/octet-stream,application/x-javascript,image/svg+xml,image/png,image/gif,image/jpeg</param-value>
>         </init-param>
>     </filter>
>     <filter-mapping>
>         <filter-name>GzipFilter</filter-name>
>         <url-pattern>/*</url-pattern>
>     </filter-mapping>
>     <!-- Force usage of prjzip mime type for downloads so IE will not
> think it's a .zip -->
>     <mime-mapping>
>         <extension>prjzip</extension>
>         <mime-type>application/prjzip</mime-type>
>     </mime-mapping>
>     <!-- Do not display stack traces on 500 -->
>     <error-page>
>         <error-code>500</error-code>
>         <location>/500.html</location>
>     </error-page>
>     <!--
>          Manage caching on static assets (which are the only assets served
>          by the Jetty DefaultServlet)
>          public: all static assets are non-user-specific and can be cached
>                  for all users equally
>          max-age=0: all static assets must be revalidated with the server
>                     before they can be served
>      -->
>     <servlet>
>         <servlet-name>default</servlet-name>
>
> <servlet-class>org.eclipse.jetty.servlet.DefaultServlet</servlet-class>
>         <init-param>
>           <param-name>cacheControl</param-name>
>           <param-value>public, max-age=0</param-value>
>         </init-param>
>     </servlet>
> </web-app>




As you can see I also tried to use Atmosphere as a filter but that resulted
in the same NPE you see above.
My original CXFServlet appears commented out in the web.xml file above and
I replaced that with AtmosphereServlet. Then I created the atmosphere.xml
file under META-INF with the following content:

> <atmosphere-handlers>
>     <!-- CXF -->
>     <atmosphere-handler support-session="false"
>                         context-root="/*"
>
> class-name="org.atmosphere.handler.ReflectorServletProcessor">
>         <property name="servletClassName"
>                   value="org.apache.cxf.transport.servlet.CXFServlet"/>
>     </atmosphere-handler>
> </atmosphere-handlers>


Doing this loads all my services using Spring at the server startup but
then all my endpoints just don't work and return a 200 OK empty response to
all requests..
Needless to say, the websocket endpoint is the only one responding to my
GET requests but it still throws the same NPE exception as it did the first
time.

I tried also to switch between different asyncSupport classes in the
init-param of the Servlet but the result is still the NPE with both
*JettyAsyncSupportWithWebSocket* and *JettyAsync30SupportWithWebSocket*.
If I use Jetty7Comet then I get a Websocket not supported error and my
Chrome client doesn't fall back to long polling due to cross-origin
requests not possible for ws URLs...

I also tried to disable all servlet filters but I still get the same error.

Can you please help me understand how can I add a simple service to my
existing CXF setup?

I also tested succesfully both the Atmosphere's rest-chat example and CXF's
distribution/src/main/release/samples/jax_rs/websocket example. They are
working as expected but I couldn't understand what is missing in my
configuration.

Thanks a lot in advance!

-- 
Roberto Francescangeli

Reply via email to