Option to preserve publish order

Issue: SPR-13989
This commit is contained in:
Rossen Stoyanchev
2018-07-23 23:22:16 -04:00
parent 430250c80f
commit 7500b144ae
17 changed files with 496 additions and 95 deletions

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -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);

View File

@@ -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"));

View File

@@ -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>

View File

@@ -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">