Refactor STOMP relay session
This change consolidates TCP-related logic in the StompRelaySession. As a result the sub-class SystemStompRelaySession now contains only logic intrinsic to the shared system session.
This commit is contained in:
parent
17aea892f0
commit
a172b32d4c
|
@ -247,9 +247,19 @@ public class StompBrokerRelayMessageHandler extends AbstractBrokerMessageHandler
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("Initializing \"system\" TCP connection");
|
logger.debug("Initializing \"system\" TCP connection");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StompHeaderAccessor headers = StompHeaderAccessor.create(StompCommand.CONNECT);
|
||||||
|
headers.setAcceptVersion("1.1,1.2");
|
||||||
|
headers.setLogin(this.systemLogin);
|
||||||
|
headers.setPasscode(this.systemPasscode);
|
||||||
|
headers.setHeartbeat(this.systemHeartbeatSendInterval, this.systemHeartbeatReceiveInterval);
|
||||||
|
headers.setHost(getVirtualHost());
|
||||||
|
Message<?> message = MessageBuilder.withPayload(new byte[0]).setHeaders(headers).build();
|
||||||
|
|
||||||
SystemStompRelaySession session = new SystemStompRelaySession();
|
SystemStompRelaySession session = new SystemStompRelaySession();
|
||||||
|
session.connect(message);
|
||||||
|
|
||||||
this.relaySessions.put(session.getId(), session);
|
this.relaySessions.put(session.getId(), session);
|
||||||
session.connect();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -302,8 +312,8 @@ public class StompBrokerRelayMessageHandler extends AbstractBrokerMessageHandler
|
||||||
message = MessageBuilder.withPayload(message.getPayload()).setHeaders(headers).build();
|
message = MessageBuilder.withPayload(message.getPayload()).setHeaders(headers).build();
|
||||||
}
|
}
|
||||||
StompRelaySession session = new StompRelaySession(sessionId);
|
StompRelaySession session = new StompRelaySession(sessionId);
|
||||||
this.relaySessions.put(sessionId, session);
|
|
||||||
session.connect(message);
|
session.connect(message);
|
||||||
|
this.relaySessions.put(sessionId, session);
|
||||||
}
|
}
|
||||||
else if (SimpMessageType.DISCONNECT.equals(messageType)) {
|
else if (SimpMessageType.DISCONNECT.equals(messageType)) {
|
||||||
StompRelaySession session = this.relaySessions.remove(sessionId);
|
StompRelaySession session = this.relaySessions.remove(sessionId);
|
||||||
|
@ -328,14 +338,30 @@ public class StompBrokerRelayMessageHandler extends AbstractBrokerMessageHandler
|
||||||
|
|
||||||
private class StompRelaySession {
|
private class StompRelaySession {
|
||||||
|
|
||||||
|
private static final long HEARTBEAT_MULTIPLIER = 3;
|
||||||
|
|
||||||
private final String sessionId;
|
private final String sessionId;
|
||||||
|
|
||||||
|
private final boolean isRemoteClientSession;
|
||||||
|
|
||||||
|
private final long reconnectInterval;
|
||||||
|
|
||||||
private volatile StompConnection stompConnection = new StompConnection();
|
private volatile StompConnection stompConnection = new StompConnection();
|
||||||
|
|
||||||
|
private volatile StompHeaderAccessor connectHeaders;
|
||||||
|
|
||||||
|
private volatile StompHeaderAccessor connectedHeaders;
|
||||||
|
|
||||||
|
|
||||||
private StompRelaySession(String sessionId) {
|
private StompRelaySession(String sessionId) {
|
||||||
|
this(sessionId, true, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private StompRelaySession(String sessionId, boolean isRemoteClientSession, long reconnectInterval) {
|
||||||
Assert.notNull(sessionId, "sessionId is required");
|
Assert.notNull(sessionId, "sessionId is required");
|
||||||
this.sessionId = sessionId;
|
this.sessionId = sessionId;
|
||||||
|
this.isRemoteClientSession = isRemoteClientSession;
|
||||||
|
this.reconnectInterval = reconnectInterval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -344,9 +370,23 @@ public class StompBrokerRelayMessageHandler extends AbstractBrokerMessageHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
public void connect(final Message<?> connectMessage) {
|
public void connect(final Message<?> connectMessage) {
|
||||||
Assert.notNull(connectMessage, "connectMessage is required");
|
|
||||||
|
|
||||||
Composable<TcpConnection<Message<byte[]>, Message<byte[]>>> promise = initConnection();
|
Assert.notNull(connectMessage, "connectMessage is required");
|
||||||
|
this.connectHeaders = StompHeaderAccessor.wrap(connectMessage);
|
||||||
|
|
||||||
|
Composable<TcpConnection<Message<byte[]>, Message<byte[]>>> promise;
|
||||||
|
if (this.reconnectInterval > 0) {
|
||||||
|
promise = tcpClient.open(new Reconnect() {
|
||||||
|
@Override
|
||||||
|
public Tuple2<InetSocketAddress, Long> reconnect(InetSocketAddress address, int attempt) {
|
||||||
|
return Tuple.of(address, 5000L);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
promise = tcpClient.open();
|
||||||
|
}
|
||||||
|
|
||||||
promise.consume(new Consumer<TcpConnection<Message<byte[]>, Message<byte[]>>>() {
|
promise.consume(new Consumer<TcpConnection<Message<byte[]>, Message<byte[]>>>() {
|
||||||
@Override
|
@Override
|
||||||
public void accept(TcpConnection<Message<byte[]>, Message<byte[]>> connection) {
|
public void accept(TcpConnection<Message<byte[]>, Message<byte[]>> connection) {
|
||||||
|
@ -366,10 +406,6 @@ public class StompBrokerRelayMessageHandler extends AbstractBrokerMessageHandler
|
||||||
this.stompConnection.setDisconnected();
|
this.stompConnection.setDisconnected();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Composable<TcpConnection<Message<byte[]>, Message<byte[]>>> initConnection() {
|
|
||||||
return tcpClient.open();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void handleConnectionReady(
|
protected void handleConnectionReady(
|
||||||
TcpConnection<Message<byte[]>, Message<byte[]>> tcpConn, final Message<?> connectMessage) {
|
TcpConnection<Message<byte[]>, Message<byte[]>> tcpConn, final Message<?> connectMessage) {
|
||||||
|
|
||||||
|
@ -403,7 +439,8 @@ public class StompBrokerRelayMessageHandler extends AbstractBrokerMessageHandler
|
||||||
|
|
||||||
StompHeaderAccessor headers = StompHeaderAccessor.wrap(message);
|
StompHeaderAccessor headers = StompHeaderAccessor.wrap(message);
|
||||||
if (StompCommand.CONNECTED == headers.getCommand()) {
|
if (StompCommand.CONNECTED == headers.getCommand()) {
|
||||||
connected(headers, this.stompConnection);
|
this.connectedHeaders = headers;
|
||||||
|
connected();
|
||||||
}
|
}
|
||||||
|
|
||||||
headers.setSessionId(this.sessionId);
|
headers.setSessionId(this.sessionId);
|
||||||
|
@ -411,7 +448,56 @@ public class StompBrokerRelayMessageHandler extends AbstractBrokerMessageHandler
|
||||||
sendMessageToClient(message);
|
sendMessageToClient(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void connected(StompHeaderAccessor headers, StompConnection stompConnection) {
|
private void initHeartbeats() {
|
||||||
|
|
||||||
|
long clientSendInterval = this.connectHeaders.getHeartbeat()[0];
|
||||||
|
long clientReceiveInterval = this.connectHeaders.getHeartbeat()[1];
|
||||||
|
|
||||||
|
long serverSendInterval = this.connectedHeaders.getHeartbeat()[0];
|
||||||
|
long serverReceiveInterval = this.connectedHeaders.getHeartbeat()[1];
|
||||||
|
|
||||||
|
if ((clientSendInterval > 0) && (serverReceiveInterval > 0)) {
|
||||||
|
long interval = Math.max(clientSendInterval, serverReceiveInterval);
|
||||||
|
stompConnection.connection.on().writeIdle(interval, new Runnable() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
TcpConnection<Message<byte[]>, Message<byte[]>> tcpConn = stompConnection.connection;
|
||||||
|
if (tcpConn != null) {
|
||||||
|
tcpConn.send(MessageBuilder.withPayload(new byte[] {'\n'}).build(),
|
||||||
|
new Consumer<Boolean>() {
|
||||||
|
@Override
|
||||||
|
public void accept(Boolean result) {
|
||||||
|
if (!result) {
|
||||||
|
handleTcpClientFailure("Failed to send heartbeat to the broker", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clientReceiveInterval > 0 && serverSendInterval > 0) {
|
||||||
|
final long interval = Math.max(clientReceiveInterval, serverSendInterval) * HEARTBEAT_MULTIPLIER;
|
||||||
|
stompConnection.connection.on().readIdle(interval, new Runnable() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
String message = "Broker hearbeat missed: connection idle for more than " + interval + "ms";
|
||||||
|
if (logger.isWarnEnabled()) {
|
||||||
|
logger.warn(message);
|
||||||
|
}
|
||||||
|
disconnected(message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void connected() {
|
||||||
|
if (!this.isRemoteClientSession) {
|
||||||
|
initHeartbeats();
|
||||||
|
}
|
||||||
this.stompConnection.setReady();
|
this.stompConnection.setReady();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -436,8 +522,19 @@ public class StompBrokerRelayMessageHandler extends AbstractBrokerMessageHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void sendMessageToClient(Message<?> message) {
|
protected void sendMessageToClient(Message<?> message) {
|
||||||
|
if (this.isRemoteClientSession) {
|
||||||
messageChannel.send(message);
|
messageChannel.send(message);
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
StompHeaderAccessor headers = StompHeaderAccessor.wrap(message);
|
||||||
|
if (StompCommand.ERROR.equals(headers.getCommand())) {
|
||||||
|
if (logger.isErrorEnabled()) {
|
||||||
|
logger.error("STOMP ERROR on sessionId=" + this.sessionId + ": " + message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ignore otherwise
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void forward(Message<?> message) {
|
private void forward(Message<?> message) {
|
||||||
TcpConnection<Message<byte[]>, Message<byte[]>> tcpConnection = this.stompConnection.getReadyConnection();
|
TcpConnection<Message<byte[]>, Message<byte[]>> tcpConnection = this.stompConnection.getReadyConnection();
|
||||||
|
@ -547,89 +644,16 @@ public class StompBrokerRelayMessageHandler extends AbstractBrokerMessageHandler
|
||||||
|
|
||||||
private class SystemStompRelaySession extends StompRelaySession {
|
private class SystemStompRelaySession extends StompRelaySession {
|
||||||
|
|
||||||
private static final long HEARTBEAT_RECEIVE_MULTIPLIER = 3;
|
|
||||||
|
|
||||||
public static final String ID = "stompRelaySystemSessionId";
|
public static final String ID = "stompRelaySystemSessionId";
|
||||||
|
|
||||||
private final byte[] heartbeatPayload = new byte[] {'\n'};
|
|
||||||
|
|
||||||
|
|
||||||
public SystemStompRelaySession() {
|
public SystemStompRelaySession() {
|
||||||
super(ID);
|
super(ID, false, 5000);
|
||||||
}
|
|
||||||
|
|
||||||
public void connect() {
|
|
||||||
StompHeaderAccessor headers = StompHeaderAccessor.create(StompCommand.CONNECT);
|
|
||||||
headers.setAcceptVersion("1.1,1.2");
|
|
||||||
headers.setLogin(systemLogin);
|
|
||||||
headers.setPasscode(systemPasscode);
|
|
||||||
headers.setHeartbeat(systemHeartbeatSendInterval, systemHeartbeatReceiveInterval);
|
|
||||||
if (getVirtualHost() != null) {
|
|
||||||
headers.setHost(getVirtualHost());
|
|
||||||
}
|
|
||||||
Message<?> connectMessage = MessageBuilder.withPayload(new byte[0]).setHeaders(headers).build();
|
|
||||||
super.connect(connectMessage);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Composable<TcpConnection<Message<byte[]>, Message<byte[]>>> initConnection() {
|
protected void connected() {
|
||||||
return tcpClient.open(new Reconnect() {
|
super.connected();
|
||||||
@Override
|
|
||||||
public Tuple2<InetSocketAddress, Long> reconnect(InetSocketAddress address, int attempt) {
|
|
||||||
return Tuple.of(address, 5000L);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void connectionClosed() {
|
|
||||||
publishBrokerUnavailableEvent();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void connected(StompHeaderAccessor headers, final StompConnection stompConnection) {
|
|
||||||
|
|
||||||
long brokerReceiveInterval = headers.getHeartbeat()[1];
|
|
||||||
if ((systemHeartbeatSendInterval > 0) && (brokerReceiveInterval > 0)) {
|
|
||||||
long interval = Math.max(systemHeartbeatSendInterval, brokerReceiveInterval);
|
|
||||||
stompConnection.connection.on().writeIdle(interval, new Runnable() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
TcpConnection<Message<byte[]>, Message<byte[]>> tcpConn = stompConnection.connection;
|
|
||||||
if (tcpConn != null) {
|
|
||||||
tcpConn.send(MessageBuilder.withPayload(heartbeatPayload).build(),
|
|
||||||
new Consumer<Boolean>() {
|
|
||||||
@Override
|
|
||||||
public void accept(Boolean result) {
|
|
||||||
if (!result) {
|
|
||||||
handleTcpClientFailure("Failed to send heartbeat to the broker", null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
long brokerSendInterval = headers.getHeartbeat()[0];
|
|
||||||
if (systemHeartbeatReceiveInterval > 0 && brokerSendInterval > 0) {
|
|
||||||
final long interval = Math.max(systemHeartbeatReceiveInterval, brokerSendInterval)
|
|
||||||
* HEARTBEAT_RECEIVE_MULTIPLIER;
|
|
||||||
stompConnection.connection.on().readIdle(interval, new Runnable() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
String message = "Broker hearbeat missed: connection idle for more than " + interval + "ms";
|
|
||||||
if (logger.isWarnEnabled()) {
|
|
||||||
logger.warn(message);
|
|
||||||
}
|
|
||||||
disconnected(message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
super.connected(headers, stompConnection);
|
|
||||||
publishBrokerAvailableEvent();
|
publishBrokerAvailableEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -640,16 +664,8 @@ public class StompBrokerRelayMessageHandler extends AbstractBrokerMessageHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void sendMessageToClient(Message<?> message) {
|
protected void connectionClosed() {
|
||||||
StompHeaderAccessor headers = StompHeaderAccessor.wrap(message);
|
publishBrokerUnavailableEvent();
|
||||||
if (StompCommand.ERROR.equals(headers.getCommand())) {
|
|
||||||
if (logger.isErrorEnabled()) {
|
|
||||||
logger.error("STOMP ERROR frame on system session: " + message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Ignore
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -46,6 +46,8 @@ import org.springframework.util.SocketUtils;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Integration tests for {@link StompBrokerRelayMessageHandler} running against ActiveMQ.
|
||||||
|
*
|
||||||
* @author Rossen Stoyanchev
|
* @author Rossen Stoyanchev
|
||||||
*/
|
*/
|
||||||
public class StompBrokerRelayMessageHandlerIntegrationTests {
|
public class StompBrokerRelayMessageHandlerIntegrationTests {
|
||||||
|
@ -91,14 +93,13 @@ public class StompBrokerRelayMessageHandlerIntegrationTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createAndStartRelay() throws InterruptedException {
|
private void createAndStartRelay() throws InterruptedException {
|
||||||
this.relay = new StompBrokerRelayMessageHandler(
|
this.relay = new StompBrokerRelayMessageHandler(this.responseChannel, Arrays.asList("/queue/", "/topic/"));
|
||||||
this.responseChannel, Arrays.asList("/queue/", "/topic/"));
|
|
||||||
this.relay.setRelayPort(port);
|
this.relay.setRelayPort(port);
|
||||||
this.relay.setApplicationEventPublisher(this.eventPublisher);
|
this.relay.setApplicationEventPublisher(this.eventPublisher);
|
||||||
this.relay.setSystemHeartbeatReceiveInterval(0);
|
this.relay.setSystemHeartbeatReceiveInterval(0);
|
||||||
this.relay.setSystemHeartbeatSendInterval(0);
|
this.relay.setSystemHeartbeatSendInterval(0);
|
||||||
|
|
||||||
this.eventPublisher.expect(true);
|
this.eventPublisher.expectAvailabilityStatusChanges(true);
|
||||||
this.relay.start();
|
this.relay.start();
|
||||||
this.eventPublisher.awaitAndAssert();
|
this.eventPublisher.awaitAndAssert();
|
||||||
}
|
}
|
||||||
|
@ -186,7 +187,7 @@ public class StompBrokerRelayMessageHandlerIntegrationTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void brokerAvailabilityEventWhenStopped() throws Exception {
|
public void brokerAvailabilityEventWhenStopped() throws Exception {
|
||||||
this.eventPublisher.expect(false);
|
this.eventPublisher.expectAvailabilityStatusChanges(false);
|
||||||
stopBrokerAndAwait();
|
stopBrokerAndAwait();
|
||||||
this.eventPublisher.awaitAndAssert();
|
this.eventPublisher.awaitAndAssert();
|
||||||
}
|
}
|
||||||
|
@ -215,10 +216,10 @@ public class StompBrokerRelayMessageHandlerIntegrationTests {
|
||||||
|
|
||||||
this.responseHandler.awaitAndAssert();
|
this.responseHandler.awaitAndAssert();
|
||||||
|
|
||||||
this.eventPublisher.expect(false);
|
this.eventPublisher.expectAvailabilityStatusChanges(false);
|
||||||
this.eventPublisher.awaitAndAssert();
|
this.eventPublisher.awaitAndAssert();
|
||||||
|
|
||||||
this.eventPublisher.expect(true);
|
this.eventPublisher.expectAvailabilityStatusChanges(true);
|
||||||
createAndStartBroker();
|
createAndStartBroker();
|
||||||
this.eventPublisher.awaitAndAssert();
|
this.eventPublisher.awaitAndAssert();
|
||||||
|
|
||||||
|
@ -587,7 +588,7 @@ public class StompBrokerRelayMessageHandlerIntegrationTests {
|
||||||
private final Object monitor = new Object();
|
private final Object monitor = new Object();
|
||||||
|
|
||||||
|
|
||||||
public void expect(Boolean... expected) {
|
public void expectAvailabilityStatusChanges(Boolean... expected) {
|
||||||
synchronized (this.monitor) {
|
synchronized (this.monitor) {
|
||||||
this.expected.addAll(Arrays.asList(expected));
|
this.expected.addAll(Arrays.asList(expected));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue