SOAP over Websocket with Appache CXF and Embedded

2019-08-23 05:00发布

问题:

I have been trying to set a a SOAP endpoint with Websocket as transport protocol via CXF and implement invoke it via CXF. With Embeded jetty. I have tried a couple of approaches non of the aproaches worked unfortunatly. Here is what I did:

Aproach 1. According to CXF documentation websocket is supported as transport protocol and its support is given via

<dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-transports-websocket</artifactId>
            <version>3.3.2</version>
</dependency>

I have setup the following dependencies:

 <dependency>
        <groupId>org.asynchttpclient</groupId>
        <artifactId>async-http-client</artifactId>
        <version>2.0.39</version>
    </dependency>
    <dependency>
        <groupId>org.apache.cxf</groupId>
        <artifactId>cxf-rt-frontend-jaxws</artifactId>
        <version>3.3.2</version>
    </dependency>
    <dependency>
        <groupId>org.apache.cxf</groupId>
        <artifactId>cxf-rt-transports-http</artifactId>
        <version>3.3.2</version>
    </dependency>
    <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-transports-http-jetty</artifactId>
            <version>3.3.2</version>
    </dependency>

The code I executo is the following:

Endpoint endpoint = Endpoint.create(new MyHelloWorldServicePortType() {

            @Override
            public String sayHello(HelloMessage message) throws FaultMessage {

                return message.sayHello();
            }
};
((org.apache.cxf.jaxws.EndpointImpl)endpoint).getFeatures().add(new 
WSAddressingFeature());
endpoint.publish("ws://localhost:8088/MyHelloWorldService"  );
URL wsdlDocumentLocation =  new URL("file:/path to wsdl file");
 String servicePart = "MyHelloWorldService";
 String namespaceURI = "mynamespaceuri";
 QName serviceQN = new QName(namespaceURI, servicePart);
Service service = Service.create(wsdlDocumentLocation, serviceQN);
 MyHelloWorldServicePortType port = service.getPort( MyHelloWorldServicePortType.class);

portType.sayHello(new HelloMessage("Say Hello"));

The result of this code is:

SEVERE: [ws] onError java.util.concurrent.TimeoutException: Request timeout to not-connected after 60000 ms at org.asynchttpclient.netty.timeout.TimeoutTimerTask.expire(TimeoutTimerTask.java:43) at org.asynchttpclient.netty.timeout.RequestTimeoutTimerTask.run(RequestTimeoutTimerTask.java:48) at io.netty.util.HashedWheelTimer$HashedWheelTimeout.expire(HashedWheelTimer.java:682) at io.netty.util.HashedWheelTimer$HashedWheelBucket.expireTimeouts(HashedWheelTimer.java:757) at io.netty.util.HashedWheelTimer$Worker.run(HashedWheelTimer.java:485) at java.base/java.lang.Thread.run(Thread.java:834)

jun. 12, 2019 1:13:33 P.M. org.apache.cxf.transport.websocket.ahc.AhcWebSocketConduit$AhcWebSocketWrappedOutputStream connect SEVERE: unable to connect java.util.concurrent.ExecutionException: java.util.concurrent.TimeoutException: Request timeout to not-connected after 60000 ms at java.base/java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:395) at java.base/java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1999) at org.asynchttpclient.netty.NettyResponseFuture.get(NettyResponseFuture.java:172) at org.apache.cxf.transport.websocket.ahc.AhcWebSocketConduit$AhcWebSocketWrappedOutputStream.connect(AhcWebSocketConduit.java:309) at org.apache.cxf.transport.websocket.ahc.AhcWebSocketConduit$AhcWebSocketWrappedOutputStream.setupWrappedStream(AhcWebSocketConduit.java:167) at org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.handleHeadersTrustCaching(HTTPConduit.java:1343) at org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.onFirstWrite(HTTPConduit.java:1304) at org.apache.cxf.io.AbstractWrappedOutputStream.write(AbstractWrappedOutputStream.java:47) at org.apache.cxf.io.AbstractThresholdOutputStream.write(AbstractThresholdOutputStream.java:69) at org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.close(HTTPConduit.java:1356) at org.apache.cxf.transport.websocket.ahc.AhcWebSocketConduit$AhcWebSocketWrappedOutputStream.close(AhcWebSocketConduit.java:139) at org.apache.cxf.transport.AbstractConduit.close(AbstractConduit.java:56)

I have absolutly no idea why. When I try to connect via websocket chrome client on the URL. It says success. At the same time when connecting via the client it says Timeout.

Aproach 2.

I decided to cheat CXF and provide a handmade Websocket endpoint that will be used as a front to the CXF webservice. The idea is that the Client will send a message via websocket the message will be unwrapped and then sent over CXF. This aproach is very similar to the aproach here but here it uses JMS as transport

https://github.com/pbielicki/soap-websocket-cxf

In oprder to do this I created the following Websocket enpoint:

@ServerEndpoint("/jaxWSFront")
public class JaxWSFrontEnd {


      @OnOpen
      public void onOpen(final Session session) {
           System.out.println("Hellooo");

      }

      @OnMessage
      public void onMessage(String mySoapMessage,final Session session) throws Exception{
    //  The goal here is to get the soap message and redirect it via SOAP web //service. The JaxWSFacade acts as a point that understands websocket and then //gets the soap content and sends it to enpoint that understands SOAP.

       session.getBasicRemote().sendText("Helllo . Now you see me.");

       System.out.println("Hellooo again");
      }

      @OnClose
      public void onClose(Session session, CloseReason closeReason) {
           System.out.println("Hellooo");
      }

      @OnError
      public void onError(Throwable t, Session session) {
           System.out.println("Hellooo");
      }

} 

Now I pointed my Client proxy to the jaxWsFrontEnd instead of the webservice endpoint. My expectation is that I will recieve the SOAP message in the onMessage method and then I will be able to forwards to SOAP to the CXF web service.

Now my code looks like this:

server = new Server(8088);

            ServletContextHandler context = new ServletContextHandler();
            context.setContextPath( "/" );
            server.setHandler(context);

            ServerContainer container = WebSocketServerContainerInitializer.configureContext(context);
            container.addEndpoint(JaxWSFrontEnd.class);

            server.setHandler( context );
            server.start();
  Endpoint endpoint = Endpoint.create(new MyHelloWorldServicePortType() {

                @Override
                public String sayHello(HelloMessage message) throws FaultMessage {

                    return message.sayHello();
                }
    };
    ((org.apache.cxf.jaxws.EndpointImpl)endpoint).getFeatures().add(new 
    WSAddressingFeature());

    URL wsdlDocumentLocation =  new URL("file:/path to wsdl file");
     String servicePart = "MyHelloWorldService";
     String namespaceURI = "mynamespaceuri";
     QName serviceQN = new QName(namespaceURI, servicePart);
    Service service = Service.create(wsdlDocumentLocation, serviceQN);
     MyHelloWorldServicePortType port = service.getPort( MyHelloWorldServicePortType.class);

    portType.sayHello(new HelloMessage("Say Hello"));

For the second aproach I had in addition to the aproach 1 the following dependencies:

<dependency>
            <groupId>org.eclipse.jetty.websocket</groupId>
            <artifactId>websocket-common</artifactId>
                    </dependency>
       <dependency>
          <groupId>org.eclipse.jetty.websocket</groupId>
          <artifactId>javax-websocket-server-impl</artifactId>

        </dependency>

Result from aproach 2 is absolutly the same as Aproach 1 the exceptions I recieve are the same, with one minor difference. When I use the the Chrome websocket client and point it directly the the jaxWsFrontend I am able to successfuly send a message. Why I am not able to connect to websocket wia the CXF websocket transport mechanisms ???? What am I doing wrong ?

UPDATE: enabling the loging from NETTY. It apears that netty has thrown java.lang.NoSuchMethodError: io.netty.channel.DefaultChannelId.newInstance()Lio/netty/channel/DefaultChannelId;

Maybe I have a version compatability issue with netty. The version I can see is imported in the project is 4.1.33. It is a transitive dependency I don|t have it declared.

回答1:

Ok I actualy managed to crack it alone. I will post the answer for completion. Apparantly CXF guys should update their documentation IMO. On their website it is stated that in order to enable Websocket as transport protocol we need cxf-rt-transports-websocket dependency.

What they do not say is that you in addition need async-http-client not any version but 2.0.39 a prettey old one. The problem is that it automaticaly includes transitive dependencies to netty 4.1 and the error specified above begins to manifest. What you actualy need is nett 4.0.56

Here is the fragment that made the things work for me:

<dependency>
            <groupId>org.asynchttpclient</groupId>
            <artifactId>async-http-client</artifactId>
            <version>2.0.39</version>
             <exclusions>
                <exclusion>
                    <groupId>io.netty</groupId>
                    <artifactId>netty-buffer</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>io.netty</groupId>
                    <artifactId>netty-codec-http</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>io.netty</groupId>
                    <artifactId>netty-handler</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>io.netty</groupId>
                    <artifactId>netty-transport-native-epoll</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>io.netty</groupId>
                    <artifactId>netty-transport</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>io.netty</groupId>
                    <artifactId>netty-common</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>io.netty</groupId>
                    <artifactId>netty-codec</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>io.netty</groupId>
                    <artifactId>netty-all</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

    <dependency>
        <groupId>io.netty</groupId>
        <artifactId>netty-all</artifactId>
        <version>4.0.56.Final</version>
    </dependency>
    <dependency>
        <groupId>org.apache.cxf</groupId>
        <artifactId>cxf-rt-transports-websocket</artifactId>
        <version>3.3.2</version>
    </dependency>
    <dependency>
        <groupId>org.apache.cxf</groupId>
        <artifactId>cxf-rt-frontend-jaxws</artifactId>
        <version>3.3.2</version>
    </dependency>
    <dependency>
        <groupId>org.apache.cxf</groupId>
        <artifactId>cxf-rt-transports-http</artifactId>
        <version>3.3.2</version>
    </dependency>
    <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-transports-http-jetty</artifactId>
            <version>3.3.2</version>
    </dependency>

Aproach 1 is working Aproach 2 I managed to trigger the onConnect event, the onMessage timedout, but in my opinion it should work I am missing something small. Anyway I don|t have more time to spent and I am happy with Aproach 1.