Added memory store for will messages and implemented the correct publishing
This commit is contained in:
@@ -3,6 +3,8 @@ Version 0.6:
|
||||
Fix issue #43, missed topic case (finance/+ does has to match finance)
|
||||
Integrated WebSocket support (issue #37)
|
||||
Integrated SSL support (issue #41)
|
||||
Fixed issue #40, check Logging inside the Felix OSGi
|
||||
Fixed issue #46, implement correct behaviour for Will stuff
|
||||
|
||||
Version 0.5:
|
||||
Fix issue 20 (no flush of message on PINGREQ)
|
||||
|
||||
@@ -21,6 +21,9 @@ import org.dna.mqtt.moquette.server.ServerChannel;
|
||||
/**
|
||||
* Interface to the underling messaging system used to publish, subscribe.
|
||||
*
|
||||
* It's the abstraction of the messaging stuff attached in after the front protocol
|
||||
* parsing stuff.
|
||||
*
|
||||
* @author andrea
|
||||
*/
|
||||
public interface IMessaging {
|
||||
|
||||
+48
-4
@@ -36,6 +36,7 @@ import org.dna.mqtt.moquette.messaging.spi.impl.subscriptions.Subscription;
|
||||
import org.dna.mqtt.moquette.messaging.spi.impl.subscriptions.SubscriptionsStore;
|
||||
import org.dna.mqtt.moquette.proto.messages.PubCompMessage;
|
||||
import org.dna.mqtt.moquette.proto.messages.AbstractMessage;
|
||||
import org.dna.mqtt.moquette.proto.messages.AbstractMessage.QOSType;
|
||||
import org.dna.mqtt.moquette.proto.messages.ConnAckMessage;
|
||||
import org.dna.mqtt.moquette.proto.messages.ConnectMessage;
|
||||
import org.dna.mqtt.moquette.proto.messages.PubAckMessage;
|
||||
@@ -62,12 +63,45 @@ import org.slf4j.LoggerFactory;
|
||||
*/
|
||||
class ProtocolProcessor implements EventHandler<ValueEvent> {
|
||||
|
||||
static final class WillMessage {
|
||||
private final String topic;
|
||||
private final ByteBuffer payload;
|
||||
private final boolean retained;
|
||||
private final QOSType qos;
|
||||
|
||||
public WillMessage(String topic, ByteBuffer payload, boolean retained, QOSType qos) {
|
||||
this.topic = topic;
|
||||
this.payload = payload;
|
||||
this.retained = retained;
|
||||
this.qos = qos;
|
||||
}
|
||||
|
||||
public String getTopic() {
|
||||
return topic;
|
||||
}
|
||||
|
||||
public ByteBuffer getPayload() {
|
||||
return payload;
|
||||
}
|
||||
|
||||
public boolean isRetained() {
|
||||
return retained;
|
||||
}
|
||||
|
||||
public QOSType getQos() {
|
||||
return qos;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ProtocolProcessor.class);
|
||||
|
||||
private Map<String, ConnectionDescriptor> m_clientIDs = new HashMap<String, ConnectionDescriptor>();
|
||||
private SubscriptionsStore subscriptions;
|
||||
private IStorageService m_storageService;
|
||||
private IAuthenticator m_authenticator;
|
||||
//maps clientID to Will testament, if specified on CONNECT
|
||||
private Map<String, WillMessage> m_willStore = new HashMap<String, WillMessage>();
|
||||
|
||||
private ExecutorService m_executor;
|
||||
BatchEventProcessor<ValueEvent> m_eventProcessor;
|
||||
@@ -149,9 +183,9 @@ class ProtocolProcessor implements EventHandler<ValueEvent> {
|
||||
AbstractMessage.QOSType willQos = AbstractMessage.QOSType.values()[msg.getWillQos()];
|
||||
byte[] willPayload = msg.getWillMessage().getBytes();
|
||||
ByteBuffer bb = (ByteBuffer) ByteBuffer.allocate(willPayload.length).put(willPayload).flip();
|
||||
PublishEvent pubEvt = new PublishEvent(msg.getWillTopic(), willQos,
|
||||
bb, msg.isWillRetain(), msg.getClientID(), session);
|
||||
processPublish(pubEvt);
|
||||
//save the will testment in the clientID store
|
||||
WillMessage will = new WillMessage(msg.getWillTopic(), bb, msg.isWillRetain(),willQos );
|
||||
m_willStore.put(msg.getClientID(), will);
|
||||
}
|
||||
|
||||
//handle user authentication
|
||||
@@ -429,8 +463,10 @@ class ProtocolProcessor implements EventHandler<ValueEvent> {
|
||||
session.close(true);
|
||||
|
||||
//de-activate the subscriptions for this ClientID
|
||||
// String clientID = (String) evt.getSession().getAttribute(Constants.ATTR_CLIENTID);
|
||||
subscriptions.deactivate(clientID);
|
||||
//cleanup the will store
|
||||
m_willStore.remove(clientID);
|
||||
|
||||
LOG.info("Disconnected client <{}> with clean session {}", clientID, cleanSession);
|
||||
}
|
||||
|
||||
@@ -442,6 +478,14 @@ class ProtocolProcessor implements EventHandler<ValueEvent> {
|
||||
subscriptions.deactivate(clientID);
|
||||
LOG.info("Lost connection with client <{}>", clientID);
|
||||
}
|
||||
//TODO publish the Will message (if any) for the clientID
|
||||
if (m_willStore.containsKey(clientID)) {
|
||||
WillMessage will = m_willStore.get(clientID);
|
||||
PublishEvent pubEvt = new PublishEvent(will.getTopic(), will.getQos(),
|
||||
will.getPayload(), will.isRetained(), clientID, null);
|
||||
processPublish(pubEvt);
|
||||
m_willStore.remove(clientID);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -89,6 +89,7 @@ public class PublishEvent extends MessagingEvent {
|
||||
return m_msgID;
|
||||
}
|
||||
|
||||
//TODO remove the session it's never used!!
|
||||
public ServerChannel getSession() {
|
||||
return m_session;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ class MoquetteIdleTimoutHandler extends ChannelDuplexHandler {
|
||||
if (evt instanceof IdleStateEvent) {
|
||||
IdleState e = ((IdleStateEvent) evt).state();
|
||||
if (e == IdleState.ALL_IDLE) {
|
||||
//fire a channelInactive to trigger publish of Will
|
||||
ctx.fireChannelInactive();
|
||||
ctx.close();
|
||||
} /*else if (e.getState() == IdleState.WRITER_IDLE) {
|
||||
ctx.writeAndFlush(new PingMessage());
|
||||
|
||||
@@ -76,7 +76,7 @@ public class NettyMQTTHandler extends ChannelInboundHandlerAdapter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx)throws Exception {
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
NettyChannel channel = m_channelMapper.get(ctx);
|
||||
String clientID = (String) channel.getAttribute(Constants.ATTR_CLIENTID);
|
||||
m_messaging.lostConnection(clientID);
|
||||
|
||||
@@ -93,6 +93,41 @@ public class ServerIntegrationFuseTest {
|
||||
assertTrue(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkWillTestmaentIsPublishedOnConnectionKill_noRetain() throws Exception {
|
||||
LOG.info("checkWillTestmaentIsPublishedOnConnectionKill");
|
||||
|
||||
String willTestamentTopic = "/will/test";
|
||||
String willTestamentMsg = "Bye bye";
|
||||
|
||||
MQTT mqtt = new MQTT();
|
||||
mqtt.setHost("localhost", 1883);
|
||||
mqtt.setClientId("WillTestamentPublisher");
|
||||
mqtt.setWillRetain(false);
|
||||
mqtt.setWillMessage(willTestamentMsg);
|
||||
mqtt.setWillTopic(willTestamentTopic);
|
||||
m_publisher = mqtt.blockingConnection();
|
||||
m_publisher.connect();
|
||||
|
||||
m_mqtt.setHost("localhost", 1883);
|
||||
m_mqtt.setCleanSession(false);
|
||||
m_mqtt.setClientId("Subscriber");
|
||||
m_subscriber = m_mqtt.blockingConnection();
|
||||
m_subscriber.connect();
|
||||
// Topic[] topics = new Topic[]{new Topic(willTestamentTopic, QoS.AT_LEAST_ONCE)};
|
||||
Topic[] topics = new Topic[]{new Topic(willTestamentTopic, QoS.AT_MOST_ONCE)};
|
||||
m_subscriber.subscribe(topics);
|
||||
|
||||
//Exercise, kill the publisher connection
|
||||
m_publisher.kill();
|
||||
|
||||
//Verify, that the testament is fired
|
||||
Message msg = m_subscriber.receive(500, TimeUnit.MILLISECONDS);
|
||||
assertNotNull("We should get notified with 'Will' message", msg);
|
||||
msg.ack();
|
||||
assertEquals(willTestamentMsg, new String(msg.getPayload()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkReplayofStoredPublishResumeAfter_a_disconnect_cleanSessionFalseQoS1() throws Exception {
|
||||
LOG.info("*** checkReplayofStoredPublishResumeAfter_a_disconnect_cleanSessionFalseQoS1 ***");
|
||||
|
||||
+53
@@ -17,20 +17,32 @@ package org.dna.mqtt.moquette.server;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.dna.mqtt.moquette.proto.messages.AbstractMessage.QOSType;
|
||||
import org.dna.mqtt.moquette.proto.messages.ConnectMessage;
|
||||
import org.dna.mqtt.moquette.testclient.Client;
|
||||
import org.fusesource.mqtt.client.BlockingConnection;
|
||||
import org.fusesource.mqtt.client.MQTT;
|
||||
import org.fusesource.mqtt.client.Message;
|
||||
import org.fusesource.mqtt.client.QoS;
|
||||
import org.fusesource.mqtt.client.Topic;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import static org.junit.Assert.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author andrea
|
||||
*/
|
||||
public class ServerLowlevelMessagesIntegrationTests {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ServerLowlevelMessagesIntegrationTests.class);
|
||||
|
||||
Server m_server;
|
||||
Client m_client;
|
||||
MQTT m_subscriberDef;
|
||||
|
||||
protected void startServer() throws IOException {
|
||||
m_server = new Server();
|
||||
@@ -41,6 +53,9 @@ public class ServerLowlevelMessagesIntegrationTests {
|
||||
public void setUp() throws Exception {
|
||||
startServer();
|
||||
m_client = new Client("localhost");
|
||||
m_subscriberDef = new MQTT();
|
||||
m_subscriberDef.setHost("localhost", 1883);
|
||||
m_subscriberDef.setClientId("Subscriber");
|
||||
}
|
||||
|
||||
@After
|
||||
@@ -68,4 +83,42 @@ public class ServerLowlevelMessagesIntegrationTests {
|
||||
|
||||
assertTrue(m_client.isConnectionLost());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkWillMessageIsWiredOnClientKeepAliveExpiry() throws Exception {
|
||||
LOG.info("*** checkWillMessageIsWiredOnClientKeepAliveExpiry ***");
|
||||
String willTestamentTopic = "/will/test";
|
||||
String willTestamentMsg = "Bye bye";
|
||||
|
||||
BlockingConnection willSubscriber = m_subscriberDef.blockingConnection();
|
||||
willSubscriber.connect();
|
||||
Topic[] topics = new Topic[]{new Topic(willTestamentTopic, QoS.AT_MOST_ONCE)};
|
||||
willSubscriber.subscribe(topics);
|
||||
|
||||
int keepAlive = 2; //secs
|
||||
ConnectMessage connectMessage = new ConnectMessage();
|
||||
connectMessage.setProcotolVersion((byte)3);
|
||||
connectMessage.setClientID("FAKECLNT");
|
||||
connectMessage.setKeepAlive(keepAlive);
|
||||
connectMessage.setWillFlag(true);
|
||||
connectMessage.setWillMessage(willTestamentMsg);
|
||||
connectMessage.setWillTopic(willTestamentTopic);
|
||||
connectMessage.setWillQos((byte) QOSType.MOST_ONE.ordinal());
|
||||
|
||||
//Execute
|
||||
m_client.sendMessage(connectMessage);
|
||||
long connectTime = System.currentTimeMillis();
|
||||
|
||||
//but after the 2 KEEP ALIVE timeut expires it gets fired,
|
||||
//NB it's 1,5 * KEEP_ALIVE so 3 secs and some millis to propagate the message
|
||||
Message msg = willSubscriber.receive(3100, TimeUnit.MILLISECONDS);
|
||||
long willMessageReceiveTime = System.currentTimeMillis();
|
||||
assertNotNull("the will message should be fired after keep alive!", msg);
|
||||
msg.ack();
|
||||
//the will message hasn't to be received before the elapsing of Keep Alive timeout
|
||||
assertTrue(willMessageReceiveTime - connectTime > 3000);
|
||||
|
||||
assertEquals(willTestamentMsg, new String(msg.getPayload()));
|
||||
willSubscriber.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user