Option to preserve publish order
Issue: SPR-13989
This commit is contained in:
@@ -163,7 +163,7 @@ class MessageBrokerBeanDefinitionParser implements BeanDefinitionParser {
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, Object> scopeMap = Collections.<String, Object>singletonMap("websocket", new SimpSessionScope());
|
||||
Map<String, Object> scopeMap = Collections.singletonMap("websocket", new SimpSessionScope());
|
||||
RootBeanDefinition scopeConfigurer = new RootBeanDefinition(CustomScopeConfigurer.class);
|
||||
scopeConfigurer.getPropertyValues().add("scopes", scopeMap);
|
||||
registerBeanDefByName("webSocketScopeConfigurer", scopeConfigurer, context, source);
|
||||
@@ -444,6 +444,12 @@ class MessageBrokerBeanDefinitionParser implements BeanDefinitionParser {
|
||||
// Should not happen
|
||||
throw new IllegalStateException("Neither <simple-broker> nor <stomp-broker-relay> elements found.");
|
||||
}
|
||||
|
||||
if (brokerElement.hasAttribute("preserve-publish-order")) {
|
||||
String preservePublishOrder = brokerElement.getAttribute("preserve-publish-order");
|
||||
brokerDef.getPropertyValues().add("preservePublishOrder", preservePublishOrder);
|
||||
}
|
||||
|
||||
registerBeanDef(brokerDef, context, source);
|
||||
return brokerDef;
|
||||
}
|
||||
|
||||
@@ -895,6 +895,22 @@
|
||||
|
||||
Prefixes without a trailing slash will have one appended automatically.
|
||||
By default the list of prefixes is empty in which case all destinations match.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="preserve-publish-order" type="xsd:boolean">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Whether the client must receive messages in the order of publication.
|
||||
|
||||
By default messages sent to the clientOutboundChannel may
|
||||
not be processed in the same order because the channel is backed by a
|
||||
ThreadPoolExecutor that in turn does not guarantee processing in order.
|
||||
|
||||
When this flag is set to true messages within the same session
|
||||
will be sent to the clientOutboundChannel one at a time in
|
||||
order to preserve the order of publication. Enable this only if needed
|
||||
since there is some performance overhead to keep messages in order.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
|
||||
@@ -208,18 +208,19 @@ public class MessageBrokerBeanDefinitionParserTests {
|
||||
assertEquals("my-selector", registry.getSelectorHeaderName());
|
||||
assertNotNull(brokerMessageHandler.getTaskScheduler());
|
||||
assertArrayEquals(new long[] {15000, 15000}, brokerMessageHandler.getHeartbeatValue());
|
||||
assertTrue(brokerMessageHandler.isPreservePublishOrder());
|
||||
|
||||
List<Class<? extends MessageHandler>> subscriberTypes =
|
||||
Arrays.<Class<? extends MessageHandler>>asList(SimpAnnotationMethodMessageHandler.class,
|
||||
Arrays.asList(SimpAnnotationMethodMessageHandler.class,
|
||||
UserDestinationMessageHandler.class, SimpleBrokerMessageHandler.class);
|
||||
testChannel("clientInboundChannel", subscriberTypes, 2);
|
||||
testExecutor("clientInboundChannel", Runtime.getRuntime().availableProcessors() * 2, Integer.MAX_VALUE, 60);
|
||||
|
||||
subscriberTypes = Collections.singletonList(SubProtocolWebSocketHandler.class);
|
||||
testChannel("clientOutboundChannel", subscriberTypes, 1);
|
||||
testChannel("clientOutboundChannel", subscriberTypes, 2);
|
||||
testExecutor("clientOutboundChannel", Runtime.getRuntime().availableProcessors() * 2, Integer.MAX_VALUE, 60);
|
||||
|
||||
subscriberTypes = Arrays.<Class<? extends MessageHandler>>asList(
|
||||
subscriberTypes = Arrays.asList(
|
||||
SimpleBrokerMessageHandler.class, UserDestinationMessageHandler.class);
|
||||
testChannel("brokerChannel", subscriberTypes, 1);
|
||||
try {
|
||||
@@ -278,6 +279,7 @@ public class MessageBrokerBeanDefinitionParserTests {
|
||||
assertEquals(5000, messageBroker.getSystemHeartbeatReceiveInterval());
|
||||
assertEquals(5000, messageBroker.getSystemHeartbeatSendInterval());
|
||||
assertThat(messageBroker.getDestinationPrefixes(), Matchers.containsInAnyOrder("/topic","/queue"));
|
||||
assertTrue(messageBroker.isPreservePublishOrder());
|
||||
|
||||
List<Class<? extends MessageHandler>> subscriberTypes = Arrays.asList(
|
||||
SimpAnnotationMethodMessageHandler.class, UserDestinationMessageHandler.class,
|
||||
@@ -286,7 +288,7 @@ public class MessageBrokerBeanDefinitionParserTests {
|
||||
testExecutor("clientInboundChannel", Runtime.getRuntime().availableProcessors() * 2, Integer.MAX_VALUE, 60);
|
||||
|
||||
subscriberTypes = Collections.singletonList(SubProtocolWebSocketHandler.class);
|
||||
testChannel("clientOutboundChannel", subscriberTypes, 1);
|
||||
testChannel("clientOutboundChannel", subscriberTypes, 2);
|
||||
testExecutor("clientOutboundChannel", Runtime.getRuntime().availableProcessors() * 2, Integer.MAX_VALUE, 60);
|
||||
|
||||
subscriberTypes = Arrays.asList(StompBrokerRelayMessageHandler.class, UserDestinationMessageHandler.class);
|
||||
|
||||
@@ -102,12 +102,13 @@ public class StompWebSocketIntegrationTests extends AbstractWebSocketIntegration
|
||||
|
||||
@Test
|
||||
public void sendMessageToControllerAndReceiveReplyViaTopic() throws Exception {
|
||||
TextMessage message1 = create(StompCommand.SUBSCRIBE)
|
||||
TextMessage m0 = create(StompCommand.CONNECT).headers("accept-version:1.1").build();
|
||||
TextMessage m1 = create(StompCommand.SUBSCRIBE)
|
||||
.headers("id:subs1", "destination:/topic/increment").build();
|
||||
TextMessage message2 = create(StompCommand.SEND)
|
||||
TextMessage m2 = create(StompCommand.SEND)
|
||||
.headers("destination:/app/increment").body("5").build();
|
||||
|
||||
TestClientWebSocketHandler clientHandler = new TestClientWebSocketHandler(1, message1, message2);
|
||||
TestClientWebSocketHandler clientHandler = new TestClientWebSocketHandler(2, m0, m1, m2);
|
||||
WebSocketSession session = doHandshake(clientHandler, "/ws").get();
|
||||
|
||||
try {
|
||||
@@ -120,16 +121,17 @@ public class StompWebSocketIntegrationTests extends AbstractWebSocketIntegration
|
||||
|
||||
@Test // SPR-10930
|
||||
public void sendMessageToBrokerAndReceiveReplyViaTopic() throws Exception {
|
||||
TextMessage m0 = create(StompCommand.CONNECT).headers("accept-version:1.1").build();
|
||||
TextMessage m1 = create(StompCommand.SUBSCRIBE).headers("id:subs1", "destination:/topic/foo").build();
|
||||
TextMessage m2 = create(StompCommand.SEND).headers("destination:/topic/foo").body("5").build();
|
||||
|
||||
TestClientWebSocketHandler clientHandler = new TestClientWebSocketHandler(1, m1, m2);
|
||||
TestClientWebSocketHandler clientHandler = new TestClientWebSocketHandler(2, m0, m1, m2);
|
||||
WebSocketSession session = doHandshake(clientHandler, "/ws").get();
|
||||
|
||||
try {
|
||||
assertTrue(clientHandler.latch.await(TIMEOUT, TimeUnit.SECONDS));
|
||||
|
||||
String payload = clientHandler.actual.get(0).getPayload();
|
||||
String payload = clientHandler.actual.get(1).getPayload();
|
||||
assertTrue("Expected STOMP MESSAGE, got " + payload, payload.startsWith("MESSAGE\n"));
|
||||
}
|
||||
finally {
|
||||
@@ -139,15 +141,16 @@ public class StompWebSocketIntegrationTests extends AbstractWebSocketIntegration
|
||||
|
||||
@Test // SPR-11648
|
||||
public void sendSubscribeToControllerAndReceiveReply() throws Exception {
|
||||
TextMessage m0 = create(StompCommand.CONNECT).headers("accept-version:1.1").build();
|
||||
String destHeader = "destination:/app/number";
|
||||
TextMessage message = create(StompCommand.SUBSCRIBE).headers("id:subs1", destHeader).build();
|
||||
TextMessage m1 = create(StompCommand.SUBSCRIBE).headers("id:subs1", destHeader).build();
|
||||
|
||||
TestClientWebSocketHandler clientHandler = new TestClientWebSocketHandler(1, message);
|
||||
TestClientWebSocketHandler clientHandler = new TestClientWebSocketHandler(2, m0, m1);
|
||||
WebSocketSession session = doHandshake(clientHandler, "/ws").get();
|
||||
|
||||
try {
|
||||
assertTrue(clientHandler.latch.await(TIMEOUT, TimeUnit.SECONDS));
|
||||
String payload = clientHandler.actual.get(0).getPayload();
|
||||
String payload = clientHandler.actual.get(1).getPayload();
|
||||
assertTrue("Expected STOMP destination=/app/number, got " + payload, payload.contains(destHeader));
|
||||
assertTrue("Expected STOMP Payload=42, got " + payload, payload.contains("42"));
|
||||
}
|
||||
@@ -159,15 +162,16 @@ public class StompWebSocketIntegrationTests extends AbstractWebSocketIntegration
|
||||
@Test
|
||||
public void handleExceptionAndSendToUser() throws Exception {
|
||||
String destHeader = "destination:/user/queue/error";
|
||||
TextMessage m0 = create(StompCommand.CONNECT).headers("accept-version:1.1").build();
|
||||
TextMessage m1 = create(StompCommand.SUBSCRIBE).headers("id:subs1", destHeader).build();
|
||||
TextMessage m2 = create(StompCommand.SEND).headers("destination:/app/exception").build();
|
||||
|
||||
TestClientWebSocketHandler clientHandler = new TestClientWebSocketHandler(1, m1, m2);
|
||||
TestClientWebSocketHandler clientHandler = new TestClientWebSocketHandler(2, m0, m1, m2);
|
||||
WebSocketSession session = doHandshake(clientHandler, "/ws").get();
|
||||
|
||||
try {
|
||||
assertTrue(clientHandler.latch.await(TIMEOUT, TimeUnit.SECONDS));
|
||||
String payload = clientHandler.actual.get(0).getPayload();
|
||||
String payload = clientHandler.actual.get(1).getPayload();
|
||||
assertTrue(payload.startsWith("MESSAGE\n"));
|
||||
assertTrue(payload.contains("destination:/user/queue/error\n"));
|
||||
assertTrue(payload.endsWith("Got error: Bad input\0"));
|
||||
@@ -179,17 +183,18 @@ public class StompWebSocketIntegrationTests extends AbstractWebSocketIntegration
|
||||
|
||||
@Test
|
||||
public void webSocketScope() throws Exception {
|
||||
TextMessage message1 = create(StompCommand.SUBSCRIBE)
|
||||
TextMessage m0 = create(StompCommand.CONNECT).headers("accept-version:1.1").build();
|
||||
TextMessage m1 = create(StompCommand.SUBSCRIBE)
|
||||
.headers("id:subs1", "destination:/topic/scopedBeanValue").build();
|
||||
TextMessage message2 = create(StompCommand.SEND)
|
||||
TextMessage m2 = create(StompCommand.SEND)
|
||||
.headers("destination:/app/scopedBeanValue").build();
|
||||
|
||||
TestClientWebSocketHandler clientHandler = new TestClientWebSocketHandler(1, message1, message2);
|
||||
TestClientWebSocketHandler clientHandler = new TestClientWebSocketHandler(2, m0, m1, m2);
|
||||
WebSocketSession session = doHandshake(clientHandler, "/ws").get();
|
||||
|
||||
try {
|
||||
assertTrue(clientHandler.latch.await(TIMEOUT, TimeUnit.SECONDS));
|
||||
String payload = clientHandler.actual.get(0).getPayload();
|
||||
String payload = clientHandler.actual.get(1).getPayload();
|
||||
assertTrue(payload.startsWith("MESSAGE\n"));
|
||||
assertTrue(payload.contains("destination:/topic/scopedBeanValue\n"));
|
||||
assertTrue(payload.endsWith("55\0"));
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket.xsd">
|
||||
|
||||
<websocket:message-broker order="2">
|
||||
<websocket:message-broker order="2" preserve-publish-order="true">
|
||||
<websocket:stomp-endpoint path="/foo">
|
||||
<websocket:sockjs/>
|
||||
</websocket:stomp-endpoint>
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
<websocket:message-broker application-destination-prefix="/app"
|
||||
user-destination-prefix="/personal"
|
||||
path-matcher="pathMatcher"
|
||||
path-helper="urlPathHelper">
|
||||
path-helper="urlPathHelper"
|
||||
preserve-publish-order="true">
|
||||
|
||||
<!-- message-size=128*1024, send-buffer-size=1024*1024 -->
|
||||
<websocket:transport message-size="131072" send-timeout="25000" send-buffer-size="1048576" time-to-first-message="30000">
|
||||
|
||||
Reference in New Issue
Block a user