mirror of
https://github.com/dotnet/MQTTnet.git
synced 2026-10-15 00:15:15 +08:00
Allow interception of will messages (#1613)
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.Tests.Server
|
||||
@@ -60,5 +61,28 @@ namespace MQTTnet.Tests.Server
|
||||
Assert.AreEqual("InjectedOne", messageReceivedHandler.ReceivedEventArgs[0].ApplicationMessage.Topic);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Intercept_Injected_Application_Message()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
var server = await testEnvironment.StartServer();
|
||||
|
||||
MqttApplicationMessage interceptedMessage = null;
|
||||
server.InterceptingPublishAsync += eventArgs =>
|
||||
{
|
||||
interceptedMessage = eventArgs.ApplicationMessage;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
var injectedApplicationMessage = new MqttApplicationMessageBuilder().WithTopic("InjectedOne").Build();
|
||||
await server.InjectApplicationMessage(new InjectedMqttApplicationMessage(injectedApplicationMessage));
|
||||
|
||||
await LongTestDelay();
|
||||
|
||||
Assert.IsNotNull(interceptedMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Client;
|
||||
@@ -10,33 +9,51 @@ namespace MQTTnet.Tests.Server
|
||||
[TestClass]
|
||||
public sealed class Will_Tests : BaseTestClass
|
||||
{
|
||||
[TestMethod]
|
||||
public async Task Intercept_Will_Message()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
var server = await testEnvironment.StartServer().ConfigureAwait(false);
|
||||
|
||||
MqttApplicationMessage willMessage = null;
|
||||
server.InterceptingPublishAsync += eventArgs =>
|
||||
{
|
||||
willMessage = eventArgs.ApplicationMessage;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
await testEnvironment.ConnectClient(new MqttClientOptionsBuilder()).ConfigureAwait(false);
|
||||
var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will").WithWillQualityOfServiceLevel(MqttQualityOfServiceLevel.AtMostOnce);
|
||||
var takeOverClient = await testEnvironment.ConnectClient(clientOptions).ConfigureAwait(false);
|
||||
takeOverClient.Dispose(); // Dispose will not send a DISCONNECT pattern first so the will message must be sent.
|
||||
|
||||
await LongTestDelay().ConfigureAwait(false);
|
||||
|
||||
Assert.IsNotNull(willMessage);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Will_Message_Do_Not_Send_On_Clean_Disconnect()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
var receivedMessagesCount = 0;
|
||||
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
var receiver = await testEnvironment.ConnectClient().ConfigureAwait(false);
|
||||
|
||||
var receivedMessages = testEnvironment.CreateApplicationMessageHandler(receiver);
|
||||
|
||||
await receiver.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
|
||||
|
||||
var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will");
|
||||
var sender = await testEnvironment.ConnectClient(clientOptions).ConfigureAwait(false);
|
||||
await sender.DisconnectAsync().ConfigureAwait(false);
|
||||
|
||||
var c1 = await testEnvironment.ConnectClient();
|
||||
await LongTestDelay().ConfigureAwait(false);
|
||||
|
||||
c1.ApplicationMessageReceivedAsync += e =>
|
||||
{
|
||||
Interlocked.Increment(ref receivedMessagesCount);
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
|
||||
|
||||
var c2 = await testEnvironment.ConnectClient(clientOptions);
|
||||
await c2.DisconnectAsync().ConfigureAwait(false);
|
||||
|
||||
await Task.Delay(1000);
|
||||
|
||||
Assert.AreEqual(0, receivedMessagesCount);
|
||||
Assert.AreEqual(0, receivedMessages.ReceivedEventArgs.Count);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,24 +64,17 @@ namespace MQTTnet.Tests.Server
|
||||
{
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder());
|
||||
|
||||
var receivedMessagesCount = 0;
|
||||
c1.ApplicationMessageReceivedAsync += e =>
|
||||
{
|
||||
Interlocked.Increment(ref receivedMessagesCount);
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
|
||||
var receiver = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder()).ConfigureAwait(false);
|
||||
var receivedMessages = testEnvironment.CreateApplicationMessageHandler(receiver);
|
||||
await receiver.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
|
||||
|
||||
var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will").WithWillQualityOfServiceLevel(MqttQualityOfServiceLevel.AtMostOnce);
|
||||
var c2 = await testEnvironment.ConnectClient(clientOptions);
|
||||
c2.Dispose(); // Dispose will not send a DISCONNECT pattern first so the will message must be sent.
|
||||
var takeOverClient = await testEnvironment.ConnectClient(clientOptions).ConfigureAwait(false);
|
||||
takeOverClient.Dispose(); // Dispose will not send a DISCONNECT pattern first so the will message must be sent.
|
||||
|
||||
await Task.Delay(1000);
|
||||
await LongTestDelay().ConfigureAwait(false);
|
||||
|
||||
Assert.AreEqual(1, receivedMessagesCount);
|
||||
Assert.AreEqual(1, receivedMessages.ReceivedEventArgs.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ namespace MQTTnet.Formatter
|
||||
{
|
||||
public MqttPubAckPacket Create(
|
||||
MqttPublishPacket publishPacket,
|
||||
InterceptingPublishEventArgs interceptingPublishEventArgs,
|
||||
DispatchApplicationMessageResult dispatchApplicationMessageResult)
|
||||
{
|
||||
if (publishPacket == null)
|
||||
@@ -22,28 +21,19 @@ namespace MQTTnet.Formatter
|
||||
throw new ArgumentNullException(nameof(publishPacket));
|
||||
}
|
||||
|
||||
if (dispatchApplicationMessageResult == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(dispatchApplicationMessageResult));
|
||||
}
|
||||
|
||||
var pubAckPacket = new MqttPubAckPacket
|
||||
{
|
||||
PacketIdentifier = publishPacket.PacketIdentifier
|
||||
PacketIdentifier = publishPacket.PacketIdentifier,
|
||||
ReasonCode = (MqttPubAckReasonCode)dispatchApplicationMessageResult.ReasonCode,
|
||||
ReasonString = dispatchApplicationMessageResult.ReasonString,
|
||||
UserProperties = dispatchApplicationMessageResult.UserProperties
|
||||
};
|
||||
|
||||
if (dispatchApplicationMessageResult.MatchingSubscribersCount == 0)
|
||||
{
|
||||
// NoMatchingSubscribers is ONLY sent by the server!
|
||||
pubAckPacket.ReasonCode = MqttPubAckReasonCode.NoMatchingSubscribers;
|
||||
}
|
||||
else
|
||||
{
|
||||
pubAckPacket.ReasonCode = MqttPubAckReasonCode.Success;
|
||||
}
|
||||
|
||||
if (interceptingPublishEventArgs != null)
|
||||
{
|
||||
pubAckPacket.ReasonCode = (MqttPubAckReasonCode)(int)interceptingPublishEventArgs.Response.ReasonCode;
|
||||
pubAckPacket.ReasonString = interceptingPublishEventArgs.Response.ReasonString;
|
||||
pubAckPacket.UserProperties = interceptingPublishEventArgs.Response.UserProperties;
|
||||
}
|
||||
|
||||
|
||||
return pubAckPacket;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,10 +25,7 @@ namespace MQTTnet.Formatter
|
||||
return pubRecPacket;
|
||||
}
|
||||
|
||||
public MqttPacket Create(
|
||||
MqttPublishPacket publishPacket,
|
||||
InterceptingPublishEventArgs interceptingPublishEventArgs,
|
||||
DispatchApplicationMessageResult dispatchApplicationMessageResult)
|
||||
public MqttPacket Create(MqttPublishPacket publishPacket, DispatchApplicationMessageResult dispatchApplicationMessageResult)
|
||||
{
|
||||
if (publishPacket == null)
|
||||
{
|
||||
@@ -38,21 +35,11 @@ namespace MQTTnet.Formatter
|
||||
var pubRecPacket = new MqttPubRecPacket
|
||||
{
|
||||
PacketIdentifier = publishPacket.PacketIdentifier,
|
||||
ReasonCode = MqttPubRecReasonCode.Success
|
||||
ReasonCode = (MqttPubRecReasonCode)dispatchApplicationMessageResult.ReasonCode,
|
||||
ReasonString = dispatchApplicationMessageResult.ReasonString,
|
||||
UserProperties = dispatchApplicationMessageResult.UserProperties
|
||||
};
|
||||
|
||||
if (dispatchApplicationMessageResult.MatchingSubscribersCount == 0)
|
||||
{
|
||||
pubRecPacket.ReasonCode = MqttPubRecReasonCode.NoMatchingSubscribers;
|
||||
}
|
||||
|
||||
if (interceptingPublishEventArgs != null)
|
||||
{
|
||||
pubRecPacket.ReasonCode = (MqttPubRecReasonCode)(int)interceptingPublishEventArgs.Response.ReasonCode;
|
||||
pubRecPacket.ReasonString = interceptingPublishEventArgs.Response.ReasonString;
|
||||
pubRecPacket.UserProperties = interceptingPublishEventArgs.Response.UserProperties;
|
||||
}
|
||||
|
||||
return pubRecPacket;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,15 +2,27 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public sealed class DispatchApplicationMessageResult
|
||||
{
|
||||
public DispatchApplicationMessageResult(int matchingSubscribersCount)
|
||||
public DispatchApplicationMessageResult(int reasonCode, bool closeConnection, string reasonString, List<MqttUserProperty> userProperties)
|
||||
{
|
||||
MatchingSubscribersCount = matchingSubscribersCount;
|
||||
ReasonCode = reasonCode;
|
||||
CloseConnection = closeConnection;
|
||||
ReasonString = reasonString;
|
||||
UserProperties = userProperties;
|
||||
}
|
||||
|
||||
public int MatchingSubscribersCount { get; }
|
||||
public bool CloseConnection { get; }
|
||||
|
||||
public int ReasonCode { get; }
|
||||
|
||||
public string ReasonString { get; }
|
||||
|
||||
public List<MqttUserProperty> UserProperties { get; }
|
||||
}
|
||||
}
|
||||
@@ -116,7 +116,7 @@ namespace MQTTnet.Server
|
||||
var willPublishPacket = MqttPacketFactories.Publish.Create(Session.LatestConnectPacket);
|
||||
var willApplicationMessage = MqttApplicationMessageFactory.Create(willPublishPacket);
|
||||
|
||||
_ = _sessionsManager.DispatchApplicationMessage(Id, willApplicationMessage);
|
||||
_ = _sessionsManager.DispatchApplicationMessage(Id, Session.Items, willApplicationMessage, CancellationToken.None);
|
||||
Session.WillMessageSent = true;
|
||||
|
||||
_logger.Info("Client '{0}': Published will message", Id);
|
||||
@@ -210,41 +210,17 @@ namespace MQTTnet.Server
|
||||
{
|
||||
HandleTopicAlias(publishPacket);
|
||||
|
||||
InterceptingPublishEventArgs interceptingPublishEventArgs = null;
|
||||
var applicationMessage = MqttApplicationMessageFactory.Create(publishPacket);
|
||||
var closeConnection = false;
|
||||
var processPublish = true;
|
||||
|
||||
if (_eventContainer.InterceptingPublishEvent.HasHandlers)
|
||||
{
|
||||
interceptingPublishEventArgs = new InterceptingPublishEventArgs(applicationMessage, cancellationToken, Id, Session.Items);
|
||||
if (string.IsNullOrEmpty(interceptingPublishEventArgs.ApplicationMessage.Topic))
|
||||
{
|
||||
// This can happen if a topic alias us used but the topic is
|
||||
// unknown to the server.
|
||||
interceptingPublishEventArgs.Response.ReasonCode = MqttPubAckReasonCode.TopicNameInvalid;
|
||||
interceptingPublishEventArgs.ProcessPublish = false;
|
||||
}
|
||||
var dispatchApplicationMessageResult =
|
||||
await _sessionsManager.DispatchApplicationMessage(Id, Session.Items, applicationMessage, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await _eventContainer.InterceptingPublishEvent.InvokeAsync(interceptingPublishEventArgs).ConfigureAwait(false);
|
||||
|
||||
applicationMessage = interceptingPublishEventArgs.ApplicationMessage;
|
||||
closeConnection = interceptingPublishEventArgs.CloseConnection;
|
||||
processPublish = interceptingPublishEventArgs.ProcessPublish;
|
||||
}
|
||||
|
||||
if (closeConnection)
|
||||
if (dispatchApplicationMessageResult.CloseConnection)
|
||||
{
|
||||
await StopAsync(MqttDisconnectReasonCode.UnspecifiedError);
|
||||
return;
|
||||
}
|
||||
|
||||
DispatchApplicationMessageResult dispatchResult = null;
|
||||
if (processPublish && applicationMessage != null)
|
||||
{
|
||||
dispatchResult = await _sessionsManager.DispatchApplicationMessage(Id, applicationMessage).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
switch (publishPacket.QualityOfServiceLevel)
|
||||
{
|
||||
case MqttQualityOfServiceLevel.AtMostOnce:
|
||||
@@ -254,13 +230,13 @@ namespace MQTTnet.Server
|
||||
}
|
||||
case MqttQualityOfServiceLevel.AtLeastOnce:
|
||||
{
|
||||
var pubAckPacket = MqttPacketFactories.PubAck.Create(publishPacket, interceptingPublishEventArgs, dispatchResult);
|
||||
var pubAckPacket = MqttPacketFactories.PubAck.Create(publishPacket, dispatchApplicationMessageResult);
|
||||
Session.EnqueueControlPacket(new MqttPacketBusItem(pubAckPacket));
|
||||
break;
|
||||
}
|
||||
case MqttQualityOfServiceLevel.ExactlyOnce:
|
||||
{
|
||||
var pubRecPacket = MqttPacketFactories.PubRec.Create(publishPacket, interceptingPublishEventArgs, dispatchResult);
|
||||
var pubRecPacket = MqttPacketFactories.PubRec.Create(publishPacket, dispatchApplicationMessageResult);
|
||||
Session.EnqueueControlPacket(new MqttPacketBusItem(pubRecPacket));
|
||||
break;
|
||||
}
|
||||
@@ -478,16 +454,16 @@ namespace MQTTnet.Server
|
||||
}
|
||||
|
||||
var logLevel = MqttNetLogLevel.Error;
|
||||
|
||||
|
||||
if (!IsRunning)
|
||||
{
|
||||
// There was an exception but the connection is already closed. So there is no chance to send a response to the client etc.
|
||||
logLevel = MqttNetLogLevel.Warning;
|
||||
}
|
||||
|
||||
|
||||
if (currentPacket == null)
|
||||
{
|
||||
_logger.Publish(logLevel, exception, "Client '{0}': Error while receiving packets", Id);
|
||||
_logger.Publish(logLevel, exception, "Client '{0}': Error while receiving packets", Id);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -126,81 +126,118 @@ namespace MQTTnet.Server
|
||||
|
||||
_logger.Verbose("Session for client '{0}' deleted.", clientId);
|
||||
}
|
||||
|
||||
public async Task<DispatchApplicationMessageResult> DispatchApplicationMessage(string senderId, MqttApplicationMessage applicationMessage)
|
||||
|
||||
public async Task<DispatchApplicationMessageResult> DispatchApplicationMessage(
|
||||
string senderId,
|
||||
IDictionary senderSessionItems,
|
||||
MqttApplicationMessage applicationMessage,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var matchingSubscribersCount = 0;
|
||||
try
|
||||
var processPublish = true;
|
||||
var closeConnection = false;
|
||||
string reasonString = null;
|
||||
List<MqttUserProperty> userProperties = null;
|
||||
var reasonCode = 0; // The reason code is later converted into several different but compatible enums!
|
||||
|
||||
// Allow the user to intercept application message...
|
||||
if (_eventContainer.InterceptingPublishEvent.HasHandlers)
|
||||
{
|
||||
if (applicationMessage.Retain)
|
||||
var interceptingPublishEventArgs = new InterceptingPublishEventArgs(applicationMessage, cancellationToken, senderId, senderSessionItems);
|
||||
if (string.IsNullOrEmpty(interceptingPublishEventArgs.ApplicationMessage.Topic))
|
||||
{
|
||||
await _retainedMessagesManager.UpdateMessage(senderId, applicationMessage).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
List<MqttSession> subscriberSessions;
|
||||
lock (_sessionsManagementLock)
|
||||
{
|
||||
subscriberSessions = _subscriberSessions.ToList();
|
||||
// This can happen if a topic alias us used but the topic is
|
||||
// unknown to the server.
|
||||
interceptingPublishEventArgs.Response.ReasonCode = MqttPubAckReasonCode.TopicNameInvalid;
|
||||
interceptingPublishEventArgs.ProcessPublish = false;
|
||||
}
|
||||
|
||||
// Calculate application message topic hash once for subscription checks
|
||||
MqttSubscription.CalculateTopicHash(applicationMessage.Topic, out var topicHash, out _, out _);
|
||||
await _eventContainer.InterceptingPublishEvent.InvokeAsync(interceptingPublishEventArgs).ConfigureAwait(false);
|
||||
|
||||
foreach (var session in subscriberSessions)
|
||||
{
|
||||
if(!session.TryCheckSubscriptions(
|
||||
applicationMessage.Topic,
|
||||
topicHash,
|
||||
applicationMessage.QualityOfServiceLevel,
|
||||
senderId,
|
||||
out var checkSubscriptionsResult))
|
||||
{
|
||||
// Checking the subscriptions has failed for the session. The session
|
||||
// will be ignored.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!checkSubscriptionsResult.IsSubscribed)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var newPublishPacket = MqttPacketFactories.Publish.Create(applicationMessage);
|
||||
newPublishPacket.QualityOfServiceLevel = checkSubscriptionsResult.QualityOfServiceLevel;
|
||||
newPublishPacket.SubscriptionIdentifiers = checkSubscriptionsResult.SubscriptionIdentifiers;
|
||||
|
||||
if (newPublishPacket.QualityOfServiceLevel > 0)
|
||||
{
|
||||
newPublishPacket.PacketIdentifier = session.PacketIdentifierProvider.GetNextPacketIdentifier();
|
||||
}
|
||||
|
||||
if (checkSubscriptionsResult.RetainAsPublished)
|
||||
{
|
||||
// Transfer the original retain state from the publisher. This is a MQTTv5 feature.
|
||||
newPublishPacket.Retain = applicationMessage.Retain;
|
||||
}
|
||||
else
|
||||
{
|
||||
newPublishPacket.Retain = false;
|
||||
}
|
||||
|
||||
session.EnqueueDataPacket(new MqttPacketBusItem(newPublishPacket));
|
||||
matchingSubscribersCount++;
|
||||
|
||||
_logger.Verbose("Client '{0}': Queued PUBLISH packet with topic '{1}'.", session.Id, applicationMessage.Topic);
|
||||
}
|
||||
|
||||
if (matchingSubscribersCount == 0)
|
||||
{
|
||||
await FireApplicationMessageNotConsumedEvent(applicationMessage, senderId).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error(exception, "Unhandled exception while processing next queued application message.");
|
||||
applicationMessage = interceptingPublishEventArgs.ApplicationMessage;
|
||||
closeConnection = interceptingPublishEventArgs.CloseConnection;
|
||||
processPublish = interceptingPublishEventArgs.ProcessPublish;
|
||||
reasonString = interceptingPublishEventArgs.Response.ReasonString;
|
||||
userProperties = interceptingPublishEventArgs.Response.UserProperties;
|
||||
reasonCode = (int)interceptingPublishEventArgs.Response.ReasonCode;
|
||||
}
|
||||
|
||||
return new DispatchApplicationMessageResult(matchingSubscribersCount);
|
||||
// Process the application message...
|
||||
if (processPublish && applicationMessage != null)
|
||||
{
|
||||
var matchingSubscribersCount = 0;
|
||||
try
|
||||
{
|
||||
if (applicationMessage.Retain)
|
||||
{
|
||||
await _retainedMessagesManager.UpdateMessage(senderId, applicationMessage).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
List<MqttSession> subscriberSessions;
|
||||
lock (_sessionsManagementLock)
|
||||
{
|
||||
subscriberSessions = _subscriberSessions.ToList();
|
||||
}
|
||||
|
||||
// Calculate application message topic hash once for subscription checks
|
||||
MqttSubscription.CalculateTopicHash(applicationMessage.Topic, out var topicHash, out _, out _);
|
||||
|
||||
foreach (var session in subscriberSessions)
|
||||
{
|
||||
if (!session.TryCheckSubscriptions(
|
||||
applicationMessage.Topic,
|
||||
topicHash,
|
||||
applicationMessage.QualityOfServiceLevel,
|
||||
senderId,
|
||||
out var checkSubscriptionsResult))
|
||||
{
|
||||
// Checking the subscriptions has failed for the session. The session
|
||||
// will be ignored.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!checkSubscriptionsResult.IsSubscribed)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var publishPacketCopy = MqttPacketFactories.Publish.Create(applicationMessage);
|
||||
publishPacketCopy.QualityOfServiceLevel = checkSubscriptionsResult.QualityOfServiceLevel;
|
||||
publishPacketCopy.SubscriptionIdentifiers = checkSubscriptionsResult.SubscriptionIdentifiers;
|
||||
|
||||
if (publishPacketCopy.QualityOfServiceLevel > 0)
|
||||
{
|
||||
publishPacketCopy.PacketIdentifier = session.PacketIdentifierProvider.GetNextPacketIdentifier();
|
||||
}
|
||||
|
||||
if (checkSubscriptionsResult.RetainAsPublished)
|
||||
{
|
||||
// Transfer the original retain state from the publisher. This is a MQTTv5 feature.
|
||||
publishPacketCopy.Retain = applicationMessage.Retain;
|
||||
}
|
||||
else
|
||||
{
|
||||
publishPacketCopy.Retain = false;
|
||||
}
|
||||
|
||||
session.EnqueueDataPacket(new MqttPacketBusItem(publishPacketCopy));
|
||||
matchingSubscribersCount++;
|
||||
|
||||
_logger.Verbose("Client '{0}': Queued PUBLISH packet with topic '{1}'.", session.Id, applicationMessage.Topic);
|
||||
}
|
||||
|
||||
if (matchingSubscribersCount == 0)
|
||||
{
|
||||
reasonCode = (int)MqttPubAckReasonCode.NoMatchingSubscribers;
|
||||
await FireApplicationMessageNotConsumedEvent(applicationMessage, senderId).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error(exception, "Unhandled exception while processing next queued application message.");
|
||||
}
|
||||
}
|
||||
|
||||
return new DispatchApplicationMessageResult(reasonCode, closeConnection, reasonString, userProperties);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
@@ -495,11 +532,11 @@ namespace MQTTnet.Server
|
||||
{
|
||||
MqttSession oldSession;
|
||||
MqttClient oldClient;
|
||||
|
||||
|
||||
lock (_sessionsManagementLock)
|
||||
{
|
||||
MqttSession session;
|
||||
|
||||
|
||||
// Create a new session (if required).
|
||||
if (!_sessions.TryGetValue(connectPacket.ClientId, out oldSession))
|
||||
{
|
||||
@@ -518,7 +555,7 @@ namespace MQTTnet.Server
|
||||
_logger.Verbose("Reusing existing session of client '{0}'.", connectPacket.ClientId);
|
||||
session = oldSession;
|
||||
oldSession = null;
|
||||
|
||||
|
||||
// Session persistence could change for MQTT 5 clients that reconnect with different SessionExpiryInterval
|
||||
session.IsPersistent = sessionShouldPersist;
|
||||
connAckPacket.IsSessionPresent = true;
|
||||
@@ -527,7 +564,7 @@ namespace MQTTnet.Server
|
||||
}
|
||||
|
||||
_sessions[connectPacket.ClientId] = session;
|
||||
|
||||
|
||||
// Create a new client (always required).
|
||||
_clients.TryGetValue(connectPacket.ClientId, out oldClient);
|
||||
if (oldClient != null)
|
||||
@@ -536,7 +573,7 @@ namespace MQTTnet.Server
|
||||
// for a later DISCONNECT packet.
|
||||
oldClient.IsTakenOver = true;
|
||||
}
|
||||
|
||||
|
||||
client = CreateClient(connectPacket, channelAdapter, session);
|
||||
|
||||
_clients[connectPacket.ClientId] = client;
|
||||
@@ -548,19 +585,15 @@ namespace MQTTnet.Server
|
||||
var preparingSessionEventArgs = new PreparingSessionEventArgs();
|
||||
await _eventContainer.PreparingSessionEvent.TryInvokeAsync(preparingSessionEventArgs, _logger).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
||||
if (oldClient != null)
|
||||
{
|
||||
await oldClient.StopAsync(MqttDisconnectReasonCode.SessionTakenOver).ConfigureAwait(false);
|
||||
|
||||
if (_eventContainer.ClientDisconnectedEvent.HasHandlers)
|
||||
{
|
||||
var eventArgs = new ClientDisconnectedEventArgs(
|
||||
oldClient.Id,
|
||||
MqttClientDisconnectType.Takeover,
|
||||
oldClient.Endpoint,
|
||||
oldClient.Session.Items);
|
||||
|
||||
var eventArgs = new ClientDisconnectedEventArgs(oldClient.Id, MqttClientDisconnectType.Takeover, oldClient.Endpoint, oldClient.Session.Items);
|
||||
|
||||
await _eventContainer.ClientDisconnectedEvent.TryInvokeAsync(eventArgs, _logger).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,17 +19,17 @@ namespace MQTTnet.Server
|
||||
{
|
||||
public class MqttServer : Disposable
|
||||
{
|
||||
readonly MqttServerEventContainer _eventContainer = new MqttServerEventContainer();
|
||||
|
||||
readonly IDictionary _sessionItems = new ConcurrentDictionary<object, object>();
|
||||
readonly ICollection<IMqttServerAdapter> _adapters;
|
||||
readonly MqttClientSessionsManager _clientSessionsManager;
|
||||
readonly MqttServerEventContainer _eventContainer = new MqttServerEventContainer();
|
||||
readonly MqttServerKeepAliveMonitor _keepAliveMonitor;
|
||||
readonly MqttNetSourceLogger _logger;
|
||||
readonly MqttServerOptions _options;
|
||||
readonly MqttRetainedMessagesManager _retainedMessagesManager;
|
||||
readonly IMqttNetLogger _rootLogger;
|
||||
|
||||
readonly IDictionary _sessionItems = new ConcurrentDictionary<object, object>();
|
||||
|
||||
CancellationTokenSource _cancellationTokenSource;
|
||||
|
||||
public MqttServer(MqttServerOptions options, IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger)
|
||||
@@ -207,7 +207,7 @@ namespace MQTTnet.Server
|
||||
return _clientSessionsManager.GetSessionStatusAsync();
|
||||
}
|
||||
|
||||
public async Task InjectApplicationMessage(InjectedMqttApplicationMessage injectedApplicationMessage)
|
||||
public Task InjectApplicationMessage(InjectedMqttApplicationMessage injectedApplicationMessage, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (injectedApplicationMessage == null)
|
||||
{
|
||||
@@ -223,34 +223,16 @@ namespace MQTTnet.Server
|
||||
|
||||
ThrowIfNotStarted();
|
||||
|
||||
var processPublish = true;
|
||||
var applicationMessage = injectedApplicationMessage.ApplicationMessage;
|
||||
|
||||
if (_eventContainer.InterceptingPublishEvent.HasHandlers)
|
||||
{
|
||||
var interceptingPublishEventArgs = new InterceptingPublishEventArgs(
|
||||
applicationMessage,
|
||||
_cancellationTokenSource.Token,
|
||||
injectedApplicationMessage.SenderClientId,
|
||||
_sessionItems);
|
||||
|
||||
await _eventContainer.InterceptingPublishEvent.InvokeAsync(interceptingPublishEventArgs).ConfigureAwait(false);
|
||||
|
||||
applicationMessage = interceptingPublishEventArgs.ApplicationMessage;
|
||||
processPublish = interceptingPublishEventArgs.ProcessPublish;
|
||||
}
|
||||
|
||||
if (!processPublish)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(applicationMessage.Topic))
|
||||
if (string.IsNullOrEmpty(injectedApplicationMessage.ApplicationMessage.Topic))
|
||||
{
|
||||
throw new NotSupportedException("Injected application messages must contain a topic. Topic alias is not supported.");
|
||||
}
|
||||
|
||||
await _clientSessionsManager.DispatchApplicationMessage(injectedApplicationMessage.SenderClientId, applicationMessage).ConfigureAwait(false);
|
||||
|
||||
return _clientSessionsManager.DispatchApplicationMessage(
|
||||
injectedApplicationMessage.SenderClientId,
|
||||
_sessionItems,
|
||||
injectedApplicationMessage.ApplicationMessage,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
public async Task StartAsync()
|
||||
|
||||
@@ -14,6 +14,6 @@ namespace MQTTnet.Server
|
||||
|
||||
public string ReasonString { get; set; }
|
||||
|
||||
public List<MqttUserProperty> UserProperties { get; }
|
||||
public List<MqttUserProperty> UserProperties { get; set; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user