-1

在尝试基于 WebSockets 的 STOMP 时,我注意到不同实现之间的不一致,即 Spring Boot Java 实现和使用 STOMP.js 编写的 NodeJs 客户端之间的不一致

当调试到它时,不同的是在 Spring Boot 应用程序中,CONNECT消息应该是一个 JSON 数组。例如,此消息由他们的测试客户端发送(使用SocksJS库以 JavaScript 编写):

["CONNECT\naccept-version:1.1,1.0\nheart-beat:10000,10000\n\n\u0000"]

相比之下,我的 NodeJs STOMP.js 测试客户端(代码如下)发送以下帧:

CONNECT
accept-version:1.0,1.1,1.2
heart-beat:4000,4000

^@

不幸的是,我对 STOMP 没有经验,但是在阅读了规范之后,我不明白为什么 Spring Boot 期望将数据表示为 JSON 数组。这是一个已知问题吗?


为了演示,让我分享两个示例运行。一次成功运行以连接到 RabbitMQ,然后尝试连接 Java Spring Boot 应用程序失败。(可以在最后找到带有代码的可重复设置。)

  1. 连接到 RabbitMQ 实例,该实例配置为使用 STOMP over WebSockets(在 上运行ws://localhost:15674/ws):
$ node client.js 
Opening Web Socket...
Web Socket Opened...
>>> CONNECT
accept-version:1.0,1.1,1.2
heart-beat:4000,4000


Received data
<<< CONNECTED
server:RabbitMQ/3.8.8
session:session-WkKD6rN5BNc_ObKpziikYA
heart-beat:4000,4000
version:1.2



connected to server RabbitMQ/3.8.8
send PING every 4000ms
check PONG every 4000ms
onConnect called
<<< PONG
Received data
<<< 

<<< PONG
>>> PING
Received data
<<< 
  1. 现在连接(不成功)到 Spring Boot 应用程序(ws://localhost:5555/chat/123/k2qn3dl7/websocket):
node client.js 
Opening Web Socket...
Web Socket Opened...
>>> CONNECT
accept-version:1.0,1.1,1.2
heart-beat:4000,4000


Received data
<<< o
Received data
<<< c[1007,""]
Connection closed to ws://localhost:5555/chat/123/k2qn3dl7/websocket
STOMP: scheduling reconnection in 5000ms
Opening Web Socket...
Web Socket Opened...
>>> CONNECT
accept-version:1.0,1.1,1.2
heart-beat:4000,4000


Received data
<<< o
^C

它失败的原因是 Jackson(JSON 解析器)未能解析该有效负载:

CONNECT
accept-version:1.0,1.1,1.2
heart-beat:4000,4000

^@

如前所述,在 Spring Boot 示例附带的客户端中,有效负载如下所示:

["CONNECT\naccept-version:1.1,1.0\nheart-beat:10000,10000\n\n\u0000"]

这是 Spring Boot 应用程序中的完整错误:

2021-07-22 13:58:59.546  INFO 74313 --- [nio-5555-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 1 ms
2021-07-22 13:58:59.594 ERROR 74313 --- [nio-5555-exec-1] s.w.s.s.t.s.WebSocketServerSockJsSession : Broken data received. Terminating WebSocket connection abruptly

com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'CONNECT': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')
 at [Source: (String)"CONNECT
accept-version:1.0,1.1,1.2
heart-beat:4000,4000

"; line: 1, column: 8]
    at com.fasterxml.jackson.core.JsonParser._constructError(JsonParser.java:2337) ~[jackson-core-2.12.3.jar:2.12.3]
    at com.fasterxml.jackson.core.base.ParserMinimalBase._reportError(ParserMinimalBase.java:720) ~[jackson-core-2.12.3.jar:2.12.3]
    at com.fasterxml.jackson.core.json.ReaderBasedJsonParser._reportInvalidToken(ReaderBasedJsonParser.java:2903) ~[jackson-core-2.12.3.jar:2.12.3]
    at com.fasterxml.jackson.core.json.ReaderBasedJsonParser._handleOddValue(ReaderBasedJsonParser.java:1949) ~[jackson-core-2.12.3.jar:2.12.3]
    at com.fasterxml.jackson.core.json.ReaderBasedJsonParser.nextToken(ReaderBasedJsonParser.java:781) ~[jackson-core-2.12.3.jar:2.12.3]
    at com.fasterxml.jackson.databind.ObjectMapper._initForReading(ObjectMapper.java:4684) ~[jackson-databind-2.12.3.jar:2.12.3]
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4586) ~[jackson-databind-2.12.3.jar:2.12.3]
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3548) ~[jackson-databind-2.12.3.jar:2.12.3]
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3516) ~[jackson-databind-2.12.3.jar:2.12.3]
    at org.springframework.web.socket.sockjs.frame.Jackson2SockJsMessageCodec.decode(Jackson2SockJsMessageCodec.java:64) ~[spring-websocket-5.3.8.jar:5.3.8]
    at org.springframework.web.socket.sockjs.transport.session.WebSocketServerSockJsSession.handleMessage(WebSocketServerSockJsSession.java:187) ~[spring-websocket-5.3.8.jar:5.3.8]
    at org.springframework.web.socket.sockjs.transport.handler.SockJsWebSocketHandler.handleTextMessage(SockJsWebSocketHandler.java:93) ~[spring-websocket-5.3.8.jar:5.3.8]
    at org.springframework.web.socket.handler.AbstractWebSocketHandler.handleMessage(AbstractWebSocketHandler.java:43) ~[spring-websocket-5.3.8.jar:5.3.8]
    at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter.handleTextMessage(StandardWebSocketHandlerAdapter.java:114) ~[spring-websocket-5.3.8.jar:5.3.8]
    at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter.access$000(StandardWebSocketHandlerAdapter.java:43) ~[spring-websocket-5.3.8.jar:5.3.8]
    at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter$3.onMessage(StandardWebSocketHandlerAdapter.java:85) ~[spring-websocket-5.3.8.jar:5.3.8]
    at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter$3.onMessage(StandardWebSocketHandlerAdapter.java:82) ~[spring-websocket-5.3.8.jar:5.3.8]
    at org.apache.tomcat.websocket.WsFrameBase.sendMessageText(WsFrameBase.java:415) ~[tomcat-embed-websocket-9.0.46.jar:9.0.46]
    at org.apache.tomcat.websocket.server.WsFrameServer.sendMessageText(WsFrameServer.java:129) ~[tomcat-embed-websocket-9.0.46.jar:9.0.46]
    at org.apache.tomcat.websocket.WsFrameBase.processDataText(WsFrameBase.java:515) ~[tomcat-embed-websocket-9.0.46.jar:9.0.46]
    at org.apache.tomcat.websocket.WsFrameBase.processData(WsFrameBase.java:301) ~[tomcat-embed-websocket-9.0.46.jar:9.0.46]
    at org.apache.tomcat.websocket.WsFrameBase.processInputBuffer(WsFrameBase.java:133) ~[tomcat-embed-websocket-9.0.46.jar:9.0.46]
    at org.apache.tomcat.websocket.server.WsFrameServer.onDataAvailable(WsFrameServer.java:85) ~[tomcat-embed-websocket-9.0.46.jar:9.0.46]
    at org.apache.tomcat.websocket.server.WsFrameServer.doOnDataAvailable(WsFrameServer.java:183) ~[tomcat-embed-websocket-9.0.46.jar:9.0.46]
    at org.apache.tomcat.websocket.server.WsFrameServer.notifyDataAvailable(WsFrameServer.java:162) ~[tomcat-embed-websocket-9.0.46.jar:9.0.46]
    at org.apache.tomcat.websocket.server.WsHttpUpgradeHandler.upgradeDispatch(WsHttpUpgradeHandler.java:156) ~[tomcat-embed-websocket-9.0.46.jar:9.0.46]
    at org.apache.coyote.http11.upgrade.UpgradeProcessorInternal.dispatch(UpgradeProcessorInternal.java:60) ~[tomcat-embed-core-9.0.46.jar:9.0.46]
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:59) ~[tomcat-embed-core-9.0.46.jar:9.0.46]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893) ~[tomcat-embed-core-9.0.46.jar:9.0.46]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1707) ~[tomcat-embed-core-9.0.46.jar:9.0.46]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.46.jar:9.0.46]
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) ~[na:na]
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) ~[na:na]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.46.jar:9.0.46]
    at java.base/java.lang.Thread.run(Thread.java:829) ~[na:na]

2021-07-22 13:59:04.610 ERROR 74313 --- [nio-5555-exec-2] s.w.s.s.t.s.WebSocketServerSockJsSession : Broken data received. Terminating WebSocket connection abruptly

重现路径:

  1. NodeJs 客户端代码
  2. Spring Boot 测试应用
  3. RabbitMQ 测试实例

  1. 用 NodeJs 编写的客户端代码:
// Required dependencies:
// "@stomp/stompjs": "6.1.0"
// "websocket": "1.0.34"

// Polyfills. For details see:
// https://stomp-js.github.io/guide/stompjs/rx-stomp/ng2-stompjs/pollyfils-for-stompjs-v5.html
Object.assign(global, { WebSocket: require('websocket').w3cwebsocket });

const StompJs = require('@stomp/stompjs');

const client = new StompJs.Client({
        //brokerURL: 'ws://localhost:15674/ws', // RabbitMQ (should work)
        brokerURL: 'ws://localhost:5555/chat/123/k2qn3dl7/websocket', // Spring app (should fail)
        reconnectDelay: 5000,
        heartbeatIncoming: 4000,
        heartbeatOutgoing: 4000,
        logRawCommunication: true,
        debug: (x) => console.log(x),
});

client.onConnect = function (frame) {
  console.log('onConnect called');
};

client.activate();

  1. 可以在此处找到 Spring Boot 应用程序。我在端口 5555 上启动它:
git clone git@github.com:eugenp/tutorials.git
cd tutorials/spring-websockets
SERVER_PORT=5555 mvn spring-boot:run

注意:如果您随后转到http://localhost:5555,您将看到由 Spring Boot 应用程序提供的聊天应用程序。单击连接时,将建立一个 STOMP 连接。

火狐网络监视器


  1. 要启动 RabbitMQ,您可以使用STOMP.js 中用于测试的Docker 容器:
git clone git@github.com:stomp-js/stompjs.git
cd stompjs
sudo docker build -t myrabbitmq rabbitmq/
sudo docker run --rm -p 15674:15674 myrabbitmq
4

1 回答 1

0

简而言之:JSON 消息不是“STOMP over native WebSockets”,而是“STOMP over SocksJS ”。SocksJS 协议引入了额外的 JSON 层,该协议用于 Spring Boot 示例应用程序。


这是更长的故事。事实证明,我的终点是错误的。代替

'ws://localhost:5555/chat/123/k2qn3dl7/websocket'

应该是

'ws://localhost:5555/chat'

它有错误的 URI,因为我正在复制我在浏览器中看到的输出。相反,我应该查看配置

@Override
public void registerStompEndpoints(final StompEndpointRegistry registry) {
  registry.addEndpoint("/chat");
  registry.addEndpoint("/chat").withSockJS();
  registry.addEndpoint("/chatwithbots");
  registry.addEndpoint("/chatwithbots").withSockJS();
}

现在是令人困惑的部分。从配置中可以看出,Spring Boot 应用程序使用 SocksJS 定义了 fallbacks。

如果您删除回退,令人困惑的错误消息就会消失。然而,当回退处于活动状态时,Spring 将尝试将请求作为 SocksJS 处理。这就是它尝试将 STOMP 帧解析为 JSON 的原因,这会导致误导性错误消息。

此外,我对 Spring Boot 示例中使用的 JavaScript 客户端感到困惑:

function connect() {
  var socket = new SockJS('/chat');
  stompClient = Stomp.over(socket);

  stompClient.connect({}, function(frame) {
    setConnected(true);
    console.log('Connected: ' + frame);
    stompClient.subscribe('/topic/messages', function(messageOutput) {
      showMessageOutput(JSON.parse(messageOutput.body));
    });
  });
}

它不是通过本机 WebSocket 而是通过 SocksJS 连接的。这就解释了为什么 Firefox 显示 JSON 请求,而不是预期的 STOMP 帧。

于 2021-07-23T18:25:20.680 回答