Skip to content

Commit 8ad8692

Browse files
authored
1681 misleading signature of mqttclientconnectasync mqttclientconnectresultcode expected but throws exception (#1749)
1 parent d8e5936 commit 8ad8692

File tree

6 files changed

+143
-78
lines changed

6 files changed

+143
-78
lines changed

.github/workflows/ReleaseNotes.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
* [Client] Exposed _UseDefaultCredentials_ and more for Web Socket options and proxy options (#1734, thanks to @impworks).
88
* [Client] Exposed more TLS options (#1729).
99
* [Client] Fixed wrong return code conversion (#1729).
10+
* [Client] Added an option to avoid throwing an exception when the server returned a proper non success (but valid) response (#1681).
1011
* [Server] Improved performance by changing internal locking strategy for subscriptions (#1716, thanks to @zeheng).
1112
* [Server] Fixed exceptions when clients are connecting and disconnecting very fast while accessing the client status for connection validation (#1742).
1213
* [Server] Exposed more properties in _ClientConnectedEventArgs_ (#1738).

Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Connection_Tests.cs

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33
// See the LICENSE file in the project root for more information.
44

55
using System;
6+
using System.Collections.Generic;
67
using System.Threading;
78
using System.Threading.Tasks;
89
using Microsoft.VisualStudio.TestTools.UnitTesting;
910
using MQTTnet.Client;
1011
using MQTTnet.Exceptions;
1112
using MQTTnet.Formatter;
1213
using MQTTnet.Internal;
14+
using MQTTnet.Packets;
1315
using MQTTnet.Protocol;
1416
using MQTTnet.Server;
1517

@@ -128,7 +130,7 @@ public async Task Disconnect_Clean_With_Custom_Reason()
128130
await client.DisconnectAsync(disconnectOptions);
129131

130132
await LongTestDelay();
131-
133+
132134
Assert.IsNotNull(eventArgs);
133135
Assert.AreEqual(MqttDisconnectReasonCode.MessageRateTooHigh, eventArgs.ReasonCode);
134136
}
@@ -156,13 +158,43 @@ public async Task Disconnect_Clean_With_User_Properties()
156158
await client.DisconnectAsync(disconnectOptions);
157159

158160
await LongTestDelay();
159-
161+
160162
Assert.IsNotNull(eventArgs);
161163
Assert.IsNotNull(eventArgs.UserProperties);
162164
Assert.AreEqual(1, eventArgs.UserProperties.Count);
163165
Assert.AreEqual("test_name", eventArgs.UserProperties[0].Name);
164166
Assert.AreEqual("test_value", eventArgs.UserProperties[0].Value);
165167
}
166168
}
169+
170+
[TestMethod]
171+
public async Task Return_Non_Success()
172+
{
173+
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
174+
{
175+
var server = await testEnvironment.StartServer();
176+
177+
server.ValidatingConnectionAsync += args =>
178+
{
179+
args.ResponseUserProperties = new List<MqttUserProperty>
180+
{
181+
new MqttUserProperty("Property", "Value")
182+
};
183+
184+
args.ReasonCode = MqttConnectReasonCode.QuotaExceeded;
185+
186+
return CompletedTask.Instance;
187+
};
188+
189+
var client = testEnvironment.CreateClient();
190+
191+
var response = await client.ConnectAsync(testEnvironment.CreateDefaultClientOptionsBuilder().WithoutThrowOnNonSuccessfulConnectResponse().Build());
192+
193+
Assert.IsNotNull(response);
194+
Assert.AreEqual(MqttClientConnectResultCode.QuotaExceeded, response.ResultCode);
195+
Assert.AreEqual(response.UserProperties[0].Name, "Property");
196+
Assert.AreEqual(response.UserProperties[0].Value, "Value");
197+
}
198+
}
167199
}
168200
}

Source/MQTTnet/Client/Internal/MqttClientResultFactory.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ namespace MQTTnet.Client.Internal
66
{
77
public static class MqttClientResultFactory
88
{
9+
public static readonly MqttClientConnectResultFactory ConnectResult = new MqttClientConnectResultFactory();
910
public static readonly MqttClientPublishResultFactory PublishResult = new MqttClientPublishResultFactory();
1011
public static readonly MqttClientSubscribeResultFactory SubscribeResult = new MqttClientSubscribeResultFactory();
1112
public static readonly MqttClientUnsubscribeResultFactory UnsubscribeResult = new MqttClientUnsubscribeResultFactory();

Source/MQTTnet/Client/MqttClient.cs

Lines changed: 82 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,12 @@ public async Task<MqttClientConnectResult> ConnectAsync(MqttClientOptions option
137137
}
138138
}
139139

140+
if (connectResult.ResultCode != MqttClientConnectResultCode.Success)
141+
{
142+
_logger.Warning("Connecting failed: {0}", connectResult.ResultCode);
143+
return connectResult;
144+
}
145+
140146
_lastPacketSentTimestamp = DateTime.UtcNow;
141147

142148
var keepAliveInterval = Options.KeepAlivePeriod;
@@ -434,8 +440,7 @@ async Task<MqttClientConnectResult> Authenticate(IMqttChannelAdapter channelAdap
434440

435441
if (receivedPacket is MqttConnAckPacket connAckPacket)
436442
{
437-
var clientConnectResultFactory = new MqttClientConnectResultFactory();
438-
result = clientConnectResultFactory.Create(connAckPacket, channelAdapter.PacketFormatterAdapter.ProtocolVersion);
443+
result = MqttClientResultFactory.ConnectResult.Create(connAckPacket, _adapter.PacketFormatterAdapter.ProtocolVersion);
439444
}
440445
else
441446
{
@@ -447,9 +452,18 @@ async Task<MqttClientConnectResult> Authenticate(IMqttChannelAdapter channelAdap
447452
throw new MqttConnectingFailedException($"Error while authenticating. {exception.Message}", exception, null);
448453
}
449454

450-
if (result.ResultCode != MqttClientConnectResultCode.Success)
455+
// This is no feature. It is basically a backward compatibility option and should be removed in the future.
456+
// The client should not throw any exception if the transport layer connection was successful and the server
457+
// did send a proper ACK packet with a non success response.
458+
if (options.ThrowOnNonSuccessfulConnectResponse)
451459
{
452-
throw new MqttConnectingFailedException($"Connecting with MQTT server failed ({result.ResultCode}).", null, result);
460+
_logger.Warning(
461+
"Client will now throw an _MqttConnectingFailedException_. This is obsolete and will be removed in the future. Consider setting _ThrowOnNonSuccessfulResponseFromServer=False_ in client options.");
462+
463+
if (result.ResultCode != MqttClientConnectResultCode.Success)
464+
{
465+
throw new MqttConnectingFailedException($"Connecting with MQTT server failed ({result.ResultCode}).", null, result);
466+
}
453467
}
454468

455469
_logger.Verbose("Authenticated MQTT connection with server established.");
@@ -498,9 +512,11 @@ async Task<MqttClientConnectResult> ConnectInternal(IMqttChannelAdapter channelA
498512
_publishPacketReceiverQueue = new AsyncQueue<MqttPublishPacket>();
499513

500514
var connectResult = await Authenticate(channelAdapter, Options, effectiveCancellationToken.Token).ConfigureAwait(false);
501-
502-
_publishPacketReceiverTask = Task.Run(() => ProcessReceivedPublishPackets(backgroundCancellationToken), backgroundCancellationToken);
503-
_packetReceiverTask = Task.Run(() => ReceivePacketsLoop(backgroundCancellationToken), backgroundCancellationToken);
515+
if (connectResult.ResultCode == MqttClientConnectResultCode.Success)
516+
{
517+
_publishPacketReceiverTask = Task.Run(() => ProcessReceivedPublishPackets(backgroundCancellationToken), backgroundCancellationToken);
518+
_packetReceiverTask = Task.Run(() => ReceivePacketsLoop(backgroundCancellationToken), backgroundCancellationToken);
519+
}
504520

505521
return connectResult;
506522
}
@@ -754,6 +770,65 @@ async Task<MqttPacket> Receive(CancellationToken cancellationToken)
754770
return packet;
755771
}
756772

773+
async Task ReceivePacketsLoop(CancellationToken cancellationToken)
774+
{
775+
try
776+
{
777+
_logger.Verbose("Start receiving packets.");
778+
779+
while (!cancellationToken.IsCancellationRequested)
780+
{
781+
var packet = await Receive(cancellationToken).ConfigureAwait(false);
782+
783+
if (cancellationToken.IsCancellationRequested)
784+
{
785+
return;
786+
}
787+
788+
if (packet == null)
789+
{
790+
await DisconnectInternal(_packetReceiverTask, null, null).ConfigureAwait(false);
791+
792+
return;
793+
}
794+
795+
await TryProcessReceivedPacket(packet, cancellationToken).ConfigureAwait(false);
796+
}
797+
}
798+
catch (Exception exception)
799+
{
800+
if (_cleanDisconnectInitiated)
801+
{
802+
return;
803+
}
804+
805+
if (exception is AggregateException aggregateException)
806+
{
807+
exception = aggregateException.GetBaseException();
808+
}
809+
810+
if (exception is OperationCanceledException)
811+
{
812+
}
813+
else if (exception is MqttCommunicationException)
814+
{
815+
_logger.Warning(exception, "Communication error while receiving packets.");
816+
}
817+
else
818+
{
819+
_logger.Error(exception, "Error while receiving packets.");
820+
}
821+
822+
_packetDispatcher.FailAll(exception);
823+
824+
await DisconnectInternal(_packetReceiverTask, exception, null).ConfigureAwait(false);
825+
}
826+
finally
827+
{
828+
_logger.Verbose("Stopped receiving packets.");
829+
}
830+
}
831+
757832
async Task<TResponsePacket> Request<TResponsePacket>(MqttPacket requestPacket, CancellationToken cancellationToken) where TResponsePacket : MqttPacket
758833
{
759834
cancellationToken.ThrowIfCancellationRequested();
@@ -920,65 +995,6 @@ async Task TryProcessReceivedPacket(MqttPacket packet, CancellationToken cancell
920995
}
921996
}
922997

923-
async Task ReceivePacketsLoop(CancellationToken cancellationToken)
924-
{
925-
try
926-
{
927-
_logger.Verbose("Start receiving packets.");
928-
929-
while (!cancellationToken.IsCancellationRequested)
930-
{
931-
var packet = await Receive(cancellationToken).ConfigureAwait(false);
932-
933-
if (cancellationToken.IsCancellationRequested)
934-
{
935-
return;
936-
}
937-
938-
if (packet == null)
939-
{
940-
await DisconnectInternal(_packetReceiverTask, null, null).ConfigureAwait(false);
941-
942-
return;
943-
}
944-
945-
await TryProcessReceivedPacket(packet, cancellationToken).ConfigureAwait(false);
946-
}
947-
}
948-
catch (Exception exception)
949-
{
950-
if (_cleanDisconnectInitiated)
951-
{
952-
return;
953-
}
954-
955-
if (exception is AggregateException aggregateException)
956-
{
957-
exception = aggregateException.GetBaseException();
958-
}
959-
960-
if (exception is OperationCanceledException)
961-
{
962-
}
963-
else if (exception is MqttCommunicationException)
964-
{
965-
_logger.Warning(exception, "Communication error while receiving packets.");
966-
}
967-
else
968-
{
969-
_logger.Error(exception, "Error while receiving packets.");
970-
}
971-
972-
_packetDispatcher.FailAll(exception);
973-
974-
await DisconnectInternal(_packetReceiverTask, exception, null).ConfigureAwait(false);
975-
}
976-
finally
977-
{
978-
_logger.Verbose("Stopped receiving packets.");
979-
}
980-
}
981-
982998
async Task TrySendKeepAliveMessages(CancellationToken cancellationToken)
983999
{
9841000
try

Source/MQTTnet/Client/Options/MqttClientOptions.cs

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@ namespace MQTTnet.Client
1212
{
1313
public sealed class MqttClientOptions
1414
{
15+
/// <summary>
16+
/// Usually the MQTT packets can be send partially. This is done by using multiple TCP packets
17+
/// or WebSocket frames etc. Unfortunately not all brokers (like Amazon Web Services (AWS)) do support this feature and
18+
/// will close the connection when receiving such packets. If such a service is used this flag must
19+
/// be set to _false_.
20+
/// </summary>
21+
public bool AllowPacketFragmentation { get; set; } = true;
22+
1523
/// <summary>
1624
/// Gets or sets the authentication data.
1725
/// <remarks>MQTT 5.0.0+ feature.</remarks>
@@ -24,14 +32,6 @@ public sealed class MqttClientOptions
2432
/// </summary>
2533
public string AuthenticationMethod { get; set; }
2634

27-
/// <summary>
28-
/// Usually the MQTT packets can be send partially. This is done by using multiple TCP packets
29-
/// or WebSocket frames etc. Unfortunately not all brokers (like Amazon Web Services (AWS)) do support this feature and
30-
/// will close the connection when receiving such packets. If such a service is used this flag must
31-
/// be set to _false_.
32-
/// </summary>
33-
public bool AllowPacketFragmentation { get; set; } = true;
34-
3535
public IMqttClientChannelOptions ChannelOptions { get; set; }
3636

3737
/// <summary>
@@ -100,6 +100,11 @@ public sealed class MqttClientOptions
100100
/// </summary>
101101
public uint SessionExpiryInterval { get; set; }
102102

103+
/// <summary>
104+
/// Gets or sets whether an exception should be thrown when the server has sent a non success ACK packet.
105+
/// </summary>
106+
public bool ThrowOnNonSuccessfulConnectResponse { get; set; } = true;
107+
103108
/// <summary>
104109
/// Gets or sets the timeout which will be applied at socket level and internal operations.
105110
/// The default value is the same as for sockets in .NET in general.
@@ -222,4 +227,4 @@ public sealed class MqttClientOptions
222227
/// </summary>
223228
public int WriterBufferSizeMax { get; set; } = 65535;
224229
}
225-
}
230+
}

Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,17 @@ public MqttClientOptions Build()
7878

7979
return _options;
8080
}
81-
81+
82+
/// <summary>
83+
/// The client will not throw an exception when the MQTT server responses with a non success ACK packet.
84+
/// This will become the default behavior in future versions of the library.
85+
/// </summary>
86+
public MqttClientOptionsBuilder WithoutThrowOnNonSuccessfulConnectResponse()
87+
{
88+
_options.ThrowOnNonSuccessfulConnectResponse = false;
89+
return this;
90+
}
91+
8292
public MqttClientOptionsBuilder WithAuthentication(string method, byte[] data)
8393
{
8494
_options.AuthenticationMethod = method;

0 commit comments

Comments
 (0)