Skip to content

Commit 26819f5

Browse files
artembilangaryrussell
authored andcommitted
INT-4360: Add ClientStompEncoder support
The `StompSubProtocolHandler` explicitly sets `stompCommand` header to the `MESSAGE` value ignoring any client inputs. In this case the message is treated as from the server and ignored on the STOMP Broker side from the client session. * Introduce `ClientStompEncoder` for the client side to be injected into the `StompSubProtocolHandler` for the proper client side messages encoding/decoding. Override `stompCommand` header to the `SEND` value if it is `MESSAGE` before encoding to the `byte[]` to send to the session JIRA: https://jira.spring.io/browse/INT-4360 **Cherry-pick to 4.3.x** Fix WebSocket test to rely on the proper client config class and don't pick up the server config unconditionally in the test context Conflicts: spring-integration-websocket/src/test/java/org/springframework/integration/websocket/client/StompIntegrationTests.java Resolved.
1 parent c9ca87b commit 26819f5

File tree

5 files changed

+86
-25
lines changed

5 files changed

+86
-25
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.integration.websocket.support;
18+
19+
import java.util.Map;
20+
21+
import org.springframework.messaging.simp.stomp.StompCommand;
22+
import org.springframework.messaging.simp.stomp.StompEncoder;
23+
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
24+
25+
/**
26+
* A {@link StompEncoder} extension to prepare a message for sending/receiving
27+
* before/after encoding/decoding when used from WebSockets client side.
28+
* For example it updates the {@code stompCommand} header from the {@code MESSAGE}
29+
* to {@code SEND} frame, which is the case of
30+
* {@link org.springframework.web.socket.messaging.StompSubProtocolHandler}.
31+
*
32+
* @author Artem Bilan
33+
*
34+
* @since 4.3.13
35+
*/
36+
public class ClientStompEncoder extends StompEncoder {
37+
38+
@Override
39+
public byte[] encode(Map<String, Object> headers, byte[] payload) {
40+
if (StompCommand.MESSAGE.equals(headers.get("stompCommand"))) {
41+
StompHeaderAccessor stompHeaderAccessor = StompHeaderAccessor.create(StompCommand.SEND);
42+
stompHeaderAccessor.copyHeadersIfAbsent(headers);
43+
headers = stompHeaderAccessor.getMessageHeaders();
44+
}
45+
return super.encode(headers, payload);
46+
}
47+
48+
}

spring-integration-websocket/src/test/java/org/springframework/integration/websocket/client/StompIntegrationTests.java

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -55,19 +55,20 @@
5555
import org.springframework.integration.config.EnableIntegration;
5656
import org.springframework.integration.core.MessageProducer;
5757
import org.springframework.integration.event.inbound.ApplicationEventListeningMessageProducer;
58-
import org.springframework.integration.test.support.LogAdjustingTestSupport;
5958
import org.springframework.integration.transformer.ExpressionEvaluatingTransformer;
6059
import org.springframework.integration.websocket.ClientWebSocketContainer;
6160
import org.springframework.integration.websocket.IntegrationWebSocketContainer;
6261
import org.springframework.integration.websocket.TomcatWebSocketTestServer;
6362
import org.springframework.integration.websocket.event.ReceiptEvent;
6463
import org.springframework.integration.websocket.inbound.WebSocketInboundChannelAdapter;
6564
import org.springframework.integration.websocket.outbound.WebSocketOutboundMessageHandler;
65+
import org.springframework.integration.websocket.support.ClientStompEncoder;
6666
import org.springframework.integration.websocket.support.SubProtocolHandlerRegistry;
6767
import org.springframework.messaging.Message;
6868
import org.springframework.messaging.MessageChannel;
6969
import org.springframework.messaging.MessageHandler;
7070
import org.springframework.messaging.PollableChannel;
71+
import org.springframework.messaging.handler.annotation.Header;
7172
import org.springframework.messaging.handler.annotation.MessageExceptionHandler;
7273
import org.springframework.messaging.handler.annotation.MessageMapping;
7374
import org.springframework.messaging.simp.annotation.SendToUser;
@@ -82,7 +83,7 @@
8283
import org.springframework.stereotype.Controller;
8384
import org.springframework.test.annotation.DirtiesContext;
8485
import org.springframework.test.context.ContextConfiguration;
85-
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
86+
import org.springframework.test.context.junit4.SpringRunner;
8687
import org.springframework.util.MultiValueMap;
8788
import org.springframework.web.socket.client.WebSocketClient;
8889
import org.springframework.web.socket.client.standard.StandardWebSocketClient;
@@ -104,10 +105,10 @@
104105
* @author Artem Bilan
105106
* @since 4.1
106107
*/
107-
@ContextConfiguration
108-
@RunWith(SpringJUnit4ClassRunner.class)
109-
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
110-
public class StompIntegrationTests extends LogAdjustingTestSupport {
108+
@ContextConfiguration(classes = StompIntegrationTests.ClientConfig.class)
109+
@RunWith(SpringRunner.class)
110+
@DirtiesContext
111+
public class StompIntegrationTests {
111112

112113
@Value("#{server.serverContext}")
113114
private ApplicationContext serverContext;
@@ -127,11 +128,6 @@ public class StompIntegrationTests extends LogAdjustingTestSupport {
127128
@Qualifier("webSocketEvents")
128129
private QueueChannel webSocketEvents;
129130

130-
public StompIntegrationTests() {
131-
super("org.springframework", "org.springframework.integration", "org.apache.catalina");
132-
}
133-
134-
135131
@Test
136132
public void sendMessageToController() throws Exception {
137133
StompHeaderAccessor headers = StompHeaderAccessor.create(StompCommand.CONNECT);
@@ -154,6 +150,7 @@ public void sendMessageToController() throws Exception {
154150

155151
SimpleController controller = this.serverContext.getBean(SimpleController.class);
156152
assertTrue(controller.latch.await(20, TimeUnit.SECONDS));
153+
assertEquals(StompCommand.SEND.name(), controller.stompCommand);
157154
}
158155

159156
@Test
@@ -329,7 +326,7 @@ private boolean containsDestination(String destination, SubscriptionRegistry sub
329326

330327
@Configuration
331328
@EnableIntegration
332-
public static class ContextConfiguration {
329+
public static class ClientConfig {
333330

334331
@Bean
335332
public TomcatWebSocketTestServer server() {
@@ -348,7 +345,9 @@ public IntegrationWebSocketContainer clientWebSocketContainer() {
348345

349346
@Bean
350347
public SubProtocolHandler stompSubProtocolHandler() {
351-
return new StompSubProtocolHandler();
348+
StompSubProtocolHandler stompSubProtocolHandler = new StompSubProtocolHandler();
349+
stompSubProtocolHandler.setEncoder(new ClientStompEncoder());
350+
return stompSubProtocolHandler;
352351
}
353352

354353
@Bean
@@ -394,19 +393,23 @@ public ApplicationListener<ApplicationEvent> webSocketEventListener() {
394393

395394
// WebSocket Server part
396395

397-
@Target({ElementType.TYPE})
396+
@Target({ ElementType.TYPE })
398397
@Retention(RetentionPolicy.RUNTIME)
399398
@Controller
400399
private @interface IntegrationTestController {
400+
401401
}
402402

403403
@IntegrationTestController
404404
static class SimpleController {
405405

406406
private final CountDownLatch latch = new CountDownLatch(1);
407407

408-
@MessageMapping(value = "/simple")
409-
public void handle() {
408+
private String stompCommand;
409+
410+
@MessageMapping("/simple")
411+
public void handle(@Header("stompCommand") String stompCommand) {
412+
this.stompCommand = stompCommand;
410413
this.latch.countDown();
411414
}
412415

spring-integration-websocket/src/test/java/org/springframework/integration/websocket/client/WebSocketClientTests.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2014 the original author or authors.
2+
* Copyright 2014-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -54,7 +54,7 @@
5454
import org.springframework.stereotype.Component;
5555
import org.springframework.test.annotation.DirtiesContext;
5656
import org.springframework.test.context.ContextConfiguration;
57-
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
57+
import org.springframework.test.context.junit4.SpringRunner;
5858
import org.springframework.web.socket.client.WebSocketClient;
5959
import org.springframework.web.socket.client.standard.StandardWebSocketClient;
6060
import org.springframework.web.socket.messaging.StompSubProtocolHandler;
@@ -65,10 +65,11 @@
6565

6666
/**
6767
* @author Artem Bilan
68+
*
6869
* @since 4.1
6970
*/
70-
@ContextConfiguration
71-
@RunWith(SpringJUnit4ClassRunner.class)
71+
@ContextConfiguration(classes = WebSocketClientTests.ClientConfig.class)
72+
@RunWith(SpringRunner.class)
7273
@DirtiesContext
7374
public class WebSocketClientTests {
7475

@@ -96,7 +97,7 @@ public void testWebSocketOutboundMessageHandler() throws Exception {
9697

9798
@Configuration
9899
@EnableIntegration
99-
public static class ContextConfiguration {
100+
public static class ClientConfig {
100101

101102
@Bean
102103
public TomcatWebSocketTestServer server() {

spring-integration-websocket/src/test/java/org/springframework/integration/websocket/server/WebSocketServerTests.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@
7575
import org.springframework.messaging.support.MessageBuilder;
7676
import org.springframework.test.annotation.DirtiesContext;
7777
import org.springframework.test.context.ContextConfiguration;
78-
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
78+
import org.springframework.test.context.junit4.SpringRunner;
7979
import org.springframework.util.MultiValueMap;
8080
import org.springframework.web.socket.WebSocketHandler;
8181
import org.springframework.web.socket.WebSocketMessage;
@@ -101,8 +101,8 @@
101101
*
102102
* @since 4.1
103103
*/
104-
@ContextConfiguration(classes = WebSocketServerTests.ContextConfiguration.class)
105-
@RunWith(SpringJUnit4ClassRunner.class)
104+
@ContextConfiguration(classes = WebSocketServerTests.ClientConfig.class)
105+
@RunWith(SpringRunner.class)
106106
@DirtiesContext
107107
public class WebSocketServerTests {
108108

@@ -186,7 +186,7 @@ public void testBrokerIsNotPresented() throws Exception {
186186

187187
@Configuration
188188
@EnableIntegration
189-
public static class ContextConfiguration {
189+
public static class ClientConfig {
190190

191191
@Bean
192192
public TomcatWebSocketTestServer server() {

src/reference/asciidoc/web-sockets.adoc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,3 +433,12 @@ Defaults to `false`.
433433

434434

435435
<13> See the same option on the `<int-websocket:outbound-channel-adapter>`.
436+
437+
[[client-stomp-encoder]]
438+
=== ClientStompEncoder
439+
440+
Starting with _version 4.3.13_, the `ClientStompEncoder` is provided as an extension of standard `StompEncoder` for using on client side of the WebSocket Channel Adapters.
441+
An instance of the `ClientStompEncoder` must be injected into the `StompSubProtocolHandler` for proper client side message preparation.
442+
One of the problem of the default `StompSubProtocolHandler` that it was designed for the server side, so it updates the `SEND` `stompCommand` header into `MESSAGE` as it must be by the STOMP protocol from server side.
443+
If client doesn't send its messages in the proper `SEND` web socket frame, some STOMP brokers won't accept them.
444+
The purpose of the `ClientStompEncoder`, in this case, is to override `stompCommand` header to the `SEND` value before encoding the message to the `byte[]`.

0 commit comments

Comments
 (0)