带有 Appache CXF 和嵌入式 Jetty 的 SOAP over Websocket
SOAP over Websocket with Appache CXF and Embedded Jetty
我一直在尝试通过 CXF 设置一个使用 Websocket 作为传输协议的 SOAP 端点,并通过 CXF 实现调用它。带有嵌入式码头。不幸的是,我尝试了几种方法,但没有一种方法有效。这是我所做的:
方法 1. 根据 CXF 文档,支持将 websocket 作为传输协议,并通过
提供支持
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-websocket</artifactId>
<version>3.3.2</version>
</dependency>
我已经设置了以下依赖项:
<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>
我执行的代码如下:
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"));
这段代码的结果是:
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)
我完全不知道为什么。当我尝试通过 URL 上的 websocket chrome 客户端连接时。它说成功。同时通过客户端连接时显示超时。
方法 2.
我决定欺骗 CXF 并提供一个手工制作的 Websocket 端点,它将用作 CXF 网络服务的前端。这个想法是客户端将通过 websocket 发送消息,消息将被解包,然后通过 CXF 发送。这种方法与此处的方法非常相似,但这里它使用 JMS 作为传输
https://github.com/pbielicki/soap-websocket-cxf
为了执行此操作,我创建了以下 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");
}
}
现在我将我的客户端代理指向 jaxWsFrontEnd 而不是 web 服务端点。我的期望是我将在 onMessage 方法中收到 SOAP 消息,然后我将能够将 SOAP 转发到 CXF Web 服务。
现在我的代码如下所示:
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"));
对于第二个方法,除了方法 1 之外,我还有以下依赖项:
<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>
方法 2 的结果与方法 1 完全相同,我收到的异常是相同的,只有一点点不同。 当我使用 Chrome websocket 客户端并将其直接指向 jaxWsFrontend 时,我能够成功发送消息。 为什么我无法通过 CXF 连接到 websocket websocket 传输机制 ????我做错了什么?
更新:启用从 NETTY 登录。看来 netty 已经抛出了 java.lang.NoSuchMethodError: io.netty.channel.DefaultChannelId.newInstance()Lio/netty/channel/DefaultChannelId;
可能是我的netty版本兼容问题。我在项目中看到导入的版本是4.1.33。这是一个传递依赖,我没有声明它。
好吧,我居然一个人破解了。我会 post 完成答案。显然,CXF 人员应该更新他们的 IMO 文档。在他们的网站上声明,为了启用 Websocket 作为传输协议,我们需要
cxf-rt-transports-websocket 依赖。
他们没有说的是你还需要 async-http-client 不是任何版本而是 2.0.39 一个漂亮的旧的。问题是它自动包含对 netty 4.1 的传递依赖,并且上面指定的错误开始显现。你实际需要的是nett 4.0.56
这是让这些东西对我有用的片段:
<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>
方法 1 有效
方法 2 我设法触发了 onConnect 事件,即 onMessage 超时,但我认为它应该可以工作,但我遗漏了一些小东西。不管怎样,我没有更多的时间可以花,我对 Aproach 1 很满意。
我一直在尝试通过 CXF 设置一个使用 Websocket 作为传输协议的 SOAP 端点,并通过 CXF 实现调用它。带有嵌入式码头。不幸的是,我尝试了几种方法,但没有一种方法有效。这是我所做的:
方法 1. 根据 CXF 文档,支持将 websocket 作为传输协议,并通过
提供支持<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-websocket</artifactId>
<version>3.3.2</version>
</dependency>
我已经设置了以下依赖项:
<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>
我执行的代码如下:
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"));
这段代码的结果是:
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)
我完全不知道为什么。当我尝试通过 URL 上的 websocket chrome 客户端连接时。它说成功。同时通过客户端连接时显示超时。
方法 2.
我决定欺骗 CXF 并提供一个手工制作的 Websocket 端点,它将用作 CXF 网络服务的前端。这个想法是客户端将通过 websocket 发送消息,消息将被解包,然后通过 CXF 发送。这种方法与此处的方法非常相似,但这里它使用 JMS 作为传输
https://github.com/pbielicki/soap-websocket-cxf
为了执行此操作,我创建了以下 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");
}
}
现在我将我的客户端代理指向 jaxWsFrontEnd 而不是 web 服务端点。我的期望是我将在 onMessage 方法中收到 SOAP 消息,然后我将能够将 SOAP 转发到 CXF Web 服务。
现在我的代码如下所示:
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"));
对于第二个方法,除了方法 1 之外,我还有以下依赖项:
<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>
方法 2 的结果与方法 1 完全相同,我收到的异常是相同的,只有一点点不同。 当我使用 Chrome websocket 客户端并将其直接指向 jaxWsFrontend 时,我能够成功发送消息。 为什么我无法通过 CXF 连接到 websocket websocket 传输机制 ????我做错了什么?
更新:启用从 NETTY 登录。看来 netty 已经抛出了 java.lang.NoSuchMethodError: io.netty.channel.DefaultChannelId.newInstance()Lio/netty/channel/DefaultChannelId;
可能是我的netty版本兼容问题。我在项目中看到导入的版本是4.1.33。这是一个传递依赖,我没有声明它。
好吧,我居然一个人破解了。我会 post 完成答案。显然,CXF 人员应该更新他们的 IMO 文档。在他们的网站上声明,为了启用 Websocket 作为传输协议,我们需要 cxf-rt-transports-websocket 依赖。
他们没有说的是你还需要 async-http-client 不是任何版本而是 2.0.39 一个漂亮的旧的。问题是它自动包含对 netty 4.1 的传递依赖,并且上面指定的错误开始显现。你实际需要的是nett 4.0.56
这是让这些东西对我有用的片段:
<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>
方法 1 有效 方法 2 我设法触发了 onConnect 事件,即 onMessage 超时,但我认为它应该可以工作,但我遗漏了一些小东西。不管怎样,我没有更多的时间可以花,我对 Aproach 1 很满意。