Rearchitected the broker, switching from ProtocolProcessor and publisher to MQTTConnection, Session and PostOffice

This commit is contained in:
andsel
2018-11-02 12:33:59 +01:00
parent 0ad6ffdd3c
commit a2266e5bbe
60 changed files with 5225 additions and 1025 deletions
@@ -10,7 +10,7 @@ import io.moquette.spi.impl.subscriptions.ISubscriptionsDirectory;
import io.moquette.spi.impl.subscriptions.Subscription;
import io.moquette.spi.impl.subscriptions.Topic;
import io.moquette.spi.security.IAuthenticator;
import io.moquette.spi.security.IAuthorizator;
import io.moquette.spi.security.IAuthorizatorPolicy;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
@@ -40,7 +40,7 @@ public class ConnectHandler {
private SessionsRepository sessionsRepository;
private ISessionsStore m_sessionsStore;
private IAuthenticator m_authenticator;
private IAuthorizator m_authorizator;
private IAuthorizatorPolicy m_authorizator;
private ISubscriptionsDirectory subscriptions;
private boolean allowAnonymous;
private boolean allowZeroByteClientId;
@@ -48,7 +48,7 @@ public class ConnectHandler {
private InternalRepublisher internalRepublisher;
ConnectHandler(IConnectionsManager connectedClients, BrokerInterceptor interceptor, SessionsRepository sessionsRepository,
ISessionsStore sessionsStore, IAuthenticator authenticator, IAuthorizator authorizator,
ISessionsStore sessionsStore, IAuthenticator authenticator, IAuthorizatorPolicy authorizator,
ISubscriptionsDirectory subscriptions, boolean allowAnonymous, boolean allowZeroByteClientId,
boolean reauthorizeSubscriptionsOnConnect, InternalRepublisher internalRepublisher) {
this.connectionDescriptors = connectedClients;
@@ -20,10 +20,19 @@ import io.netty.buffer.ByteBuf;
import java.nio.charset.StandardCharsets;
final class DebugUtils {
public final class DebugUtils {
static String payload2Str(ByteBuf content) {
return new String(content.copy().array(), StandardCharsets.UTF_8);
public static String payload2Str(ByteBuf content) {
final ByteBuf copy = content.copy();
final byte[] bytesContent;
if (copy.isDirect()) {
final int size = copy.readableBytes();
bytesContent = new byte[size];
copy.readBytes(bytesContent);
} else {
bytesContent = copy.array();
}
return new String(bytesContent, StandardCharsets.UTF_8);
}
private DebugUtils() {
@@ -31,7 +31,7 @@ import io.moquette.spi.impl.subscriptions.ISubscriptionsDirectory;
import io.moquette.spi.impl.subscriptions.Subscription;
import io.moquette.spi.impl.subscriptions.Topic;
import io.moquette.spi.security.IAuthenticator;
import io.moquette.spi.security.IAuthorizator;
import io.moquette.spi.security.IAuthorizatorPolicy;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.handler.codec.mqtt.*;
@@ -41,11 +41,9 @@ import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import static io.moquette.server.ConnectionDescriptor.ConnectionState.*;
import static io.moquette.spi.impl.InternalRepublisher.createPublishForQos;
import static io.moquette.spi.impl.Utils.messageId;
import static io.moquette.spi.impl.Utils.readBytesAndRewind;
@@ -72,7 +70,7 @@ public class ProtocolProcessor {
private ConcurrentMap<RunningSubscription, SubscriptionState> subscriptionInCourse;
private ISubscriptionsDirectory subscriptions;
private IAuthorizator m_authorizator;
private IAuthorizatorPolicy m_authorizator;
private IMessagesStore m_messagesStore;
@@ -94,7 +92,7 @@ public class ProtocolProcessor {
}
public void init(ISubscriptionsDirectory subscriptions, IMessagesStore storageService, ISessionsStore sessionsStore,
IAuthenticator authenticator, boolean allowAnonymous, IAuthorizator authorizator,
IAuthenticator authenticator, boolean allowAnonymous, IAuthorizatorPolicy authorizator,
BrokerInterceptor interceptor, SessionsRepository sessionsRepository,
boolean reauthorizeSubscriptionsOnConnect) {
init(subscriptions, storageService, sessionsStore, authenticator, allowAnonymous, false,
@@ -103,7 +101,7 @@ public class ProtocolProcessor {
public void init(ISubscriptionsDirectory subscriptions, IMessagesStore storageService, ISessionsStore sessionsStore,
IAuthenticator authenticator, boolean allowAnonymous, boolean allowZeroByteClientId,
IAuthorizator authorizator, BrokerInterceptor interceptor, SessionsRepository sessionsRepository,
IAuthorizatorPolicy authorizator, BrokerInterceptor interceptor, SessionsRepository sessionsRepository,
boolean reauthorizeSubscriptionsOnConnect) {
init(new ConnectionDescriptorStore(), subscriptions, storageService, sessionsStore,
authenticator, allowAnonymous, allowZeroByteClientId, authorizator, interceptor, sessionsRepository,
@@ -130,7 +128,7 @@ public class ProtocolProcessor {
*/
void init(IConnectionsManager connectionDescriptors, ISubscriptionsDirectory subscriptions,
IMessagesStore storageService, ISessionsStore sessionsStore, IAuthenticator authenticator,
boolean allowAnonymous, boolean allowZeroByteClientId, IAuthorizator authorizator,
boolean allowAnonymous, boolean allowZeroByteClientId, IAuthorizatorPolicy authorizator,
BrokerInterceptor interceptor, SessionsRepository sessionsRepository,
boolean reauthorizeSubscriptionsOnConnect) {
LOG.info("Initializing MQTT protocol processor..");
@@ -31,7 +31,7 @@ import io.moquette.spi.impl.subscriptions.CTrieSubscriptionDirectory;
import io.moquette.spi.impl.subscriptions.ISubscriptionsDirectory;
import io.moquette.spi.impl.subscriptions.Subscription;
import io.moquette.spi.security.IAuthenticator;
import io.moquette.spi.security.IAuthorizator;
import io.moquette.spi.security.IAuthorizatorPolicy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -82,7 +82,7 @@ public class ProtocolProcessorBootstrapper {
* @return the processor created for the broker.
*/
public ProtocolProcessor init(IConfig props, List<? extends InterceptHandler> embeddedObservers,
IAuthenticator authenticator, IAuthorizator authorizator, Server server) {
IAuthenticator authenticator, IAuthorizatorPolicy authorizator, Server server) {
IMessagesStore messagesStore;
LOG.info("Initializing messages and sessions stores...");
String storageClassName = props.getProperty(BrokerConstants.STORAGE_CLASS_NAME, INMEMDB_STORE_CLASS);
@@ -145,13 +145,13 @@ public class ProtocolProcessorBootstrapper {
LOG.info("Configuring MQTT authorizator...");
String authorizatorClassName = props.getProperty(BrokerConstants.AUTHORIZATOR_CLASS_NAME, "");
if (authorizator == null && !authorizatorClassName.isEmpty()) {
authorizator = loadClass(authorizatorClassName, IAuthorizator.class, IConfig.class, props);
authorizator = loadClass(authorizatorClassName, IAuthorizatorPolicy.class, IConfig.class, props);
}
if (authorizator == null) {
String aclFilePath = props.getProperty(BrokerConstants.ACL_FILE_PROPERTY_NAME, "");
if (aclFilePath != null && !aclFilePath.isEmpty()) {
authorizator = new DenyAllAuthorizator();
authorizator = new DenyAllAuthorizatorPolicy();
try {
LOG.info("Parsing ACL file. Path = {}", aclFilePath);
authorizator = ACLFileParser.parse(resourceLoader.loadResource(aclFilePath));
@@ -159,7 +159,7 @@ public class ProtocolProcessorBootstrapper {
LOG.error("Unable to parse ACL file. path=" + aclFilePath, pex);
}
} else {
authorizator = new PermitAllAuthorizator();
authorizator = new PermitAllAuthorizatorPolicy();
}
LOG.info("An {} authorizator instance will be used", authorizator.getClass().getName());
}
@@ -19,7 +19,7 @@ package io.moquette.spi.impl;
import io.moquette.server.netty.NettyUtils;
import io.moquette.spi.IMessagesStore;
import io.moquette.spi.impl.subscriptions.Topic;
import io.moquette.spi.security.IAuthorizator;
import io.moquette.spi.security.IAuthorizatorPolicy;
import io.netty.channel.Channel;
import io.netty.handler.codec.mqtt.MqttPublishMessage;
import org.slf4j.Logger;
@@ -34,7 +34,7 @@ class Qos0PublishHandler extends QosPublishHandler {
private final BrokerInterceptor m_interceptor;
private final MessagesPublisher publisher;
public Qos0PublishHandler(IAuthorizator authorizator, IMessagesStore messagesStore, BrokerInterceptor interceptor,
public Qos0PublishHandler(IAuthorizatorPolicy authorizator, IMessagesStore messagesStore, BrokerInterceptor interceptor,
MessagesPublisher messagesPublisher) {
super(authorizator);
this.m_messagesStore = messagesStore;
@@ -18,11 +18,10 @@ package io.moquette.spi.impl;
import io.moquette.connections.IConnectionsManager;
import io.moquette.server.ConnectionDescriptor;
import io.moquette.server.ConnectionDescriptorStore;
import io.moquette.server.netty.NettyUtils;
import io.moquette.spi.IMessagesStore;
import io.moquette.spi.impl.subscriptions.Topic;
import io.moquette.spi.security.IAuthorizator;
import io.moquette.spi.security.IAuthorizatorPolicy;
import io.netty.channel.Channel;
import io.netty.handler.codec.mqtt.MqttFixedHeader;
import io.netty.handler.codec.mqtt.MqttMessageType;
@@ -47,7 +46,7 @@ class Qos1PublishHandler extends QosPublishHandler {
private final IConnectionsManager connectionDescriptors;
private final MessagesPublisher publisher;
Qos1PublishHandler(IAuthorizator authorizator, IMessagesStore messagesStore, BrokerInterceptor interceptor,
Qos1PublishHandler(IAuthorizatorPolicy authorizator, IMessagesStore messagesStore, BrokerInterceptor interceptor,
IConnectionsManager connectionDescriptors, MessagesPublisher messagesPublisher) {
super(authorizator);
this.m_messagesStore = messagesStore;
@@ -18,14 +18,12 @@ package io.moquette.spi.impl;
import io.moquette.connections.IConnectionsManager;
import io.moquette.server.ConnectionDescriptor;
import io.moquette.server.ConnectionDescriptorStore;
import io.moquette.server.netty.NettyUtils;
import io.moquette.spi.ClientSession;
import io.moquette.spi.IMessagesStore;
import io.moquette.spi.ISessionsStore;
import io.moquette.spi.impl.subscriptions.ISubscriptionsDirectory;
import io.moquette.spi.impl.subscriptions.Topic;
import io.moquette.spi.security.IAuthorizator;
import io.moquette.spi.security.IAuthorizatorPolicy;
import io.netty.channel.Channel;
import io.netty.handler.codec.mqtt.*;
import org.slf4j.Logger;
@@ -50,7 +48,7 @@ class Qos2PublishHandler extends QosPublishHandler {
private final MessagesPublisher publisher;
private final SessionsRepository sessionsRepository;
Qos2PublishHandler(IAuthorizator authorizator, ISubscriptionsDirectory subscriptions,
Qos2PublishHandler(IAuthorizatorPolicy authorizator, ISubscriptionsDirectory subscriptions,
IMessagesStore messagesStore, BrokerInterceptor interceptor,
IConnectionsManager connectionDescriptors,
MessagesPublisher messagesPublisher, SessionsRepository sessionsRepository) {
@@ -20,16 +20,16 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.moquette.server.netty.NettyUtils;
import io.moquette.spi.impl.subscriptions.Topic;
import io.moquette.spi.security.IAuthorizator;
import io.moquette.spi.security.IAuthorizatorPolicy;
import io.netty.channel.Channel;
abstract class QosPublishHandler {
private static final Logger LOG = LoggerFactory.getLogger(QosPublishHandler.class);
protected final IAuthorizator m_authorizator;
protected final IAuthorizatorPolicy m_authorizator;
protected QosPublishHandler(IAuthorizator m_authorizator) {
protected QosPublishHandler(IAuthorizatorPolicy m_authorizator) {
this.m_authorizator = m_authorizator;
}
@@ -17,7 +17,7 @@
package io.moquette.spi.impl.security;
import io.moquette.spi.impl.subscriptions.Topic;
import io.moquette.spi.security.IAuthorizator;
import io.moquette.spi.security.IAuthorizatorPolicy;
import java.text.ParseException;
import java.util.*;
@@ -27,7 +27,7 @@ import java.util.*;
*
* Not thread safe.
*/
class AuthorizationsCollector implements IAuthorizator {
class AuthorizationsCollector implements IAuthorizatorPolicy {
private List<Authorization> m_globalAuthorizations = new ArrayList<>();
private List<Authorization> m_patternAuthorizations = new ArrayList<>();
@@ -17,9 +17,9 @@
package io.moquette.spi.impl.security;
import io.moquette.spi.impl.subscriptions.Topic;
import io.moquette.spi.security.IAuthorizator;
import io.moquette.spi.security.IAuthorizatorPolicy;
public class DenyAllAuthorizator implements IAuthorizator {
public class DenyAllAuthorizatorPolicy implements IAuthorizatorPolicy {
@Override
public boolean canWrite(Topic topic, String user, String client) {
@@ -17,9 +17,9 @@
package io.moquette.spi.impl.security;
import io.moquette.spi.impl.subscriptions.Topic;
import io.moquette.spi.security.IAuthorizator;
import io.moquette.spi.security.IAuthorizatorPolicy;
public class PermitAllAuthorizator implements IAuthorizator {
public class PermitAllAuthorizatorPolicy implements IAuthorizatorPolicy {
@Override
public boolean canWrite(Topic topic, String user, String client) {
@@ -15,6 +15,8 @@
*/
package io.moquette.spi.impl.subscriptions;
import io.netty.handler.codec.mqtt.MqttQoS;
import java.util.*;
class CNode {
@@ -79,8 +81,8 @@ class CNode {
this.children.remove(node);
}
CNode addSubscription(String clientId, Topic topic) {
this.subscriptions.add(new Subscription(clientId, topic));
CNode addSubscription(Subscription newSubscription) {
this.subscriptions.add(new Subscription(newSubscription));
return this;
}
@@ -98,9 +98,45 @@ public class CTrieSubscriptionDirectory implements ISubscriptionsDirectory {
* to use fo searching matching subscriptions.
* @return the list of matching subscriptions, or empty if not matching.
*/
Set<Subscription> match(Topic topic) {
final Set<Subscription> matchingSubs = recursiveMatch(topic, this.root);
@Override
public Set<Subscription> matchWithoutQosSharpening(Topic topic) {
return recursiveMatch(topic, this.root);
}
@Override
public Set<Subscription> matchQosSharpening(Topic topic) {
final Set<Subscription> subscriptions = matchWithoutQosSharpening(topic);
Map<String, Subscription> subsGroupedByClient = new HashMap<>();
for (Subscription sub : subscriptions) {
Subscription existingSub = subsGroupedByClient.get(sub.clientId);
// update the selected subscriptions if not present or if has a greater qos
if (existingSub == null || existingSub.qosLessThan(sub)) {
subsGroupedByClient.put(sub.clientId, sub);
}
}
return new HashSet<>(subsGroupedByClient.values());
}
/**
* Given a topic string return the clients subscriptions that matches it. Topic string can't
* contain character # and + because they are reserved to listeners subscriptions, and not topic
* publishing.
*
* @param topic
* to use fo searching matching subscriptions.
* @return the list of matching subscriptions, or empty if not matching.
*/
@Deprecated
Set<Subscription> match(Topic topic) {
final Set<Subscription> matchingSubs = matchWithoutQosSharpening(topic);
// remove the overlapping subscriptions, selecting ones with greatest qos
Map<String, Subscription> subsForClient = loadSubscriptionsFromStorage(matchingSubs);
return new HashSet<>(subsForClient.values());
}
@Deprecated
private Map<String, Subscription> loadSubscriptionsFromStorage(Set<Subscription> matchingSubs) {
Map<String, Subscription> subsForClient = new HashMap<>();
for (Subscription matchingSub : matchingSubs) {
Subscription existingSub = subsForClient.get(matchingSub.clientId);
@@ -120,7 +156,7 @@ public class CTrieSubscriptionDirectory implements ISubscriptionsDirectory {
subsForClient.put(sub.clientId, sub);
}
}
return new HashSet<>(subsForClient.values());
return subsForClient;
}
Set<Subscription> recursiveMatch(Topic topic, INode inode) {
@@ -171,28 +207,28 @@ public class CTrieSubscriptionDirectory implements ISubscriptionsDirectory {
public void add(Subscription newSubscription) {
Action res;
do {
res = insert(newSubscription.clientId, newSubscription.topicFilter, this.root, newSubscription.topicFilter);
res = insert(newSubscription.topicFilter, this.root, newSubscription);
} while (res == Action.REPEAT);
}
private Action insert(String clientId, Topic topic, final INode inode, Topic fullpath) {
private Action insert(Topic topic, final INode inode, Subscription newSubscription) {
Token token = topic.headToken();
if (!topic.isEmpty() && inode.mainNode().anyChildrenMatch(token)) {
Topic remainingTopic = topic.exceptHeadToken();
INode nextInode = inode.mainNode().childOf(token);
return insert(clientId, remainingTopic, nextInode, fullpath);
return insert(remainingTopic, nextInode, newSubscription);
} else {
if (topic.isEmpty()) {
return insertSubscription(clientId, fullpath, inode);
return insertSubscription(inode, newSubscription);
} else {
return createNodeAndInsertSubscription(clientId, topic, inode, fullpath);
return createNodeAndInsertSubscription(topic, inode, newSubscription);
}
}
}
private Action insertSubscription(String clientId, Topic topic, INode inode) {
private Action insertSubscription(INode inode, Subscription newSubscription) {
CNode cnode = inode.mainNode();
CNode updatedCnode = cnode.copy().addSubscription(clientId, topic);
CNode updatedCnode = cnode.copy().addSubscription(newSubscription);
if (inode.compareAndSet(cnode, updatedCnode)) {
return Action.OK;
} else {
@@ -200,8 +236,8 @@ public class CTrieSubscriptionDirectory implements ISubscriptionsDirectory {
}
}
private Action createNodeAndInsertSubscription(String clientId, Topic topic, INode inode, Topic fullpath) {
INode newInode = createPathRec(clientId, topic, fullpath);
private Action createNodeAndInsertSubscription(Topic topic, INode inode, Subscription newSubscription) {
INode newInode = createPathRec(topic, newSubscription);
CNode cnode = inode.mainNode();
CNode updatedCnode = cnode.copy();
updatedCnode.add(newInode);
@@ -209,24 +245,24 @@ public class CTrieSubscriptionDirectory implements ISubscriptionsDirectory {
return inode.compareAndSet(cnode, updatedCnode) ? Action.OK : Action.REPEAT;
}
private INode createLeafNodes(String clientId, Topic fullpath, Token token) {
private INode createLeafNodes(Token token, Subscription newSubscription) {
CNode newLeafCnode = new CNode();
newLeafCnode.token = token;
newLeafCnode.addSubscription(clientId, fullpath);
newLeafCnode.addSubscription(newSubscription);
return new INode(newLeafCnode);
}
private INode createPathRec(String clientId, Topic topic, Topic fullpath) {
private INode createPathRec(Topic topic, Subscription newSubscription) {
Topic remainingTopic = topic.exceptHeadToken();
if (!remainingTopic.isEmpty()) {
INode inode = createPathRec(clientId, remainingTopic, fullpath);
INode inode = createPathRec(remainingTopic, newSubscription);
CNode cnode = new CNode();
cnode.token = topic.headToken();
cnode.add(inode);
return new INode(cnode);
} else {
return createLeafNodes(clientId, fullpath, topic.headToken());
return createLeafNodes(topic.headToken(), newSubscription);
}
}
@@ -18,11 +18,16 @@ package io.moquette.spi.impl.subscriptions;
import io.moquette.spi.impl.SessionsRepository;
import java.util.List;
import java.util.Set;
public interface ISubscriptionsDirectory {
void init(SessionsRepository sessionsRepository);
Set<Subscription> matchWithoutQosSharpening(Topic topic);
Set<Subscription> matchQosSharpening(Topic topic);
void add(Subscription newSubscription);
void removeSubscription(Topic topic, String clientID);
@@ -33,7 +33,7 @@ class TNode extends CNode {
}
@Override
CNode addSubscription(String clientId, Topic topic) {
CNode addSubscription(Subscription newSubscription) {
throw new IllegalStateException("Can't be invoked on TNode");
}