Topic Subscriptions/ Sessions/ Request Response features (#36)
* Implement Request/Response and RenewLock/ReceiveBySeq Number * Git attributes * Configure await for the new AsyncCalls * Test Comment Change * nit cleanup * With Sessions, GetState/SetState and Renewlock * Removing orig files * Remove test assembly changes * Remove unused usings * Removing files from Microsoft.Azure.Messaging * Added some missing Configure Awaits * Remove Unused Usings * Remove unused usings * Review Comments * xmldoc comments * Sessions/Topic Subscriptions/Refactoring/Tests * Addressing review comments * Removing Subscription Names
This commit is contained in:
Родитель
d61e62c07c
Коммит
a34fbdb62e
|
@ -1,26 +1,63 @@
|
|||
# Default behavior: if Git thinks a file is text (as opposed to binary), it
|
||||
# will normalize line endings to LF in the repository, but convert to your
|
||||
# platform's native line endings on checkout (e.g., CRLF for Windows).
|
||||
###############################################################################
|
||||
# Set default behavior to automatically normalize line endings.
|
||||
###############################################################################
|
||||
* text=auto
|
||||
|
||||
# Explicitly declare text files you want to always be normalized and converted
|
||||
# to native line endings on checkout. E.g.,
|
||||
#*.c text
|
||||
###############################################################################
|
||||
# Set default behavior for command prompt diff.
|
||||
#
|
||||
# This is need for earlier builds of msysgit that does not have it on by
|
||||
# default for csharp files.
|
||||
# Note: This is only used by command line
|
||||
###############################################################################
|
||||
#*.cs diff=csharp
|
||||
|
||||
# Declare files that will always have CRLF line endings on checkout. E.g.,
|
||||
#*.sln text eol=crlf
|
||||
###############################################################################
|
||||
# Set the merge driver for project and solution files
|
||||
#
|
||||
# Merging from the command prompt will add diff markers to the files if there
|
||||
# are conflicts (Merging from VS is not affected by the settings below, in VS
|
||||
# the diff markers are never inserted). Diff markers may cause the following
|
||||
# file extensions to fail to load in VS. An alternative would be to treat
|
||||
# these files as binary and thus will always conflict and require user
|
||||
# intervention with every merge. To do so, just uncomment the entries below
|
||||
###############################################################################
|
||||
#*.sln merge=binary
|
||||
#*.csproj merge=binary
|
||||
#*.vbproj merge=binary
|
||||
#*.vcxproj merge=binary
|
||||
#*.vcproj merge=binary
|
||||
#*.dbproj merge=binary
|
||||
#*.fsproj merge=binary
|
||||
#*.lsproj merge=binary
|
||||
#*.wixproj merge=binary
|
||||
#*.modelproj merge=binary
|
||||
#*.sqlproj merge=binary
|
||||
#*.wwaproj merge=binary
|
||||
|
||||
# Declare files that will always have LF line endings on checkout. E.g.,
|
||||
*.sh text eol=lf
|
||||
###############################################################################
|
||||
# behavior for image files
|
||||
#
|
||||
# image files are treated as binary by default.
|
||||
###############################################################################
|
||||
#*.jpg binary
|
||||
#*.png binary
|
||||
#*.gif binary
|
||||
|
||||
# Denote all files that should not have line endings normalized, should not be
|
||||
# merged, and should not show in a textual diff.
|
||||
*.docm binary
|
||||
*.docx binary
|
||||
*.ico binary
|
||||
*.lib binary
|
||||
*.png binary
|
||||
*.pptx binary
|
||||
*.snk binary
|
||||
*.vsdx binary
|
||||
*.xps binary
|
||||
###############################################################################
|
||||
# diff behavior for common document formats
|
||||
#
|
||||
# Convert binary document formats to text before diffing them. This feature
|
||||
# is only available from the command line. Turn it on by uncommenting the
|
||||
# entries below.
|
||||
###############################################################################
|
||||
#*.doc diff=astextplain
|
||||
#*.DOC diff=astextplain
|
||||
#*.docx diff=astextplain
|
||||
#*.DOCX diff=astextplain
|
||||
#*.dot diff=astextplain
|
||||
#*.DOT diff=astextplain
|
||||
#*.pdf diff=astextplain
|
||||
#*.PDF diff=astextplain
|
||||
#*.rtf diff=astextplain
|
||||
#*.RTF diff=astextplain
|
||||
|
|
|
@ -11,6 +11,7 @@ namespace Microsoft.Azure.ServiceBus.Amqp
|
|||
{
|
||||
// AMQP Management Operation
|
||||
public const string ManagementAddress = "$management";
|
||||
public const string EntityTypeManagement = "entity-mgmt";
|
||||
public const string EntityNameKey = "name";
|
||||
public const string PartitionNameKey = "partition";
|
||||
public const string ManagementOperationKey = "operation";
|
||||
|
@ -62,5 +63,6 @@ namespace Microsoft.Azure.ServiceBus.Amqp
|
|||
public static readonly AmqpSymbol EntityAlreadyExistsError = AmqpConstants.Vendor + ":entity-already-exists";
|
||||
public static readonly AmqpSymbol RelayNotFoundError = AmqpConstants.Vendor + ":relay-not-found";
|
||||
public static readonly AmqpSymbol MessageNotFoundError = AmqpConstants.Vendor + ":message-not-found";
|
||||
public static readonly AmqpSymbol LockedUntilUtc = AmqpConstants.Vendor + ":locked-until-utc";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Azure.ServiceBus.Amqp
|
||||
{
|
||||
using System;
|
||||
using Microsoft.Azure.Amqp;
|
||||
using Microsoft.Azure.Amqp.Sasl;
|
||||
using Microsoft.Azure.Amqp.Transport;
|
||||
|
||||
public class AmqpConnectionHelper
|
||||
{
|
||||
const string CbsSaslMechanismName = "MSSBCBS";
|
||||
|
||||
public static AmqpSettings CreateAmqpSettings(
|
||||
Version amqpVersion,
|
||||
bool useSslStreamSecurity,
|
||||
bool hasTokenProvider,
|
||||
string sslHostName = null,
|
||||
bool useWebSockets = false,
|
||||
bool sslStreamUpgrade = false,
|
||||
System.Net.NetworkCredential networkCredential = null,
|
||||
System.Net.Security.RemoteCertificateValidationCallback certificateValidationCallback = null,
|
||||
bool forceTokenProvider = true)
|
||||
{
|
||||
AmqpSettings settings = new AmqpSettings();
|
||||
if (useSslStreamSecurity && !useWebSockets && sslStreamUpgrade)
|
||||
{
|
||||
TlsTransportSettings tlsSettings = new TlsTransportSettings();
|
||||
tlsSettings.CertificateValidationCallback = certificateValidationCallback;
|
||||
tlsSettings.TargetHost = sslHostName;
|
||||
|
||||
TlsTransportProvider tlsProvider = new TlsTransportProvider(tlsSettings);
|
||||
tlsProvider.Versions.Add(new AmqpVersion(amqpVersion));
|
||||
settings.TransportProviders.Add(tlsProvider);
|
||||
}
|
||||
|
||||
if (hasTokenProvider || networkCredential != null)
|
||||
{
|
||||
SaslTransportProvider saslProvider = new SaslTransportProvider();
|
||||
saslProvider.Versions.Add(new AmqpVersion(amqpVersion));
|
||||
settings.TransportProviders.Add(saslProvider);
|
||||
|
||||
if (forceTokenProvider)
|
||||
{
|
||||
saslProvider.AddHandler(new SaslAnonymousHandler(CbsSaslMechanismName));
|
||||
}
|
||||
else if (networkCredential != null)
|
||||
{
|
||||
SaslPlainHandler plainHandler = new SaslPlainHandler();
|
||||
plainHandler.AuthenticationIdentity = networkCredential.UserName;
|
||||
plainHandler.Password = networkCredential.Password;
|
||||
saslProvider.AddHandler(plainHandler);
|
||||
}
|
||||
else
|
||||
{
|
||||
// old client behavior: keep it for validation only
|
||||
saslProvider.AddHandler(new SaslExternalHandler());
|
||||
}
|
||||
}
|
||||
|
||||
AmqpTransportProvider amqpProvider = new AmqpTransportProvider();
|
||||
amqpProvider.Versions.Add(new AmqpVersion(amqpVersion));
|
||||
settings.TransportProviders.Add(amqpProvider);
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage(
|
||||
"StyleCop.CSharp.NamingRules",
|
||||
"SA1305:FieldNamesMustNotUseHungarianNotation",
|
||||
Justification = "tpSettings is a local variable.")]
|
||||
public static TransportSettings CreateTcpTransportSettings(
|
||||
string networkHost,
|
||||
string hostName,
|
||||
int port,
|
||||
bool useSslStreamSecurity,
|
||||
bool sslStreamUpgrade = false,
|
||||
string sslHostName = null,
|
||||
System.Security.Cryptography.X509Certificates.X509Certificate2 certificate = null,
|
||||
System.Net.Security.RemoteCertificateValidationCallback certificateValidationCallback = null)
|
||||
{
|
||||
TcpTransportSettings tcpSettings = new TcpTransportSettings
|
||||
{
|
||||
Host = networkHost,
|
||||
Port = port < 0 ? AmqpConstants.DefaultSecurePort : port,
|
||||
ReceiveBufferSize = AmqpConstants.TransportBufferSize,
|
||||
SendBufferSize = AmqpConstants.TransportBufferSize
|
||||
};
|
||||
|
||||
TransportSettings tpSettings = tcpSettings;
|
||||
if (useSslStreamSecurity && !sslStreamUpgrade)
|
||||
{
|
||||
TlsTransportSettings tlsSettings = new TlsTransportSettings(tcpSettings)
|
||||
{
|
||||
TargetHost = sslHostName ?? hostName,
|
||||
Certificate = certificate,
|
||||
CertificateValidationCallback = certificateValidationCallback
|
||||
};
|
||||
tpSettings = tlsSettings;
|
||||
}
|
||||
|
||||
return tpSettings;
|
||||
}
|
||||
|
||||
public static AmqpConnectionSettings CreateAmqpConnectionSettings(uint maxFrameSize, string containerId, string hostName)
|
||||
{
|
||||
AmqpConnectionSettings connectionSettings = new AmqpConnectionSettings
|
||||
{
|
||||
MaxFrameSize = maxFrameSize,
|
||||
ContainerId = containerId,
|
||||
HostName = hostName
|
||||
};
|
||||
return connectionSettings;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,11 +5,12 @@ namespace Microsoft.Azure.ServiceBus.Amqp
|
|||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Messaging.Amqp;
|
||||
using Microsoft.Azure.Amqp;
|
||||
using Microsoft.Azure.Amqp.Encoding;
|
||||
using Microsoft.Azure.Amqp.Framing;
|
||||
|
||||
class AmqpExceptionHelper
|
||||
static class AmqpExceptionHelper
|
||||
{
|
||||
static readonly Dictionary<string, AmqpResponseStatusCode> ConditionToStatusMap = new Dictionary<string, AmqpResponseStatusCode>()
|
||||
{
|
||||
|
@ -55,6 +56,21 @@ namespace Microsoft.Azure.ServiceBus.Amqp
|
|||
return AmqpErrorCode.InternalError;
|
||||
}
|
||||
|
||||
public static AmqpResponseStatusCode GetResponseStatusCode(this AmqpMessage responseMessage)
|
||||
{
|
||||
AmqpResponseStatusCode responseStatusCode = AmqpResponseStatusCode.Unused;
|
||||
if (responseMessage != null)
|
||||
{
|
||||
object statusCodeValue = responseMessage.ApplicationProperties.Map[ManagementConstants.Response.StatusCode] ?? responseMessage.ApplicationProperties.Map[AmqpClientConstants.ResponseStatusCode];
|
||||
if (statusCodeValue is int && Enum.IsDefined(typeof(AmqpResponseStatusCode), statusCodeValue))
|
||||
{
|
||||
responseStatusCode = (AmqpResponseStatusCode)statusCodeValue;
|
||||
}
|
||||
}
|
||||
|
||||
return responseStatusCode;
|
||||
}
|
||||
|
||||
public static Exception ToMessagingContract(Error error, bool connectionError = false)
|
||||
{
|
||||
if (error == null)
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Azure.ServiceBus.Amqp
|
||||
{
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.Amqp;
|
||||
using Microsoft.Azure.Amqp.Framing;
|
||||
|
||||
public abstract class AmqpLinkCreator
|
||||
{
|
||||
readonly string entityPath;
|
||||
readonly ServiceBusConnection serviceBusConnection;
|
||||
readonly string[] requiredClaims;
|
||||
readonly ICbsTokenProvider cbsTokenProvider;
|
||||
readonly AmqpLinkSettings amqpLinkSettings;
|
||||
|
||||
protected AmqpLinkCreator(string entityPath, ServiceBusConnection serviceBusConnection, string[] requiredClaims, ICbsTokenProvider cbsTokenProvider, AmqpLinkSettings amqpLinkSettings)
|
||||
{
|
||||
this.entityPath = entityPath;
|
||||
this.serviceBusConnection = serviceBusConnection;
|
||||
this.requiredClaims = requiredClaims;
|
||||
this.cbsTokenProvider = cbsTokenProvider;
|
||||
this.amqpLinkSettings = amqpLinkSettings;
|
||||
}
|
||||
|
||||
public async Task<AmqpObject> CreateAndOpenAmqpLinkAsync()
|
||||
{
|
||||
TimeoutHelper timeoutHelper = new TimeoutHelper(this.serviceBusConnection.OperationTimeout);
|
||||
AmqpConnection connection = await this.serviceBusConnection.ConnectionManager.GetOrCreateAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false);
|
||||
|
||||
// Authenticate over CBS
|
||||
AmqpCbsLink cbsLink = connection.Extensions.Find<AmqpCbsLink>();
|
||||
Uri address = new Uri(this.serviceBusConnection.Endpoint, this.entityPath);
|
||||
string audience = address.AbsoluteUri;
|
||||
string resource = address.AbsoluteUri;
|
||||
await cbsLink.SendTokenAsync(this.cbsTokenProvider, address, audience, resource, this.requiredClaims, timeoutHelper.RemainingTime()).ConfigureAwait(false);
|
||||
|
||||
AmqpSession session = null;
|
||||
try
|
||||
{
|
||||
// Create our Session
|
||||
AmqpSessionSettings sessionSettings = new AmqpSessionSettings { Properties = new Fields() };
|
||||
session = connection.CreateSession(sessionSettings);
|
||||
await session.OpenAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false);
|
||||
|
||||
// Create our Link
|
||||
AmqpObject link = this.OnCreateAmqpLink(connection, this.amqpLinkSettings, session);
|
||||
await link.OpenAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false);
|
||||
return link;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
session?.Abort();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract AmqpObject OnCreateAmqpLink(AmqpConnection connection, AmqpLinkSettings linkSettings, AmqpSession amqpSession);
|
||||
}
|
||||
}
|
|
@ -42,7 +42,7 @@ namespace Microsoft.Azure.ServiceBus.Amqp
|
|||
}
|
||||
else
|
||||
{
|
||||
var dataList = new List<Data>();
|
||||
List<Data> dataList = new List<Data>();
|
||||
foreach (BrokeredMessage brokeredMessage in brokeredMessages)
|
||||
{
|
||||
AmqpMessage amqpMessageItem = AmqpMessageConverter.ClientGetMessage(brokeredMessage);
|
||||
|
@ -52,7 +52,7 @@ namespace Microsoft.Azure.ServiceBus.Amqp
|
|||
dataList.Add(new Data() { Value = value });
|
||||
}
|
||||
|
||||
var firstBrokeredMessage = brokeredMessages.First();
|
||||
BrokeredMessage firstBrokeredMessage = brokeredMessages.First();
|
||||
|
||||
amqpMessage = AmqpMessage.Create(dataList);
|
||||
amqpMessage.MessageFormat = AmqpConstants.AmqpBatchedMessageFormat;
|
||||
|
@ -336,7 +336,7 @@ namespace Microsoft.Azure.ServiceBus.Amqp
|
|||
amqpObject = null;
|
||||
if (netObject == null)
|
||||
{
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (SerializationUtilities.GetTypeId(netObject))
|
||||
|
@ -413,7 +413,7 @@ namespace Microsoft.Azure.ServiceBus.Amqp
|
|||
netObject = null;
|
||||
if (amqpObject == null)
|
||||
{
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (SerializationUtilities.GetTypeId(amqpObject))
|
||||
|
|
|
@ -8,43 +8,115 @@ namespace Microsoft.Azure.ServiceBus.Amqp
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.Amqp;
|
||||
using Microsoft.Azure.Amqp.Encoding;
|
||||
using Microsoft.Azure.Amqp.Framing;
|
||||
using Microsoft.Azure.Messaging.Amqp;
|
||||
using Microsoft.Azure.ServiceBus.Primitives;
|
||||
|
||||
sealed class AmqpMessageReceiver : MessageReceiver
|
||||
{
|
||||
public static readonly TimeSpan DefaultBatchFlushInterval = TimeSpan.FromMilliseconds(20);
|
||||
|
||||
public AmqpMessageReceiver(QueueClient queueClient)
|
||||
: base(queueClient.Mode)
|
||||
readonly ConcurrentExpiringSet<Guid> requestResponseLockedMessages;
|
||||
readonly string entityName;
|
||||
readonly bool isSessionReceiver;
|
||||
|
||||
string sessionId;
|
||||
DateTime lockedUntilUtc;
|
||||
|
||||
public AmqpMessageReceiver(string entityName, MessagingEntityType entityType, ReceiveMode mode, int prefetchCount, ServiceBusConnection serviceBusConnection, ICbsTokenProvider cbsTokenProvider)
|
||||
: this(entityName, entityType, mode, prefetchCount, serviceBusConnection, cbsTokenProvider, null)
|
||||
{
|
||||
this.QueueClient = queueClient;
|
||||
this.Path = queueClient.QueueName;
|
||||
}
|
||||
|
||||
public AmqpMessageReceiver(string entityName, MessagingEntityType entityType, ReceiveMode mode, int prefetchCount, ServiceBusConnection serviceBusConnection, ICbsTokenProvider cbsTokenProvider, string sessionId, bool isSessionReceiver = false)
|
||||
: base(mode, serviceBusConnection.OperationTimeout)
|
||||
{
|
||||
this.entityName = entityName;
|
||||
this.EntityType = entityType;
|
||||
this.ServiceBusConnection = serviceBusConnection;
|
||||
this.CbsTokenProvider = cbsTokenProvider;
|
||||
this.sessionId = sessionId;
|
||||
this.isSessionReceiver = isSessionReceiver;
|
||||
this.ReceiveLinkManager = new FaultTolerantAmqpObject<ReceivingAmqpLink>(this.CreateLinkAsync, this.CloseSession);
|
||||
this.PrefetchCount = queueClient.PrefetchCount;
|
||||
this.RequestResponseLinkManager = new FaultTolerantAmqpObject<RequestResponseAmqpLink>(this.CreateRequestResponseLinkAsync, this.CloseRequestResponseSession);
|
||||
this.requestResponseLockedMessages = new ConcurrentExpiringSet<Guid>();
|
||||
this.PrefetchCount = prefetchCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get Prefetch Count configured on the Receiver.
|
||||
/// </summary>
|
||||
/// <value>The upper limit of events this receiver will actively receive regardless of whether a receive operation is pending.</value>
|
||||
public int PrefetchCount { get; set; }
|
||||
public override int PrefetchCount
|
||||
{
|
||||
get
|
||||
{
|
||||
return base.PrefetchCount;
|
||||
}
|
||||
|
||||
QueueClient QueueClient { get; }
|
||||
set
|
||||
{
|
||||
if (value != base.PrefetchCount)
|
||||
{
|
||||
ReceivingAmqpLink link;
|
||||
if (this.ReceiveLinkManager.TryGetOpenedObject(out link))
|
||||
{
|
||||
link.SetTotalLinkCredit((uint)value, true, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string Path { get; }
|
||||
public override string Path
|
||||
{
|
||||
get { return this.entityName; }
|
||||
}
|
||||
|
||||
public DateTime LockedUntilUtc
|
||||
{
|
||||
get { return this.lockedUntilUtc; }
|
||||
}
|
||||
|
||||
public string SessionId
|
||||
{
|
||||
get { return this.sessionId; }
|
||||
}
|
||||
|
||||
ServiceBusConnection ServiceBusConnection { get; }
|
||||
|
||||
ICbsTokenProvider CbsTokenProvider { get; }
|
||||
|
||||
FaultTolerantAmqpObject<ReceivingAmqpLink> ReceiveLinkManager { get; }
|
||||
|
||||
public override Task CloseAsync()
|
||||
FaultTolerantAmqpObject<RequestResponseAmqpLink> RequestResponseLinkManager { get; }
|
||||
|
||||
public override async Task CloseAsync()
|
||||
{
|
||||
return this.ReceiveLinkManager.CloseAsync();
|
||||
await this.ReceiveLinkManager.CloseAsync().ConfigureAwait(false);
|
||||
await this.RequestResponseLinkManager.CloseAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal async Task GetSessionReceiverLinkAsync()
|
||||
{
|
||||
TimeoutHelper timeoutHelper = new TimeoutHelper(this.OperationTimeout, true);
|
||||
ReceivingAmqpLink receivingAmqpLink = await this.ReceiveLinkManager.GetOrCreateAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false);
|
||||
Source source = (Source)receivingAmqpLink.Settings.Source;
|
||||
if (!source.FilterSet.TryGetValue<string>(AmqpClientConstants.SessionFilterName, out this.sessionId))
|
||||
{
|
||||
receivingAmqpLink.Session.SafeClose();
|
||||
throw new ServiceBusException(false, Resources.AmqpFieldSessionId);
|
||||
}
|
||||
|
||||
long lockedUntilUtcTicks;
|
||||
this.lockedUntilUtc = receivingAmqpLink.Settings.Properties.TryGetValue(AmqpClientConstants.LockedUntilUtc, out lockedUntilUtcTicks) ? new DateTime(lockedUntilUtcTicks, DateTimeKind.Utc) : DateTime.MinValue;
|
||||
}
|
||||
|
||||
protected override async Task<IList<BrokeredMessage>> OnReceiveAsync(int maxMessageCount)
|
||||
{
|
||||
try
|
||||
{
|
||||
var timeoutHelper = new TimeoutHelper(this.QueueClient.ConnectionSettings.OperationTimeout, true);
|
||||
TimeoutHelper timeoutHelper = new TimeoutHelper(this.OperationTimeout, true);
|
||||
ReceivingAmqpLink receiveLink = await this.ReceiveLinkManager.GetOrCreateAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false);
|
||||
IEnumerable<AmqpMessage> amqpMessages = null;
|
||||
bool hasMessages = await Task.Factory.FromAsync(
|
||||
|
@ -67,7 +139,7 @@ namespace Microsoft.Azure.ServiceBus.Amqp
|
|||
brokeredMessages = new List<BrokeredMessage>();
|
||||
}
|
||||
|
||||
if (this.QueueClient.Mode == ReceiveMode.ReceiveAndDelete)
|
||||
if (this.ReceiveMode == ReceiveMode.ReceiveAndDelete)
|
||||
{
|
||||
receiveLink.DisposeDelivery(amqpMessage, true, AmqpConstants.AcceptedOutcome);
|
||||
}
|
||||
|
@ -88,11 +160,57 @@ namespace Microsoft.Azure.ServiceBus.Amqp
|
|||
}
|
||||
}
|
||||
|
||||
protected override async Task<IList<BrokeredMessage>> OnReceiveBySequenceNumberAsync(IEnumerable<long> sequenceNumbers)
|
||||
{
|
||||
List<BrokeredMessage> messages = new List<BrokeredMessage>();
|
||||
try
|
||||
{
|
||||
AmqpRequestMessage requestMessage = AmqpRequestMessage.CreateRequest(ManagementConstants.Operations.ReceiveBySequenceNumberOperation, this.OperationTimeout, null);
|
||||
requestMessage.Map[ManagementConstants.Properties.SequenceNumbers] = sequenceNumbers.ToArray();
|
||||
requestMessage.Map[ManagementConstants.Properties.ReceiverSettleMode] = (uint)(this.ReceiveMode == ReceiveMode.ReceiveAndDelete ? 0 : 1);
|
||||
|
||||
AmqpResponseMessage response = await this.ExecuteRequestResponseAsync(requestMessage).ConfigureAwait(false);
|
||||
|
||||
if (response.StatusCode == AmqpResponseStatusCode.OK)
|
||||
{
|
||||
IEnumerable<AmqpMap> amqpMapList = response.GetListValue<AmqpMap>(ManagementConstants.Properties.Messages);
|
||||
foreach (AmqpMap entry in amqpMapList)
|
||||
{
|
||||
ArraySegment<byte> payload = (ArraySegment<byte>)entry[ManagementConstants.Properties.Message];
|
||||
AmqpMessage amqpMessage = AmqpMessage.CreateAmqpStreamMessage(new BufferListStream(new[] { payload }), true);
|
||||
BrokeredMessage brokeredMessage = AmqpMessageConverter.ClientGetMessage(amqpMessage);
|
||||
brokeredMessage.Receiver = this; // Associate the Message with this Receiver.
|
||||
Guid lockToken;
|
||||
if (entry.TryGetValue(ManagementConstants.Properties.LockToken, out lockToken))
|
||||
{
|
||||
brokeredMessage.LockToken = lockToken;
|
||||
this.requestResponseLockedMessages.AddOrUpdate(lockToken, brokeredMessage.LockedUntilUtc);
|
||||
}
|
||||
|
||||
messages.Add(brokeredMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (AmqpException amqpException)
|
||||
{
|
||||
throw AmqpExceptionHelper.ToMessagingContract(amqpException.Error);
|
||||
}
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
protected override async Task OnCompleteAsync(IEnumerable<Guid> lockTokens)
|
||||
{
|
||||
try
|
||||
{
|
||||
await this.DisposeMessagesAsync(lockTokens, AmqpConstants.AcceptedOutcome).ConfigureAwait(false);
|
||||
if (lockTokens.Any((lt) => this.requestResponseLockedMessages.Contains(lt)))
|
||||
{
|
||||
await this.DisposeMessageRequestResponseAsync(lockTokens, DispositionStatus.Completed).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await this.DisposeMessagesAsync(lockTokens, AmqpConstants.AcceptedOutcome).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (AmqpException amqpException)
|
||||
{
|
||||
|
@ -104,7 +222,14 @@ namespace Microsoft.Azure.ServiceBus.Amqp
|
|||
{
|
||||
try
|
||||
{
|
||||
await this.DisposeMessagesAsync(lockTokens, new Modified()).ConfigureAwait(false);
|
||||
if (lockTokens.Any((lt) => this.requestResponseLockedMessages.Contains(lt)))
|
||||
{
|
||||
await this.DisposeMessageRequestResponseAsync(lockTokens, DispositionStatus.Abandoned).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await this.DisposeMessagesAsync(lockTokens, new Modified()).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (AmqpException amqpException)
|
||||
{
|
||||
|
@ -116,7 +241,14 @@ namespace Microsoft.Azure.ServiceBus.Amqp
|
|||
{
|
||||
try
|
||||
{
|
||||
await this.DisposeMessagesAsync(lockTokens, new Modified() { UndeliverableHere = true }).ConfigureAwait(false);
|
||||
if (lockTokens.Any((lt) => this.requestResponseLockedMessages.Contains(lt)))
|
||||
{
|
||||
await this.DisposeMessageRequestResponseAsync(lockTokens, DispositionStatus.Defered).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await this.DisposeMessagesAsync(lockTokens, new Modified() { UndeliverableHere = true }).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (AmqpException amqpException)
|
||||
{
|
||||
|
@ -128,7 +260,14 @@ namespace Microsoft.Azure.ServiceBus.Amqp
|
|||
{
|
||||
try
|
||||
{
|
||||
await this.DisposeMessagesAsync(lockTokens, AmqpConstants.RejectedOutcome).ConfigureAwait(false);
|
||||
if (lockTokens.Any((lt) => this.requestResponseLockedMessages.Contains(lt)))
|
||||
{
|
||||
await this.DisposeMessageRequestResponseAsync(lockTokens, DispositionStatus.Suspended).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await this.DisposeMessagesAsync(lockTokens, AmqpConstants.RejectedOutcome).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (AmqpException amqpException)
|
||||
{
|
||||
|
@ -136,9 +275,49 @@ namespace Microsoft.Azure.ServiceBus.Amqp
|
|||
}
|
||||
}
|
||||
|
||||
protected override async Task<DateTime> OnRenewLockAsync(Guid lockToken)
|
||||
{
|
||||
DateTime lockedUntilUtc = DateTime.MinValue;
|
||||
try
|
||||
{
|
||||
// Create an AmqpRequest Message to renew lock
|
||||
AmqpRequestMessage requestMessage = AmqpRequestMessage.CreateRequest(ManagementConstants.Operations.RenewLockOperation, this.OperationTimeout, null);
|
||||
requestMessage.Map[ManagementConstants.Properties.LockTokens] = new Guid[] { lockToken };
|
||||
|
||||
AmqpResponseMessage response = await this.ExecuteRequestResponseAsync(requestMessage).ConfigureAwait(false);
|
||||
|
||||
if (response.StatusCode == AmqpResponseStatusCode.OK)
|
||||
{
|
||||
IEnumerable<DateTime> lockedUntilUtcTimes = response.GetValue<IEnumerable<DateTime>>(ManagementConstants.Properties.Expirations);
|
||||
lockedUntilUtc = lockedUntilUtcTimes.First();
|
||||
}
|
||||
}
|
||||
catch (AmqpException amqpException)
|
||||
{
|
||||
throw AmqpExceptionHelper.ToMessagingContract(amqpException.Error);
|
||||
}
|
||||
|
||||
return lockedUntilUtc;
|
||||
}
|
||||
|
||||
protected override async Task<AmqpResponseMessage> OnExecuteRequestResponseAsync(AmqpRequestMessage amqpRequestMessage)
|
||||
{
|
||||
AmqpMessage amqpMessage = amqpRequestMessage.AmqpMessage;
|
||||
TimeoutHelper timeoutHelper = new TimeoutHelper(this.OperationTimeout, true);
|
||||
RequestResponseAmqpLink requestResponseAmqpLink = await this.RequestResponseLinkManager.GetOrCreateAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false);
|
||||
|
||||
AmqpMessage responseAmqpMessage = await Task.Factory.FromAsync(
|
||||
(c, s) => requestResponseAmqpLink.BeginRequest(amqpMessage, timeoutHelper.RemainingTime(), c, s),
|
||||
(a) => requestResponseAmqpLink.EndRequest(a),
|
||||
this).ConfigureAwait(false);
|
||||
|
||||
AmqpResponseMessage responseMessage = AmqpResponseMessage.CreateResponse(responseAmqpMessage);
|
||||
return responseMessage;
|
||||
}
|
||||
|
||||
async Task DisposeMessagesAsync(IEnumerable<Guid> lockTokens, Outcome outcome)
|
||||
{
|
||||
var timeoutHelper = new TimeoutHelper(this.QueueClient.ConnectionSettings.OperationTimeout, true);
|
||||
TimeoutHelper timeoutHelper = new TimeoutHelper(this.OperationTimeout, true);
|
||||
IList<ArraySegment<byte>> deliveryTags = this.ConvertLockTokensToDeliveryTags(lockTokens);
|
||||
|
||||
ReceivingAmqpLink receiveLink = await this.ReceiveLinkManager.GetOrCreateAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false);
|
||||
|
@ -155,6 +334,23 @@ namespace Microsoft.Azure.ServiceBus.Amqp
|
|||
await Task.WhenAll(disposeMessageTasks).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
async Task DisposeMessageRequestResponseAsync(IEnumerable<Guid> lockTokens, DispositionStatus dispositionStatus)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Create an AmqpRequest Message to update disposition
|
||||
AmqpRequestMessage requestMessage = AmqpRequestMessage.CreateRequest(ManagementConstants.Operations.UpdateDispositionOperation, this.OperationTimeout, null);
|
||||
requestMessage.Map[ManagementConstants.Properties.LockTokens] = lockTokens.ToArray();
|
||||
requestMessage.Map[ManagementConstants.Properties.DispositionStatus] = dispositionStatus.ToString().ToLowerInvariant();
|
||||
|
||||
await this.ExecuteRequestResponseAsync(requestMessage);
|
||||
}
|
||||
catch (AmqpException amqpException)
|
||||
{
|
||||
throw AmqpExceptionHelper.ToMessagingContract(amqpException.Error);
|
||||
}
|
||||
}
|
||||
|
||||
IList<ArraySegment<byte>> ConvertLockTokensToDeliveryTags(IEnumerable<Guid> lockTokens)
|
||||
{
|
||||
return lockTokens.Select(lockToken => new ArraySegment<byte>(lockToken.ToByteArray())).ToList();
|
||||
|
@ -162,60 +358,50 @@ namespace Microsoft.Azure.ServiceBus.Amqp
|
|||
|
||||
async Task<ReceivingAmqpLink> CreateLinkAsync(TimeSpan timeout)
|
||||
{
|
||||
var amqpQueueClient = (AmqpQueueClient)this.QueueClient;
|
||||
var connectionSettings = amqpQueueClient.ConnectionSettings;
|
||||
var timeoutHelper = new TimeoutHelper(connectionSettings.OperationTimeout);
|
||||
AmqpConnection connection = await amqpQueueClient.ConnectionManager.GetOrCreateAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false);
|
||||
|
||||
// Authenticate over CBS
|
||||
var cbsLink = connection.Extensions.Find<AmqpCbsLink>();
|
||||
|
||||
ICbsTokenProvider cbsTokenProvider = amqpQueueClient.CbsTokenProvider;
|
||||
Uri address = new Uri(connectionSettings.Endpoint, this.Path);
|
||||
string audience = address.AbsoluteUri;
|
||||
string resource = address.AbsoluteUri;
|
||||
var expiresAt = await cbsLink.SendTokenAsync(cbsTokenProvider, address, audience, resource, new[] { ClaimConstants.Listen }, timeoutHelper.RemainingTime()).ConfigureAwait(false);
|
||||
|
||||
AmqpSession session = null;
|
||||
bool succeeded = false;
|
||||
try
|
||||
FilterSet filterMap = null;
|
||||
if (this.isSessionReceiver)
|
||||
{
|
||||
// Create our Session
|
||||
var sessionSettings = new AmqpSessionSettings { Properties = new Fields() };
|
||||
session = connection.CreateSession(sessionSettings);
|
||||
await session.OpenAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false);
|
||||
|
||||
// Create our Link
|
||||
var linkSettings = new AmqpLinkSettings();
|
||||
linkSettings.Role = true;
|
||||
linkSettings.TotalLinkCredit = (uint)this.PrefetchCount;
|
||||
linkSettings.AutoSendFlow = this.PrefetchCount > 0;
|
||||
linkSettings.AddProperty(AmqpClientConstants.EntityTypeName, (int)MessagingEntityType.Queue);
|
||||
linkSettings.Source = new Source { Address = address.AbsolutePath };
|
||||
linkSettings.Target = new Target { Address = this.ClientId };
|
||||
linkSettings.SettleType = (this.QueueClient.Mode == ReceiveMode.PeekLock) ? SettleMode.SettleOnDispose : SettleMode.SettleOnSend;
|
||||
|
||||
var link = new ReceivingAmqpLink(linkSettings);
|
||||
linkSettings.LinkName = $"{amqpQueueClient.ContainerId};{connection.Identifier}:{session.Identifier}:{link.Identifier}";
|
||||
link.AttachTo(session);
|
||||
|
||||
await link.OpenAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false);
|
||||
succeeded = true;
|
||||
return link;
|
||||
filterMap = new FilterSet { { AmqpClientConstants.SessionFilterName, this.sessionId } };
|
||||
}
|
||||
finally
|
||||
|
||||
AmqpLinkSettings linkSettings = new AmqpLinkSettings
|
||||
{
|
||||
if (!succeeded)
|
||||
{
|
||||
// Cleanup any session (and thus link) in case of exception.
|
||||
session?.Abort();
|
||||
}
|
||||
}
|
||||
Role = true,
|
||||
TotalLinkCredit = (uint)this.PrefetchCount,
|
||||
AutoSendFlow = this.PrefetchCount > 0,
|
||||
Source = new Source { Address = this.Path, FilterSet = filterMap },
|
||||
SettleType = (this.ReceiveMode == ReceiveMode.PeekLock) ? SettleMode.SettleOnDispose : SettleMode.SettleOnSend
|
||||
};
|
||||
|
||||
linkSettings.AddProperty(AmqpClientConstants.EntityTypeName, (int)this.EntityType);
|
||||
linkSettings.AddProperty(AmqpClientConstants.TimeoutName, (uint)timeout.TotalMilliseconds);
|
||||
|
||||
AmqpSendReceiveLinkCreator sendReceiveLinkCreator = new AmqpSendReceiveLinkCreator(this.Path, this.ServiceBusConnection, new[] { ClaimConstants.Listen }, this.CbsTokenProvider, linkSettings);
|
||||
ReceivingAmqpLink receivingAmqpLink = (ReceivingAmqpLink)await sendReceiveLinkCreator.CreateAndOpenAmqpLinkAsync().ConfigureAwait(false);
|
||||
|
||||
return receivingAmqpLink;
|
||||
}
|
||||
|
||||
// TODO: Consolidate the link creation paths
|
||||
async Task<RequestResponseAmqpLink> CreateRequestResponseLinkAsync(TimeSpan timeout)
|
||||
{
|
||||
string entityPath = this.Path + '/' + AmqpClientConstants.ManagementAddress;
|
||||
AmqpLinkSettings linkSettings = new AmqpLinkSettings();
|
||||
linkSettings.AddProperty(AmqpClientConstants.EntityTypeName, AmqpClientConstants.EntityTypeManagement);
|
||||
|
||||
AmqpRequestResponseLinkCreator requestResponseLinkCreator = new AmqpRequestResponseLinkCreator(entityPath, this.ServiceBusConnection, new[] { ClaimConstants.Manage, ClaimConstants.Listen }, this.CbsTokenProvider, linkSettings);
|
||||
RequestResponseAmqpLink requestResponseAmqpLink = (RequestResponseAmqpLink)await requestResponseLinkCreator.CreateAndOpenAmqpLinkAsync().ConfigureAwait(false);
|
||||
return requestResponseAmqpLink;
|
||||
}
|
||||
|
||||
void CloseSession(ReceivingAmqpLink link)
|
||||
{
|
||||
link.Session.SafeClose();
|
||||
}
|
||||
|
||||
void CloseRequestResponseSession(RequestResponseAmqpLink requestResponseAmqpLink)
|
||||
{
|
||||
requestResponseAmqpLink.Session.SafeClose();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,22 +9,28 @@ namespace Microsoft.Azure.ServiceBus.Amqp
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.Amqp;
|
||||
using Microsoft.Azure.Amqp.Framing;
|
||||
using Microsoft.Azure.Messaging.Amqp;
|
||||
|
||||
sealed class AmqpMessageSender : MessageSender
|
||||
{
|
||||
int deliveryCount;
|
||||
|
||||
internal AmqpMessageSender(AmqpQueueClient queueClient)
|
||||
internal AmqpMessageSender(string entityName, MessagingEntityType entityType, ServiceBusConnection serviceBusConnection, ICbsTokenProvider cbsTokenProvider)
|
||||
: base(serviceBusConnection.OperationTimeout)
|
||||
{
|
||||
this.QueueClient = queueClient;
|
||||
this.Path = this.QueueClient.QueueName;
|
||||
this.Path = entityName;
|
||||
this.EntityType = entityType;
|
||||
this.ServiceBusConnection = serviceBusConnection;
|
||||
this.CbsTokenProvider = cbsTokenProvider;
|
||||
this.SendLinkManager = new FaultTolerantAmqpObject<SendingAmqpLink>(this.CreateLinkAsync, this.CloseSession);
|
||||
}
|
||||
|
||||
QueueClient QueueClient { get; }
|
||||
|
||||
string Path { get; }
|
||||
|
||||
ServiceBusConnection ServiceBusConnection { get; }
|
||||
|
||||
ICbsTokenProvider CbsTokenProvider { get; }
|
||||
|
||||
FaultTolerantAmqpObject<SendingAmqpLink> SendLinkManager { get; }
|
||||
|
||||
public override Task CloseAsync()
|
||||
|
@ -34,10 +40,10 @@ namespace Microsoft.Azure.ServiceBus.Amqp
|
|||
|
||||
protected override async Task OnSendAsync(IEnumerable<BrokeredMessage> brokeredMessages)
|
||||
{
|
||||
var timeoutHelper = new TimeoutHelper(this.QueueClient.ConnectionSettings.OperationTimeout, true);
|
||||
TimeoutHelper timeoutHelper = new TimeoutHelper(this.OperationTimeout, true);
|
||||
using (AmqpMessage amqpMessage = AmqpMessageConverter.BrokeredMessagesToAmqpMessage(brokeredMessages, true))
|
||||
{
|
||||
var amqpLink = await this.SendLinkManager.GetOrCreateAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false);
|
||||
SendingAmqpLink amqpLink = await this.SendLinkManager.GetOrCreateAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false);
|
||||
if (amqpLink.Settings.MaxMessageSize.HasValue)
|
||||
{
|
||||
ulong size = (ulong)amqpMessage.SerializedMessageSize;
|
||||
|
@ -67,51 +73,18 @@ namespace Microsoft.Azure.ServiceBus.Amqp
|
|||
|
||||
async Task<SendingAmqpLink> CreateLinkAsync(TimeSpan timeout)
|
||||
{
|
||||
var amqpQueueClient = (AmqpQueueClient)this.QueueClient;
|
||||
var connectionSettings = amqpQueueClient.ConnectionSettings;
|
||||
var timeoutHelper = new TimeoutHelper(connectionSettings.OperationTimeout);
|
||||
AmqpConnection connection = await amqpQueueClient.ConnectionManager.GetOrCreateAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false);
|
||||
|
||||
// Authenticate over CBS
|
||||
var cbsLink = connection.Extensions.Find<AmqpCbsLink>();
|
||||
|
||||
ICbsTokenProvider cbsTokenProvider = amqpQueueClient.CbsTokenProvider;
|
||||
Uri address = new Uri(connectionSettings.Endpoint, this.Path);
|
||||
string audience = address.AbsoluteUri;
|
||||
string resource = address.AbsoluteUri;
|
||||
var expiresAt = await cbsLink.SendTokenAsync(cbsTokenProvider, address, audience, resource, new[] { ClaimConstants.Send }, timeoutHelper.RemainingTime()).ConfigureAwait(false);
|
||||
|
||||
AmqpSession session = null;
|
||||
try
|
||||
AmqpLinkSettings linkSettings = new AmqpLinkSettings
|
||||
{
|
||||
// Create our Session
|
||||
var sessionSettings = new AmqpSessionSettings { Properties = new Fields() };
|
||||
////sessionSettings.Properties[AmqpClientConstants.BatchFlushIntervalName] = (uint)connectionSettings.BatchFlushInterval.TotalMilliseconds;
|
||||
session = connection.CreateSession(sessionSettings);
|
||||
await session.OpenAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false);
|
||||
Role = false,
|
||||
InitialDeliveryCount = 0,
|
||||
Target = new Target { Address = this.Path },
|
||||
Source = new Source { Address = this.ClientId },
|
||||
};
|
||||
linkSettings.AddProperty(AmqpClientConstants.EntityTypeName, (int)this.EntityType);
|
||||
|
||||
// Create our Link
|
||||
var linkSettings = new AmqpLinkSettings();
|
||||
linkSettings.AddProperty(AmqpClientConstants.TimeoutName, (uint)timeoutHelper.RemainingTime().TotalMilliseconds);
|
||||
linkSettings.AddProperty(AmqpClientConstants.EntityTypeName, (int)MessagingEntityType.Queue);
|
||||
linkSettings.Role = false;
|
||||
linkSettings.InitialDeliveryCount = 0;
|
||||
linkSettings.Target = new Target { Address = address.AbsolutePath };
|
||||
linkSettings.Source = new Source { Address = this.ClientId };
|
||||
|
||||
var link = new SendingAmqpLink(linkSettings);
|
||||
linkSettings.LinkName = $"{amqpQueueClient.ContainerId};{connection.Identifier}:{session.Identifier}:{link.Identifier}";
|
||||
link.AttachTo(session);
|
||||
|
||||
await link.OpenAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false);
|
||||
return link;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Cleanup any session (and thus link) in case of exception.
|
||||
session?.Abort();
|
||||
throw;
|
||||
}
|
||||
AmqpSendReceiveLinkCreator sendReceiveLinkCreator = new AmqpSendReceiveLinkCreator(this.Path, this.ServiceBusConnection, new[] { ClaimConstants.Send }, this.CbsTokenProvider, linkSettings);
|
||||
SendingAmqpLink sendingAmqpLink = (SendingAmqpLink)await sendReceiveLinkCreator.CreateAndOpenAmqpLinkAsync().ConfigureAwait(false);
|
||||
return sendingAmqpLink;
|
||||
}
|
||||
|
||||
void CloseSession(SendingAmqpLink link)
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Azure.ServiceBus.Amqp
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.Amqp;
|
||||
using Microsoft.Azure.Messaging.Amqp;
|
||||
|
||||
public class AmqpMessageSession : MessageSession
|
||||
{
|
||||
public AmqpMessageSession(string sessionId, DateTime lockedUntilUtc, MessageReceiver innerMessageReceiver)
|
||||
: base(innerMessageReceiver.ReceiveMode, sessionId, lockedUntilUtc, innerMessageReceiver)
|
||||
{
|
||||
}
|
||||
|
||||
protected override async Task<Stream> OnGetStateAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
AmqpRequestMessage amqpRequestMessage = AmqpRequestMessage.CreateRequest(ManagementConstants.Operations.GetSessionStateOperation, this.OperationTimeout, null);
|
||||
amqpRequestMessage.Map[ManagementConstants.Properties.SessionId] = this.SessionId;
|
||||
|
||||
AmqpResponseMessage amqpResponseMessage = await this.InnerMessageReceiver.ExecuteRequestResponseAsync(amqpRequestMessage).ConfigureAwait(false);
|
||||
|
||||
Stream sessionState = null;
|
||||
if (amqpResponseMessage.StatusCode == AmqpResponseStatusCode.OK)
|
||||
{
|
||||
if (amqpResponseMessage.Map[ManagementConstants.Properties.SessionState] != null)
|
||||
{
|
||||
sessionState = new BufferListStream(new[] { amqpResponseMessage.GetValue<ArraySegment<byte>>(ManagementConstants.Properties.SessionState) });
|
||||
}
|
||||
}
|
||||
|
||||
return sessionState;
|
||||
}
|
||||
catch (AmqpException amqpException)
|
||||
{
|
||||
throw AmqpExceptionHelper.ToMessagingContract(amqpException.Error);
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnSetStateAsync(Stream sessionState)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (sessionState != null && sessionState.CanSeek && sessionState.Position != 0)
|
||||
{
|
||||
throw new InvalidOperationException("CannotSerializeSessionStateWithPartiallyConsumedStream");
|
||||
}
|
||||
|
||||
AmqpRequestMessage amqpRequestMessage = AmqpRequestMessage.CreateRequest(ManagementConstants.Operations.SetSessionStateOperation, this.OperationTimeout, null);
|
||||
amqpRequestMessage.Map[ManagementConstants.Properties.SessionId] = this.SessionId;
|
||||
|
||||
if (sessionState != null)
|
||||
{
|
||||
BufferListStream buffer = BufferListStream.Create(sessionState, AmqpConstants.SegmentSize);
|
||||
ArraySegment<byte> value = buffer.ReadBytes((int)buffer.Length);
|
||||
amqpRequestMessage.Map[ManagementConstants.Properties.SessionState] = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
amqpRequestMessage.Map[ManagementConstants.Properties.SessionState] = null;
|
||||
}
|
||||
|
||||
await this.InnerMessageReceiver.ExecuteRequestResponseAsync(amqpRequestMessage).ConfigureAwait(false);
|
||||
}
|
||||
catch (AmqpException amqpException)
|
||||
{
|
||||
throw AmqpExceptionHelper.ToMessagingContract(amqpException.Error);
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnRenewLockAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
AmqpRequestMessage amqpRequestMessage = AmqpRequestMessage.CreateRequest(ManagementConstants.Operations.RenewSessionLockOperation, this.OperationTimeout, null);
|
||||
amqpRequestMessage.Map[ManagementConstants.Properties.SessionId] = this.SessionId;
|
||||
|
||||
AmqpResponseMessage amqpResponseMessage = await this.InnerMessageReceiver.ExecuteRequestResponseAsync(amqpRequestMessage).ConfigureAwait(false);
|
||||
|
||||
if (amqpResponseMessage.StatusCode == AmqpResponseStatusCode.OK)
|
||||
{
|
||||
this.LockedUntilUtc = amqpResponseMessage.GetValue<DateTime>(ManagementConstants.Properties.Expiration);
|
||||
}
|
||||
}
|
||||
catch (AmqpException amqpException)
|
||||
{
|
||||
throw AmqpExceptionHelper.ToMessagingContract(amqpException.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,227 +3,53 @@
|
|||
|
||||
namespace Microsoft.Azure.ServiceBus.Amqp
|
||||
{
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Security;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.Amqp;
|
||||
using Microsoft.Azure.Amqp.Sasl;
|
||||
using Microsoft.Azure.Amqp.Transport;
|
||||
using Microsoft.Azure.ServiceBus.Primitives;
|
||||
|
||||
sealed class AmqpQueueClient : QueueClient
|
||||
{
|
||||
const string CbsSaslMechanismName = "MSSBCBS";
|
||||
|
||||
public AmqpQueueClient(ServiceBusConnectionSettings connectionSettings, ReceiveMode mode)
|
||||
: base(connectionSettings, mode)
|
||||
public AmqpQueueClient(ServiceBusConnection servicebusConnection, string entityPath, ReceiveMode mode)
|
||||
: base(servicebusConnection, entityPath, mode)
|
||||
{
|
||||
this.ContainerId = Guid.NewGuid().ToString("N");
|
||||
this.AmqpVersion = new Version(1, 0, 0, 0);
|
||||
this.MaxFrameSize = AmqpConstants.DefaultMaxFrameSize;
|
||||
var tokenProvider = connectionSettings.CreateTokenProvider();
|
||||
this.TokenProvider = tokenProvider;
|
||||
this.CbsTokenProvider = new TokenProviderAdapter(this);
|
||||
this.ConnectionManager = new FaultTolerantAmqpObject<AmqpConnection>(this.CreateConnectionAsync, this.CloseConnection);
|
||||
this.TokenProvider = TokenProvider.CreateSharedAccessSignatureTokenProvider(this.ServiceBusConnection.SasKeyName, this.ServiceBusConnection.SasKey);
|
||||
this.CbsTokenProvider = new TokenProviderAdapter(this.TokenProvider, this.ServiceBusConnection.OperationTimeout);
|
||||
}
|
||||
|
||||
internal ICbsTokenProvider CbsTokenProvider { get; }
|
||||
|
||||
internal FaultTolerantAmqpObject<AmqpConnection> ConnectionManager { get; }
|
||||
|
||||
internal string ContainerId { get; }
|
||||
|
||||
Version AmqpVersion { get; }
|
||||
|
||||
uint MaxFrameSize { get; }
|
||||
|
||||
TokenProvider TokenProvider { get; }
|
||||
|
||||
internal static AmqpSettings CreateAmqpSettings(
|
||||
Version amqpVersion,
|
||||
bool useSslStreamSecurity,
|
||||
bool hasTokenProvider,
|
||||
string sslHostName = null,
|
||||
bool useWebSockets = false,
|
||||
bool sslStreamUpgrade = false,
|
||||
NetworkCredential networkCredential = null,
|
||||
RemoteCertificateValidationCallback certificateValidationCallback = null,
|
||||
bool forceTokenProvider = true)
|
||||
protected override MessageSender OnCreateMessageSender()
|
||||
{
|
||||
var settings = new AmqpSettings();
|
||||
if (useSslStreamSecurity && !useWebSockets && sslStreamUpgrade)
|
||||
{
|
||||
var tlsSettings = new TlsTransportSettings();
|
||||
tlsSettings.CertificateValidationCallback = certificateValidationCallback;
|
||||
tlsSettings.TargetHost = sslHostName;
|
||||
|
||||
var tlsProvider = new TlsTransportProvider(tlsSettings);
|
||||
tlsProvider.Versions.Add(new AmqpVersion(amqpVersion));
|
||||
settings.TransportProviders.Add(tlsProvider);
|
||||
}
|
||||
|
||||
if (hasTokenProvider || networkCredential != null)
|
||||
{
|
||||
var saslProvider = new SaslTransportProvider();
|
||||
saslProvider.Versions.Add(new AmqpVersion(amqpVersion));
|
||||
settings.TransportProviders.Add(saslProvider);
|
||||
|
||||
if (forceTokenProvider)
|
||||
{
|
||||
saslProvider.AddHandler(new SaslAnonymousHandler(CbsSaslMechanismName));
|
||||
}
|
||||
else if (networkCredential != null)
|
||||
{
|
||||
var plainHandler = new SaslPlainHandler();
|
||||
plainHandler.AuthenticationIdentity = networkCredential.UserName;
|
||||
plainHandler.Password = networkCredential.Password;
|
||||
saslProvider.AddHandler(plainHandler);
|
||||
}
|
||||
else
|
||||
{
|
||||
// old client behavior: keep it for validation only
|
||||
saslProvider.AddHandler(new SaslExternalHandler());
|
||||
}
|
||||
}
|
||||
|
||||
var amqpProvider = new AmqpTransportProvider();
|
||||
amqpProvider.Versions.Add(new AmqpVersion(amqpVersion));
|
||||
settings.TransportProviders.Add(amqpProvider);
|
||||
|
||||
return settings;
|
||||
return new AmqpMessageSender(this.QueueName, MessagingEntityType.Queue, this.ServiceBusConnection, this.CbsTokenProvider);
|
||||
}
|
||||
|
||||
internal override MessageSender OnCreateMessageSender()
|
||||
protected override MessageReceiver OnCreateMessageReceiver()
|
||||
{
|
||||
return new AmqpMessageSender(this);
|
||||
return new AmqpMessageReceiver(this.QueueName, MessagingEntityType.Queue, this.Mode, this.ServiceBusConnection.PrefetchCount, this.ServiceBusConnection, this.CbsTokenProvider);
|
||||
}
|
||||
|
||||
internal override MessageReceiver OnCreateMessageReceiver()
|
||||
protected override async Task<MessageSession> OnAcceptMessageSessionAsync(string sessionId)
|
||||
{
|
||||
return new AmqpMessageReceiver(this);
|
||||
AmqpMessageReceiver receiver = new AmqpMessageReceiver(this.QueueName, MessagingEntityType.Queue, this.Mode, this.ServiceBusConnection.PrefetchCount, this.ServiceBusConnection, this.CbsTokenProvider, sessionId, true);
|
||||
try
|
||||
{
|
||||
await receiver.GetSessionReceiverLinkAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch (AmqpException exception)
|
||||
{
|
||||
// ToDo: Abort the Receiver here
|
||||
AmqpExceptionHelper.ToMessagingContract(exception.Error, false);
|
||||
}
|
||||
MessageSession session = new AmqpMessageSession(receiver.SessionId, receiver.LockedUntilUtc, receiver);
|
||||
return session;
|
||||
}
|
||||
|
||||
protected override Task OnCloseAsync()
|
||||
{
|
||||
// Closing the Connection will also close all Links associated with it.
|
||||
return this.ConnectionManager.CloseAsync();
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage(
|
||||
"StyleCop.CSharp.NamingRules",
|
||||
"SA1305:FieldNamesMustNotUseHungarianNotation",
|
||||
Justification = "tpSettings is a local variable.")]
|
||||
static TransportSettings CreateTcpTransportSettings(
|
||||
string networkHost,
|
||||
string hostName,
|
||||
int port,
|
||||
bool useSslStreamSecurity,
|
||||
bool sslStreamUpgrade = false,
|
||||
string sslHostName = null,
|
||||
X509Certificate2 certificate = null,
|
||||
RemoteCertificateValidationCallback certificateValidationCallback = null)
|
||||
{
|
||||
TcpTransportSettings tcpSettings = new TcpTransportSettings
|
||||
{
|
||||
Host = networkHost,
|
||||
Port = port < 0 ? AmqpConstants.DefaultSecurePort : port,
|
||||
ReceiveBufferSize = AmqpConstants.TransportBufferSize,
|
||||
SendBufferSize = AmqpConstants.TransportBufferSize
|
||||
};
|
||||
|
||||
TransportSettings tpSettings = tcpSettings;
|
||||
if (useSslStreamSecurity && !sslStreamUpgrade)
|
||||
{
|
||||
TlsTransportSettings tlsSettings = new TlsTransportSettings(tcpSettings)
|
||||
{
|
||||
TargetHost = sslHostName ?? hostName,
|
||||
Certificate = certificate,
|
||||
CertificateValidationCallback = certificateValidationCallback
|
||||
};
|
||||
tpSettings = tlsSettings;
|
||||
}
|
||||
|
||||
return tpSettings;
|
||||
}
|
||||
|
||||
static AmqpConnectionSettings CreateAmqpConnectionSettings(uint maxFrameSize, string containerId, string hostName)
|
||||
{
|
||||
var connectionSettings = new AmqpConnectionSettings
|
||||
{
|
||||
MaxFrameSize = maxFrameSize,
|
||||
ContainerId = containerId,
|
||||
HostName = hostName
|
||||
};
|
||||
return connectionSettings;
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage(
|
||||
"StyleCop.CSharp.NamingRules",
|
||||
"SA1305:FieldNamesMustNotUseHungarianNotation",
|
||||
Justification = "tpSettings is a local variable.")]
|
||||
async Task<AmqpConnection> CreateConnectionAsync(TimeSpan timeout)
|
||||
{
|
||||
string hostName = this.ConnectionSettings.Endpoint.Host;
|
||||
string networkHost = this.ConnectionSettings.Endpoint.Host;
|
||||
int port = this.ConnectionSettings.Endpoint.Port;
|
||||
|
||||
var timeoutHelper = new TimeoutHelper(timeout);
|
||||
var amqpSettings = CreateAmqpSettings(
|
||||
amqpVersion: this.AmqpVersion,
|
||||
useSslStreamSecurity: true,
|
||||
hasTokenProvider: true);
|
||||
|
||||
TransportSettings tpSettings = CreateTcpTransportSettings(
|
||||
networkHost: networkHost,
|
||||
hostName: hostName,
|
||||
port: port,
|
||||
useSslStreamSecurity: true);
|
||||
|
||||
var initiator = new AmqpTransportInitiator(amqpSettings, tpSettings);
|
||||
var transport = await initiator.ConnectTaskAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false);
|
||||
|
||||
var connectionSettings = CreateAmqpConnectionSettings(this.MaxFrameSize, this.ContainerId, hostName);
|
||||
var connection = new AmqpConnection(transport, amqpSettings, connectionSettings);
|
||||
await connection.OpenAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false);
|
||||
|
||||
// Always create the CBS Link + Session
|
||||
var cbsLink = new AmqpCbsLink(connection);
|
||||
if (connection.Extensions.Find<AmqpCbsLink>() == null)
|
||||
{
|
||||
connection.Extensions.Add(cbsLink);
|
||||
}
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
void CloseConnection(AmqpConnection connection)
|
||||
{
|
||||
connection.SafeClose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides an adapter from TokenProvider to ICbsTokenProvider for AMQP CBS usage.
|
||||
/// </summary>
|
||||
sealed class TokenProviderAdapter : ICbsTokenProvider
|
||||
{
|
||||
readonly AmqpQueueClient queueClient;
|
||||
|
||||
public TokenProviderAdapter(AmqpQueueClient queueClient)
|
||||
{
|
||||
Fx.Assert(queueClient != null, "tokenProvider cannot be null");
|
||||
this.queueClient = queueClient;
|
||||
}
|
||||
|
||||
public async Task<CbsToken> GetTokenAsync(Uri namespaceAddress, string appliesTo, string[] requiredClaims)
|
||||
{
|
||||
string claim = requiredClaims?.FirstOrDefault();
|
||||
var tokenProvider = this.queueClient.TokenProvider;
|
||||
var timeout = this.queueClient.ConnectionSettings.OperationTimeout;
|
||||
var token = await tokenProvider.GetTokenAsync(appliesTo, claim, timeout).ConfigureAwait(false);
|
||||
return new CbsToken(token.TokenValue, CbsConstants.ServiceBusSasTokenType, token.ExpiresAtUtc);
|
||||
}
|
||||
return this.ServiceBusConnection.CloseAsync();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Azure.ServiceBus.Amqp
|
||||
{
|
||||
using System;
|
||||
using Azure.Amqp;
|
||||
using Azure.Amqp.Encoding;
|
||||
using Azure.Amqp.Framing;
|
||||
using Microsoft.Azure.Messaging.Amqp;
|
||||
|
||||
public sealed class AmqpRequestMessage
|
||||
{
|
||||
readonly AmqpMessage requestMessage;
|
||||
|
||||
AmqpRequestMessage(string operation, TimeSpan timeout, string trackingId)
|
||||
{
|
||||
this.Map = new AmqpMap();
|
||||
this.requestMessage = AmqpMessage.Create(new AmqpValue() { Value = this.Map });
|
||||
this.requestMessage.ApplicationProperties.Map[ManagementConstants.Request.Operation] = operation;
|
||||
this.requestMessage.ApplicationProperties.Map[ManagementConstants.Properties.ServerTimeout] = (uint)timeout.TotalMilliseconds;
|
||||
this.requestMessage.ApplicationProperties.Map[ManagementConstants.Properties.TrackingId] = trackingId ?? Guid.NewGuid().ToString();
|
||||
}
|
||||
|
||||
public AmqpMessage AmqpMessage
|
||||
{
|
||||
get { return this.requestMessage; }
|
||||
}
|
||||
|
||||
public AmqpMap Map { get; }
|
||||
|
||||
public static AmqpRequestMessage CreateRequest(string operation, TimeSpan timeout, string trackingId)
|
||||
{
|
||||
return new AmqpRequestMessage(operation, timeout, trackingId);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Azure.ServiceBus.Amqp
|
||||
{
|
||||
using Microsoft.Azure.Amqp;
|
||||
|
||||
public class AmqpRequestResponseLinkCreator : AmqpLinkCreator
|
||||
{
|
||||
readonly string entityPath;
|
||||
|
||||
public AmqpRequestResponseLinkCreator(string entityPath, ServiceBusConnection serviceBusConnection, string[] requiredClaims, ICbsTokenProvider cbsTokenProvider, AmqpLinkSettings linkSettings)
|
||||
: base(entityPath, serviceBusConnection, requiredClaims, cbsTokenProvider, linkSettings)
|
||||
{
|
||||
this.entityPath = entityPath;
|
||||
}
|
||||
|
||||
protected override AmqpObject OnCreateAmqpLink(AmqpConnection connection, AmqpLinkSettings linkSettings, AmqpSession amqpSession)
|
||||
{
|
||||
AmqpObject link = new RequestResponseAmqpLink(AmqpClientConstants.EntityTypeManagement, amqpSession, this.entityPath, linkSettings.Properties);
|
||||
linkSettings.LinkName = $"{connection.Settings.ContainerId};{connection.Identifier}:{amqpSession.Identifier}:{link.Identifier}";
|
||||
return link;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Azure.ServiceBus.Amqp
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Azure.Amqp;
|
||||
using Azure.Amqp.Encoding;
|
||||
using Azure.Amqp.Framing;
|
||||
using Microsoft.Azure.Messaging.Amqp;
|
||||
|
||||
public sealed class AmqpResponseMessage
|
||||
{
|
||||
readonly AmqpMessage responseMessage;
|
||||
|
||||
AmqpResponseMessage(AmqpMessage responseMessage)
|
||||
{
|
||||
this.responseMessage = responseMessage;
|
||||
this.StatusCode = this.responseMessage.GetResponseStatusCode();
|
||||
string trackingId;
|
||||
if (this.responseMessage.ApplicationProperties.Map.TryGetValue(ManagementConstants.Properties.TrackingId, out trackingId))
|
||||
{
|
||||
this.TrackingId = trackingId;
|
||||
}
|
||||
|
||||
if (responseMessage.ValueBody != null)
|
||||
{
|
||||
this.Map = responseMessage.ValueBody.Value as AmqpMap;
|
||||
}
|
||||
}
|
||||
|
||||
public AmqpMessage AmqpMessage
|
||||
{
|
||||
get { return this.responseMessage; }
|
||||
}
|
||||
|
||||
public AmqpResponseStatusCode StatusCode { get; }
|
||||
|
||||
public string TrackingId { get; private set; }
|
||||
|
||||
public AmqpMap Map { get; }
|
||||
|
||||
public static AmqpResponseMessage CreateResponse(AmqpMessage response)
|
||||
{
|
||||
return new AmqpResponseMessage(response);
|
||||
}
|
||||
|
||||
public TValue GetValue<TValue>(MapKey key)
|
||||
{
|
||||
if (this.Map == null)
|
||||
{
|
||||
throw new ArgumentException(AmqpValue.Name);
|
||||
}
|
||||
|
||||
var valueObject = this.Map[key];
|
||||
if (valueObject == null)
|
||||
{
|
||||
throw new ArgumentException(key.ToString());
|
||||
}
|
||||
|
||||
if (!(valueObject is TValue))
|
||||
{
|
||||
throw new ArgumentException(key.ToString());
|
||||
}
|
||||
|
||||
return (TValue)this.Map[key];
|
||||
}
|
||||
|
||||
public IEnumerable<TValue> GetListValue<TValue>(MapKey key)
|
||||
{
|
||||
if (this.Map == null)
|
||||
{
|
||||
throw new ArgumentException(AmqpValue.Name);
|
||||
}
|
||||
|
||||
List<object> list = (List<object>)this.Map[key];
|
||||
|
||||
return list.Cast<TValue>();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Azure.ServiceBus.Amqp
|
||||
{
|
||||
using Microsoft.Azure.Amqp;
|
||||
|
||||
public class AmqpSendReceiveLinkCreator : AmqpLinkCreator
|
||||
{
|
||||
public AmqpSendReceiveLinkCreator(string entityPath, ServiceBusConnection serviceBusConnection, string[] requiredClaims, ICbsTokenProvider cbsTokenProvider, AmqpLinkSettings linkSettings)
|
||||
: base(entityPath, serviceBusConnection, requiredClaims, cbsTokenProvider, linkSettings)
|
||||
{
|
||||
}
|
||||
|
||||
protected override AmqpObject OnCreateAmqpLink(AmqpConnection connection, AmqpLinkSettings linkSettings, AmqpSession amqpSession)
|
||||
{
|
||||
AmqpObject link = (linkSettings.IsReceiver()) ? (AmqpObject)new ReceivingAmqpLink(linkSettings) : (AmqpObject)new SendingAmqpLink(linkSettings);
|
||||
linkSettings.LinkName = $"{connection.Settings.ContainerId};{connection.Identifier}:{amqpSession.Identifier}:{link.Identifier}";
|
||||
((AmqpLink)link).AttachTo(amqpSession);
|
||||
return link;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Azure.ServiceBus.Amqp
|
||||
{
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.Amqp;
|
||||
using Microsoft.Azure.ServiceBus.Primitives;
|
||||
|
||||
public class AmqpSubscriptionClient : SubscriptionClient
|
||||
{
|
||||
public AmqpSubscriptionClient(ServiceBusConnection servicebusConnection, string topicPath, string subscriptionName, ReceiveMode mode)
|
||||
: base(servicebusConnection, topicPath, subscriptionName, mode)
|
||||
{
|
||||
this.TokenProvider = TokenProvider.CreateSharedAccessSignatureTokenProvider(this.ServiceBusConnection.SasKeyName, this.ServiceBusConnection.SasKey);
|
||||
this.CbsTokenProvider = new TokenProviderAdapter(this.TokenProvider, this.ServiceBusConnection.OperationTimeout);
|
||||
}
|
||||
|
||||
internal ICbsTokenProvider CbsTokenProvider { get; }
|
||||
|
||||
TokenProvider TokenProvider { get; }
|
||||
|
||||
protected override MessageReceiver OnCreateMessageReceiver()
|
||||
{
|
||||
return new AmqpMessageReceiver(this.SubscriptionPath, MessagingEntityType.Subscriber, this.Mode, this.ServiceBusConnection.PrefetchCount, this.ServiceBusConnection, this.CbsTokenProvider);
|
||||
}
|
||||
|
||||
protected override async Task<MessageSession> OnAcceptMessageSessionAsync(string sessionId)
|
||||
{
|
||||
AmqpMessageReceiver receiver = new AmqpMessageReceiver(this.SubscriptionPath, MessagingEntityType.Subscriber, this.Mode, this.ServiceBusConnection.PrefetchCount, this.ServiceBusConnection, this.CbsTokenProvider, sessionId, true);
|
||||
try
|
||||
{
|
||||
await receiver.GetSessionReceiverLinkAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch (AmqpException exception)
|
||||
{
|
||||
// ToDo: Abort the Receiver here
|
||||
AmqpExceptionHelper.ToMessagingContract(exception.Error, false);
|
||||
}
|
||||
MessageSession session = new AmqpMessageSession(receiver.SessionId, receiver.LockedUntilUtc, receiver);
|
||||
return session;
|
||||
}
|
||||
|
||||
protected override Task OnCloseAsync()
|
||||
{
|
||||
// Closing the Connection will also close all Links associated with it.
|
||||
return this.ServiceBusConnection.CloseAsync();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Azure.ServiceBus.Amqp
|
||||
{
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.Amqp;
|
||||
using Microsoft.Azure.ServiceBus.Primitives;
|
||||
|
||||
sealed class AmqpTopicClient : TopicClient
|
||||
{
|
||||
public AmqpTopicClient(ServiceBusConnection servicebusConnection, string entityPath)
|
||||
: base(servicebusConnection, entityPath)
|
||||
{
|
||||
this.TokenProvider = TokenProvider.CreateSharedAccessSignatureTokenProvider(this.ServiceBusConnection.SasKeyName, this.ServiceBusConnection.SasKey);
|
||||
this.CbsTokenProvider = new TokenProviderAdapter(this.TokenProvider, this.ServiceBusConnection.OperationTimeout);
|
||||
}
|
||||
|
||||
internal ICbsTokenProvider CbsTokenProvider { get; }
|
||||
|
||||
TokenProvider TokenProvider { get; }
|
||||
|
||||
protected override MessageSender OnCreateMessageSender()
|
||||
{
|
||||
return new AmqpMessageSender(this.TopicName, MessagingEntityType.Topic, this.ServiceBusConnection, this.CbsTokenProvider);
|
||||
}
|
||||
|
||||
protected override Task OnCloseAsync()
|
||||
{
|
||||
// Closing the Connection will also close all Links associated with it.
|
||||
return this.ServiceBusConnection.CloseAsync();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Azure.Messaging.Amqp
|
||||
{
|
||||
using Microsoft.Azure.Amqp.Encoding;
|
||||
|
||||
static class ManagementConstants
|
||||
{
|
||||
public const string Microsoft = "com.microsoft";
|
||||
|
||||
public static class Request
|
||||
{
|
||||
public const string Operation = "operation";
|
||||
}
|
||||
|
||||
public static class Response
|
||||
{
|
||||
public const string StatusCode = "statusCode";
|
||||
public const string StatusDescription = "statusDescription";
|
||||
public const string ErrorCondition = "errorCondition";
|
||||
}
|
||||
|
||||
public static class Operations
|
||||
{
|
||||
public const string RenewLockOperation = Microsoft + ":renew-lock";
|
||||
public const string ReceiveBySequenceNumberOperation = Microsoft + ":receive-by-sequence-number";
|
||||
public const string UpdateDispositionOperation = Microsoft + ":update-disposition";
|
||||
public const string RenewSessionLockOperation = Microsoft + ":renew-session-lock";
|
||||
public const string SetSessionStateOperation = Microsoft + ":set-session-state";
|
||||
public const string GetSessionStateOperation = Microsoft + ":get-session-state";
|
||||
}
|
||||
|
||||
public static class Properties
|
||||
{
|
||||
public static readonly MapKey ServerTimeout = new MapKey(Microsoft + ":server-timeout");
|
||||
public static readonly MapKey TrackingId = new MapKey(Microsoft + ":tracking-id");
|
||||
|
||||
public static readonly MapKey SessionState = new MapKey("session-state");
|
||||
public static readonly MapKey LockToken = new MapKey("lock-token");
|
||||
public static readonly MapKey LockTokens = new MapKey("lock-tokens");
|
||||
public static readonly MapKey SequenceNumbers = new MapKey("sequence-numbers");
|
||||
public static readonly MapKey Expirations = new MapKey("expirations");
|
||||
public static readonly MapKey Expiration = new MapKey("expiration");
|
||||
public static readonly MapKey SessionId = new MapKey("session-id");
|
||||
|
||||
public static readonly MapKey ReceiverSettleMode = new MapKey("receiver-settle-mode");
|
||||
public static readonly MapKey Message = new MapKey("message");
|
||||
public static readonly MapKey Messages = new MapKey("messages");
|
||||
public static readonly MapKey DispositionStatus = new MapKey("disposition-status");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -59,7 +59,10 @@ namespace Microsoft.Azure.ServiceBus
|
|||
{
|
||||
try
|
||||
{
|
||||
this.messageId = messageIdGeneratorFunc();
|
||||
if (messageIdGeneratorFunc != null)
|
||||
{
|
||||
this.messageId = messageIdGeneratorFunc();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -919,11 +922,7 @@ namespace Microsoft.Azure.ServiceBus
|
|||
/// <exception cref="ArgumentNullException">Thrown if invoked with null.</exception>
|
||||
public static void SetMessageIdGenerator(Func<string> messageIdGenerator)
|
||||
{
|
||||
if (messageIdGenerator == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(messageIdGenerator));
|
||||
}
|
||||
messageIdGeneratorFunc = messageIdGenerator;
|
||||
messageIdGeneratorFunc = messageIdGenerator;
|
||||
}
|
||||
|
||||
/// <summary>Deserializes the brokered message body into an object of the specified type by using the
|
||||
|
@ -1010,9 +1009,9 @@ namespace Microsoft.Azure.ServiceBus
|
|||
return this.Receiver.AbandonAsync(new[] { this.LockToken });
|
||||
}
|
||||
|
||||
// Summary:
|
||||
// Asynchronously completes the receive operation of a message and indicates that
|
||||
// the message should be marked as processed and deleted.
|
||||
/// <summary>Asynchronously completes the receive operation of a message and
|
||||
/// indicates that the message should be marked as processed and deleted.</summary>
|
||||
/// <returns>The asynchronous result of the operation.</returns>
|
||||
public Task CompleteAsync()
|
||||
{
|
||||
this.ThrowIfDisposed();
|
||||
|
@ -1021,8 +1020,8 @@ namespace Microsoft.Azure.ServiceBus
|
|||
return this.Receiver.CompleteAsync(new[] { this.LockToken });
|
||||
}
|
||||
|
||||
// Summary:
|
||||
// Asynchronously moves the message to the dead letter queue.
|
||||
/// <summary>Asynchronously moves the message to the dead letter queue.</summary>
|
||||
/// <returns>The asynchronous result of the operation.</returns>
|
||||
public Task DeadLetterAsync()
|
||||
{
|
||||
this.ThrowIfDisposed();
|
||||
|
@ -1031,8 +1030,8 @@ namespace Microsoft.Azure.ServiceBus
|
|||
return this.Receiver.DeadLetterAsync(new[] { this.LockToken });
|
||||
}
|
||||
|
||||
// Summary:
|
||||
// Asynchronously indicates that the receiver wants to defer the processing for this message.
|
||||
/// <summary>Asynchronously indicates that the receiver wants to defer the processing for this message.</summary>
|
||||
/// <returns>The asynchronous result of the operation.</returns>
|
||||
public Task DeferAsync()
|
||||
{
|
||||
this.ThrowIfDisposed();
|
||||
|
@ -1041,6 +1040,16 @@ namespace Microsoft.Azure.ServiceBus
|
|||
return this.Receiver.DeferAsync(new[] { this.LockToken });
|
||||
}
|
||||
|
||||
/// <summary>Specifies the time period within which the host renews its lock on a message.</summary>
|
||||
/// <returns>The host that is being locked.</returns>
|
||||
public Task RenewLockAsync()
|
||||
{
|
||||
this.ThrowIfDisposed();
|
||||
this.ThrowIfNotLocked();
|
||||
|
||||
return this.InternalRenewLockAsync(this.LockToken);
|
||||
}
|
||||
|
||||
/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
|
@ -1239,6 +1248,11 @@ namespace Microsoft.Azure.ServiceBus
|
|||
return (value == null) ? typeof(object) : value.GetType();
|
||||
}
|
||||
|
||||
async Task InternalRenewLockAsync(Guid lockToken)
|
||||
{
|
||||
this.LockedUntilUtc = await this.Receiver.RenewLockAsync(this.LockToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary> Performs application-defined tasks associated with freeing, releasing, or resetting
|
||||
/// unmanaged resources. </summary>
|
||||
/// <param name="disposing"> true if resources should be disposed, false if not. </param>
|
||||
|
|
|
@ -7,26 +7,70 @@ namespace Microsoft.Azure.ServiceBus
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.ServiceBus.Amqp;
|
||||
|
||||
abstract class MessageReceiver : ClientEntity
|
||||
public abstract class MessageReceiver : ClientEntity
|
||||
{
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage(
|
||||
"StyleCop.CSharp.ReadabilityRules",
|
||||
"SA1126:PrefixCallsCorrectly",
|
||||
Justification = "This is not a method call, but a type.")]
|
||||
protected MessageReceiver(ReceiveMode receiveMode)
|
||||
readonly TimeSpan operationTimeout;
|
||||
int prefetchCount;
|
||||
|
||||
protected MessageReceiver(ReceiveMode receiveMode, TimeSpan operationTimeout)
|
||||
: base(nameof(MessageReceiver) + StringUtility.GetRandomString())
|
||||
{
|
||||
this.ReceiveMode = receiveMode;
|
||||
this.operationTimeout = operationTimeout;
|
||||
}
|
||||
|
||||
public abstract string Path { get; }
|
||||
|
||||
public ReceiveMode ReceiveMode { get; protected set; }
|
||||
|
||||
public virtual int PrefetchCount
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.prefetchCount;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value < 0)
|
||||
{
|
||||
throw Fx.Exception.ArgumentOutOfRange(nameof(this.PrefetchCount), value, "Value must be greater than 0");
|
||||
}
|
||||
|
||||
this.prefetchCount = value;
|
||||
}
|
||||
}
|
||||
|
||||
internal TimeSpan OperationTimeout
|
||||
{
|
||||
get { return this.operationTimeout; }
|
||||
}
|
||||
|
||||
protected MessagingEntityType EntityType { get; set; }
|
||||
|
||||
public async Task<BrokeredMessage> ReceiveAsync()
|
||||
{
|
||||
IList<BrokeredMessage> messages = await this.ReceiveAsync(1).ConfigureAwait(false);
|
||||
if (messages != null && messages.Count > 0)
|
||||
{
|
||||
return messages[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Task<IList<BrokeredMessage>> ReceiveAsync(int maxMessageCount)
|
||||
{
|
||||
return this.OnReceiveAsync(maxMessageCount);
|
||||
}
|
||||
|
||||
public Task<IList<BrokeredMessage>> ReceiveBySequenceNumberAsync(IEnumerable<long> sequenceNumbers)
|
||||
{
|
||||
return this.OnReceiveBySequenceNumberAsync(sequenceNumbers);
|
||||
}
|
||||
|
||||
public Task CompleteAsync(IEnumerable<Guid> lockTokens)
|
||||
{
|
||||
this.ThrowIfNotPeekLockMode();
|
||||
|
@ -59,8 +103,23 @@ namespace Microsoft.Azure.ServiceBus
|
|||
return this.OnDeadLetterAsync(lockTokens);
|
||||
}
|
||||
|
||||
public Task<DateTime> RenewLockAsync(Guid lockToken)
|
||||
{
|
||||
this.ThrowIfNotPeekLockMode();
|
||||
MessageReceiver.ValidateLockTokens(new Guid[] { lockToken });
|
||||
|
||||
return this.OnRenewLockAsync(lockToken);
|
||||
}
|
||||
|
||||
internal Task<AmqpResponseMessage> ExecuteRequestResponseAsync(AmqpRequestMessage amqpRequestMessage)
|
||||
{
|
||||
return this.OnExecuteRequestResponseAsync(amqpRequestMessage);
|
||||
}
|
||||
|
||||
protected abstract Task<IList<BrokeredMessage>> OnReceiveAsync(int maxMessageCount);
|
||||
|
||||
protected abstract Task<IList<BrokeredMessage>> OnReceiveBySequenceNumberAsync(IEnumerable<long> sequenceNumbers);
|
||||
|
||||
protected abstract Task OnCompleteAsync(IEnumerable<Guid> lockTokens);
|
||||
|
||||
protected abstract Task OnAbandonAsync(IEnumerable<Guid> lockTokens);
|
||||
|
@ -69,6 +128,10 @@ namespace Microsoft.Azure.ServiceBus
|
|||
|
||||
protected abstract Task OnDeadLetterAsync(IEnumerable<Guid> lockTokens);
|
||||
|
||||
protected abstract Task<DateTime> OnRenewLockAsync(Guid lockToken);
|
||||
|
||||
protected abstract Task<AmqpResponseMessage> OnExecuteRequestResponseAsync(AmqpRequestMessage requestAmqpMessage);
|
||||
|
||||
static void ValidateLockTokens(IEnumerable<Guid> lockTokens)
|
||||
{
|
||||
if (lockTokens == null || !lockTokens.Any())
|
||||
|
|
|
@ -3,19 +3,30 @@
|
|||
|
||||
namespace Microsoft.Azure.ServiceBus
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
abstract class MessageSender : ClientEntity
|
||||
public abstract class MessageSender : ClientEntity
|
||||
{
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage(
|
||||
"StyleCop.CSharp.ReadabilityRules",
|
||||
"SA1126:PrefixCallsCorrectly",
|
||||
Justification = "This is not a method call, but a type.")]
|
||||
protected MessageSender()
|
||||
protected MessageSender(TimeSpan operationTimeout)
|
||||
: base(nameof(MessageSender) + StringUtility.GetRandomString())
|
||||
{
|
||||
this.OperationTimeout = operationTimeout;
|
||||
}
|
||||
|
||||
internal TimeSpan OperationTimeout { get; }
|
||||
|
||||
protected MessagingEntityType EntityType { get; set; }
|
||||
|
||||
public Task SendAsync(BrokeredMessage brokeredMessage)
|
||||
{
|
||||
return this.SendAsync(new BrokeredMessage[] { brokeredMessage });
|
||||
}
|
||||
|
||||
public Task SendAsync(IEnumerable<BrokeredMessage> brokeredMessages)
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Azure.ServiceBus
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Amqp;
|
||||
using Azure.Amqp;
|
||||
using Messaging.Amqp;
|
||||
|
||||
public abstract class MessageSession : MessageReceiver
|
||||
{
|
||||
/// <summary>Represents a message session that allows grouping of related messages for processing in a single transaction.</summary>
|
||||
protected MessageSession(ReceiveMode receiveMode, string sessionId, DateTime lockedUntilUtc, MessageReceiver innerReceiver)
|
||||
: base(receiveMode, innerReceiver.OperationTimeout)
|
||||
{
|
||||
if (innerReceiver == null)
|
||||
{
|
||||
throw Fx.Exception.ArgumentNull("innerReceiver");
|
||||
}
|
||||
|
||||
this.SessionId = sessionId;
|
||||
this.LockedUntilUtc = lockedUntilUtc;
|
||||
this.InnerMessageReceiver = innerReceiver;
|
||||
}
|
||||
|
||||
public string SessionId { get; protected set; }
|
||||
|
||||
public DateTime LockedUntilUtc { get; protected set; }
|
||||
|
||||
public override string Path
|
||||
{
|
||||
get { return this.InnerMessageReceiver.Path; }
|
||||
}
|
||||
|
||||
protected MessageReceiver InnerMessageReceiver { get; set; }
|
||||
|
||||
public override async Task CloseAsync()
|
||||
{
|
||||
if (this.InnerMessageReceiver != null)
|
||||
{
|
||||
await this.InnerMessageReceiver.CloseAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public Task<Stream> GetStateAsync()
|
||||
{
|
||||
return this.OnGetStateAsync();
|
||||
}
|
||||
|
||||
public Task SetStateAsync(Stream sessionState)
|
||||
{
|
||||
return this.OnSetStateAsync(sessionState);
|
||||
}
|
||||
|
||||
public Task RenewLockAsync()
|
||||
{
|
||||
return this.OnRenewLockAsync();
|
||||
}
|
||||
|
||||
protected abstract Task<Stream> OnGetStateAsync();
|
||||
|
||||
protected abstract Task OnSetStateAsync(Stream sessionState);
|
||||
|
||||
protected abstract Task OnRenewLockAsync();
|
||||
|
||||
protected override Task<IList<BrokeredMessage>> OnReceiveAsync(int maxMessageCount)
|
||||
{
|
||||
return this.InnerMessageReceiver.ReceiveAsync(maxMessageCount);
|
||||
}
|
||||
|
||||
protected override Task<IList<BrokeredMessage>> OnReceiveBySequenceNumberAsync(IEnumerable<long> sequenceNumbers)
|
||||
{
|
||||
return this.InnerMessageReceiver.ReceiveBySequenceNumberAsync(sequenceNumbers);
|
||||
}
|
||||
|
||||
protected override Task OnCompleteAsync(IEnumerable<Guid> lockTokens)
|
||||
{
|
||||
return this.InnerMessageReceiver.CompleteAsync(lockTokens);
|
||||
}
|
||||
|
||||
protected override Task OnAbandonAsync(IEnumerable<Guid> lockTokens)
|
||||
{
|
||||
return this.InnerMessageReceiver.AbandonAsync(lockTokens);
|
||||
}
|
||||
|
||||
protected override Task OnDeferAsync(IEnumerable<Guid> lockTokens)
|
||||
{
|
||||
return this.InnerMessageReceiver.DeferAsync(lockTokens);
|
||||
}
|
||||
|
||||
protected override Task OnDeadLetterAsync(IEnumerable<Guid> lockTokens)
|
||||
{
|
||||
return this.InnerMessageReceiver.DeadLetterAsync(lockTokens);
|
||||
}
|
||||
|
||||
protected override Task<DateTime> OnRenewLockAsync(Guid lockToken)
|
||||
{
|
||||
return this.InnerMessageReceiver.RenewLockAsync(lockToken);
|
||||
}
|
||||
|
||||
protected override Task<AmqpResponseMessage> OnExecuteRequestResponseAsync(AmqpRequestMessage requestAmqpMessage)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -27,7 +27,7 @@ namespace Microsoft.Azure.ServiceBus
|
|||
|
||||
public Task<LockRelease> LockAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var waitTask = this.asyncSemaphore.WaitAsync(cancellationToken);
|
||||
Task waitTask = this.asyncSemaphore.WaitAsync(cancellationToken);
|
||||
if (waitTask.IsCompleted)
|
||||
{
|
||||
// Avoid an allocation in the non-contention case.
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Azure.ServiceBus.Primitives
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
sealed class ConcurrentExpiringSet<TKey>
|
||||
{
|
||||
readonly ConcurrentDictionary<TKey, DateTime> dictionary;
|
||||
readonly object cleanupSynObject = new object();
|
||||
bool cleanupScheduled;
|
||||
|
||||
public ConcurrentExpiringSet()
|
||||
{
|
||||
this.dictionary = new ConcurrentDictionary<TKey, DateTime>();
|
||||
}
|
||||
|
||||
public void AddOrUpdate(TKey key, DateTime expiration)
|
||||
{
|
||||
this.dictionary[key] = expiration;
|
||||
this.ScheduleCleanup();
|
||||
}
|
||||
|
||||
public bool Contains(TKey key)
|
||||
{
|
||||
DateTime expiration;
|
||||
if (this.dictionary.TryGetValue(key, out expiration))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ScheduleCleanup()
|
||||
{
|
||||
lock (this.cleanupSynObject)
|
||||
{
|
||||
if (this.cleanupScheduled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.cleanupScheduled = true;
|
||||
Task.Run(() => this.CollectExpiredEntries());
|
||||
}
|
||||
}
|
||||
|
||||
void CollectExpiredEntries()
|
||||
{
|
||||
lock (this.cleanupSynObject)
|
||||
{
|
||||
this.cleanupScheduled = false;
|
||||
}
|
||||
|
||||
foreach (TKey key in this.dictionary.Keys)
|
||||
{
|
||||
if (DateTime.UtcNow > this.dictionary[key])
|
||||
{
|
||||
DateTime entry;
|
||||
this.dictionary.TryRemove(key, out entry);
|
||||
}
|
||||
}
|
||||
|
||||
this.ScheduleCleanup();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Azure.ServiceBus.Primitives
|
||||
{
|
||||
enum DispositionStatus
|
||||
{
|
||||
Completed = 1,
|
||||
Defered = 2,
|
||||
Suspended = 3,
|
||||
Abandoned = 4,
|
||||
Renewed = 5,
|
||||
}
|
||||
}
|
|
@ -6,18 +6,24 @@ namespace Microsoft.Azure.ServiceBus
|
|||
public static class EntityNameHelper
|
||||
{
|
||||
public const string PathDelimiter = @"/";
|
||||
public const string Subscriptions = "Subscriptions";
|
||||
public const string SubQueuePrefix = "$";
|
||||
public const string DeadLetterQueueSuffix = "DeadLetterQueue";
|
||||
public const string DeadLetterQueueName = SubQueuePrefix + DeadLetterQueueSuffix;
|
||||
|
||||
public static string FormatDeadLetterPath(string queuePath)
|
||||
public static string FormatDeadLetterPath(string entityPath)
|
||||
{
|
||||
return EntityNameHelper.FormatSubQueuePath(queuePath, EntityNameHelper.DeadLetterQueueName);
|
||||
return EntityNameHelper.FormatSubQueuePath(entityPath, EntityNameHelper.DeadLetterQueueName);
|
||||
}
|
||||
|
||||
public static string FormatSubQueuePath(string entityPath, string subQueueName)
|
||||
{
|
||||
return string.Concat(entityPath, EntityNameHelper.PathDelimiter, subQueueName);
|
||||
}
|
||||
|
||||
public static string FormatSubscriptionPath(string topicPath, string subscriptionName)
|
||||
{
|
||||
return string.Concat(topicPath, PathDelimiter, Subscriptions, PathDelimiter, subscriptionName);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,20 +3,13 @@
|
|||
|
||||
namespace Microsoft.Azure.ServiceBus
|
||||
{
|
||||
enum MessagingEntityType
|
||||
public enum MessagingEntityType
|
||||
{
|
||||
Queue = 0,
|
||||
Topic = 1,
|
||||
Subscriber = 2,
|
||||
Filter = 3,
|
||||
Namespace = 4,
|
||||
VolatileTopic = 5,
|
||||
VolatileTopicSubscription = 6,
|
||||
EventHub = 7,
|
||||
ConsumerGroup = 8,
|
||||
Partition = 9,
|
||||
Checkpoint = 10,
|
||||
RevokedPublisher = 11,
|
||||
Unknown = (int)0x7FFFFFFE,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Azure.ServiceBus
|
||||
{
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.Amqp;
|
||||
using Microsoft.Azure.Amqp.Transport;
|
||||
using Microsoft.Azure.ServiceBus.Amqp;
|
||||
using Microsoft.Azure.ServiceBus.Primitives;
|
||||
|
||||
public abstract class ServiceBusConnection
|
||||
{
|
||||
public static readonly TimeSpan DefaultOperationTimeout = TimeSpan.FromMinutes(1);
|
||||
static readonly Version AmqpVersion = new Version(1, 0, 0, 0);
|
||||
int prefetchCount;
|
||||
|
||||
protected ServiceBusConnection(TimeSpan operationTimeout, RetryPolicy retryPolicy)
|
||||
{
|
||||
this.OperationTimeout = operationTimeout;
|
||||
this.RetryPolicy = retryPolicy;
|
||||
}
|
||||
|
||||
public Uri Endpoint { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// OperationTimeout is applied in erroneous situations to notify the caller about the relevant <see cref="ServiceBusException"/>
|
||||
/// </summary>
|
||||
public TimeSpan OperationTimeout { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the retry policy instance that was created as part of this builder's creation.
|
||||
/// </summary>
|
||||
public RetryPolicy RetryPolicy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the shared access policy key value from the connection string
|
||||
/// </summary>
|
||||
/// <value>Shared Access Signature key</value>
|
||||
public string SasKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the shared access policy owner name from the connection string
|
||||
/// </summary>
|
||||
public string SasKeyName { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the number of messages that the message receiver can simultaneously request.</summary>
|
||||
/// <value>The number of messages that the message receiver can simultaneously request.</value>
|
||||
public int PrefetchCount
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.prefetchCount;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value < 0)
|
||||
{
|
||||
throw Fx.Exception.ArgumentOutOfRange(nameof(this.PrefetchCount), value, "Value must be greater than 0");
|
||||
}
|
||||
|
||||
this.prefetchCount = value;
|
||||
}
|
||||
}
|
||||
|
||||
internal FaultTolerantAmqpObject<AmqpConnection> ConnectionManager { get; set; }
|
||||
|
||||
public Task CloseAsync()
|
||||
{
|
||||
return this.ConnectionManager.CloseAsync();
|
||||
}
|
||||
|
||||
internal QueueClient CreateQueueClient(string entityPath, ReceiveMode mode)
|
||||
{
|
||||
return new AmqpQueueClient(this, entityPath, mode);
|
||||
}
|
||||
|
||||
internal TopicClient CreateTopicClient(string topicPath)
|
||||
{
|
||||
return new AmqpTopicClient(this, topicPath);
|
||||
}
|
||||
|
||||
internal SubscriptionClient CreateSubscriptionClient(string topicPath, string subscriptionName, ReceiveMode mode)
|
||||
{
|
||||
return new AmqpSubscriptionClient(this, topicPath, subscriptionName, mode);
|
||||
}
|
||||
|
||||
protected void InitializeConnection(ServiceBusConnectionStringBuilder builder)
|
||||
{
|
||||
this.Endpoint = builder.Endpoint;
|
||||
this.SasKeyName = builder.SasKeyName;
|
||||
this.SasKey = builder.SasKey;
|
||||
this.ConnectionManager = new FaultTolerantAmqpObject<AmqpConnection>(this.CreateConnectionAsync, ServiceBusConnection.CloseConnection);
|
||||
}
|
||||
|
||||
static void CloseConnection(AmqpConnection connection)
|
||||
{
|
||||
connection.SafeClose();
|
||||
}
|
||||
|
||||
async Task<AmqpConnection> CreateConnectionAsync(TimeSpan timeout)
|
||||
{
|
||||
string hostName = this.Endpoint.Host;
|
||||
string networkHost = this.Endpoint.Host;
|
||||
int port = this.Endpoint.Port;
|
||||
|
||||
TimeoutHelper timeoutHelper = new TimeoutHelper(timeout);
|
||||
AmqpSettings amqpSettings = AmqpConnectionHelper.CreateAmqpSettings(
|
||||
amqpVersion: ServiceBusConnection.AmqpVersion,
|
||||
useSslStreamSecurity: true,
|
||||
hasTokenProvider: true);
|
||||
|
||||
TransportSettings tpSettings = AmqpConnectionHelper.CreateTcpTransportSettings(
|
||||
networkHost: networkHost,
|
||||
hostName: hostName,
|
||||
port: port,
|
||||
useSslStreamSecurity: true);
|
||||
|
||||
AmqpTransportInitiator initiator = new AmqpTransportInitiator(amqpSettings, tpSettings);
|
||||
TransportBase transport = await initiator.ConnectTaskAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false);
|
||||
|
||||
string containerId = Guid.NewGuid().ToString();
|
||||
AmqpConnectionSettings amqpConnectionSettings = AmqpConnectionHelper.CreateAmqpConnectionSettings(AmqpConstants.DefaultMaxFrameSize, containerId, hostName);
|
||||
AmqpConnection connection = new AmqpConnection(transport, amqpSettings, amqpConnectionSettings);
|
||||
await connection.OpenAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false);
|
||||
|
||||
// Always create the CBS Link + Session
|
||||
AmqpCbsLink cbsLink = new AmqpCbsLink(connection);
|
||||
if (connection.Extensions.Find<AmqpCbsLink>() == null)
|
||||
{
|
||||
connection.Extensions.Add(cbsLink);
|
||||
}
|
||||
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,229 +0,0 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Azure.ServiceBus
|
||||
{
|
||||
using System;
|
||||
using System.Text;
|
||||
using Microsoft.Azure.ServiceBus.Amqp;
|
||||
|
||||
/// <summary>
|
||||
/// ServiceBusConnectionSettings can be used to construct a connection string which can establish communication with ServiceBus entities.
|
||||
/// It can also be used to perform basic validation on an existing connection string.
|
||||
/// <para/>
|
||||
/// A connection string is basically a string consisted of key-value pair separated by ";".
|
||||
/// Basic format is "<key>=<value>[;<key>=<value>]" where supported key name are as follow:
|
||||
/// <para/> Endpoint - the URL that contains the servicebus namespace
|
||||
/// <para/> EntityPath - the path to the service bus entity (queue/topic/eventhub/subscription/consumergroup/partition)
|
||||
/// <para/> SharedAccessKeyName - the key name to the corresponding shared access policy rule for the namespace, or entity.
|
||||
/// <para/> SharedAccessKey - the key for the corresponding shared access policy rule of the namespace or entity.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// Sample code:
|
||||
/// <code>
|
||||
/// var connectionSettings = new ServiceBusConnectionSettings(
|
||||
/// "ServiceBusNamespaceName",
|
||||
/// "ServiceBusEntityName", // eventHub, queue, or topic name
|
||||
/// "SharedAccessSignatureKeyName",
|
||||
/// "SharedAccessSignatureKey");
|
||||
/// string connectionString = connectionSettings.ToString();
|
||||
/// </code>
|
||||
/// </example>
|
||||
public class ServiceBusConnectionSettings
|
||||
{
|
||||
const string KeyValueSeparator = "=";
|
||||
const string KeyValuePairDelimiter = ";";
|
||||
static readonly TimeSpan DefaultOperationTimeout = TimeSpan.FromMinutes(1);
|
||||
static readonly string EndpointScheme = "amqps";
|
||||
static readonly string EndpointFormat = $"{EndpointScheme}://{{0}}.servicebus.windows.net";
|
||||
static readonly string EndpointConfigName = "Endpoint";
|
||||
static readonly string SharedAccessKeyNameConfigName = "SharedAccessKeyName";
|
||||
static readonly string SharedAccessKeyConfigName = "SharedAccessKey";
|
||||
static readonly string EntityPathConfigName = "EntityPath";
|
||||
|
||||
/// <summary>
|
||||
/// Build a connection string consumable by <see cref="QueueClient.Create(string)"/>
|
||||
/// </summary>
|
||||
/// <param name="namespaceName">Namespace name (the dns suffix, ex: .servicebus.windows.net, is not required)</param>
|
||||
/// <param name="entityPath">Entity path. For Queue case specify Queue name.</param>
|
||||
/// <param name="sharedAccessKeyName">Shared Access Key name</param>
|
||||
/// <param name="sharedAccessKey">Shared Access Key</param>
|
||||
public ServiceBusConnectionSettings(string namespaceName, string entityPath, string sharedAccessKeyName, string sharedAccessKey)
|
||||
: this(namespaceName, entityPath, sharedAccessKeyName, sharedAccessKey, DefaultOperationTimeout, RetryPolicy.Default)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ConnectionString format:
|
||||
/// Endpoint=sb://namespace_DNS_Name;EntityPath=EVENT_HUB_NAME;SharedAccessKeyName=SHARED_ACCESS_KEY_NAME;SharedAccessKey=SHARED_ACCESS_KEY
|
||||
/// </summary>
|
||||
/// <param name="connectionString">ServiceBus ConnectionString</param>
|
||||
public ServiceBusConnectionSettings(string connectionString)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(connectionString))
|
||||
{
|
||||
throw Fx.Exception.ArgumentNullOrWhiteSpace(nameof(connectionString));
|
||||
}
|
||||
|
||||
this.OperationTimeout = DefaultOperationTimeout;
|
||||
this.RetryPolicy = RetryPolicy.Default;
|
||||
this.ParseConnectionString(connectionString);
|
||||
}
|
||||
|
||||
ServiceBusConnectionSettings(
|
||||
string namespaceName,
|
||||
string entityPath,
|
||||
string sharedAccessKeyName,
|
||||
string sharedAccessKey,
|
||||
TimeSpan operationTimeout,
|
||||
RetryPolicy retryPolicy)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(namespaceName) || string.IsNullOrWhiteSpace(entityPath))
|
||||
{
|
||||
throw Fx.Exception.ArgumentNullOrWhiteSpace(string.IsNullOrWhiteSpace(namespaceName) ? nameof(namespaceName) : nameof(entityPath));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(sharedAccessKeyName) || string.IsNullOrWhiteSpace(sharedAccessKey))
|
||||
{
|
||||
throw Fx.Exception.ArgumentNullOrWhiteSpace(string.IsNullOrWhiteSpace(sharedAccessKeyName) ? nameof(sharedAccessKeyName) : nameof(sharedAccessKey));
|
||||
}
|
||||
|
||||
if (namespaceName.Contains("."))
|
||||
{
|
||||
// It appears to be a fully qualified host name, use it.
|
||||
this.Endpoint = new Uri(EndpointScheme + "://" + namespaceName);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.Endpoint = new Uri(EndpointFormat.FormatInvariant(namespaceName));
|
||||
}
|
||||
|
||||
this.EntityPath = entityPath;
|
||||
this.SasKey = sharedAccessKey;
|
||||
this.SasKeyName = sharedAccessKeyName;
|
||||
this.OperationTimeout = operationTimeout;
|
||||
this.RetryPolicy = retryPolicy ?? RetryPolicy.Default;
|
||||
}
|
||||
|
||||
public Uri Endpoint { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the shared access policy key value from the connection string
|
||||
/// </summary>
|
||||
/// <value>Shared Access Signature key</value>
|
||||
public string SasKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the shared access policy owner name from the connection string
|
||||
/// </summary>
|
||||
public string SasKeyName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the entity path value from the connection string
|
||||
/// </summary>
|
||||
public string EntityPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// OperationTimeout is applied in erroneous situations to notify the caller about the relevant <see cref="ServiceBusException"/>
|
||||
/// </summary>
|
||||
public TimeSpan OperationTimeout { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the retry policy instance that was created as part of this builder's creation.
|
||||
/// </summary>
|
||||
public RetryPolicy RetryPolicy { get; set; }
|
||||
|
||||
public ServiceBusConnectionSettings Clone()
|
||||
{
|
||||
var clone = new ServiceBusConnectionSettings(this.ToString())
|
||||
{
|
||||
OperationTimeout = this.OperationTimeout,
|
||||
RetryPolicy = this.RetryPolicy
|
||||
};
|
||||
return clone;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a TokenProvider given the credentials in this ServiceBusConnectionSettings.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public TokenProvider CreateTokenProvider()
|
||||
{
|
||||
return TokenProvider.CreateSharedAccessSignatureTokenProvider(this.SasKeyName, this.SasKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an interoperable connection string that can be used to connect to ServiceBus Namespace
|
||||
/// </summary>
|
||||
/// <returns>the connection string</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
var connectionStringBuilder = new StringBuilder();
|
||||
if (this.Endpoint != null)
|
||||
{
|
||||
connectionStringBuilder.Append($"{EndpointConfigName}{KeyValueSeparator}{this.Endpoint}{KeyValuePairDelimiter}");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(this.EntityPath))
|
||||
{
|
||||
connectionStringBuilder.Append($"{EntityPathConfigName}{KeyValueSeparator}{this.EntityPath}{KeyValuePairDelimiter}");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(this.SasKeyName))
|
||||
{
|
||||
connectionStringBuilder.Append($"{SharedAccessKeyNameConfigName}{KeyValueSeparator}{this.SasKeyName}{KeyValuePairDelimiter}");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(this.SasKey))
|
||||
{
|
||||
connectionStringBuilder.Append($"{SharedAccessKeyConfigName}{KeyValueSeparator}{this.SasKey}");
|
||||
}
|
||||
|
||||
return connectionStringBuilder.ToString();
|
||||
}
|
||||
|
||||
internal QueueClient CreateQueueClient(ReceiveMode mode)
|
||||
{
|
||||
// In the future to support other protocols add that logic here.
|
||||
return new AmqpQueueClient(this.Clone(), mode);
|
||||
}
|
||||
|
||||
void ParseConnectionString(string connectionString)
|
||||
{
|
||||
// First split based on ';'
|
||||
string[] keyValuePairs = connectionString.Split(new[] { KeyValuePairDelimiter }, StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var keyValuePair in keyValuePairs)
|
||||
{
|
||||
// Now split based on the _first_ '='
|
||||
string[] keyAndValue = keyValuePair.Split(new[] { KeyValueSeparator[0] }, 2);
|
||||
string key = keyAndValue[0];
|
||||
if (keyAndValue.Length != 2)
|
||||
{
|
||||
throw Fx.Exception.Argument(nameof(connectionString), $"Value for the connection string parameter name '{key}' was not found.");
|
||||
}
|
||||
|
||||
string value = keyAndValue[1];
|
||||
if (key.Equals(EndpointConfigName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
this.Endpoint = new Uri(value);
|
||||
}
|
||||
else if (key.Equals(EntityPathConfigName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
this.EntityPath = value;
|
||||
}
|
||||
else if (key.Equals(SharedAccessKeyNameConfigName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
this.SasKeyName = value;
|
||||
}
|
||||
else if (key.Equals(SharedAccessKeyConfigName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
this.SasKey = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw Fx.Exception.Argument(nameof(connectionString), $"Illegal connection string parameter name '{key}'");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Azure.ServiceBus.Primitives
|
||||
{
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
public class ServiceBusConnectionStringBuilder
|
||||
{
|
||||
const char KeyValueSeparator = '=';
|
||||
const char KeyValuePairDelimiter = ';';
|
||||
static readonly string EndpointScheme = "amqps";
|
||||
static readonly string EndpointFormat = EndpointScheme + "://{0}.servicebus.windows.net";
|
||||
static readonly string EndpointConfigName = "Endpoint";
|
||||
static readonly string SharedAccessKeyNameConfigName = "SharedAccessKeyName";
|
||||
static readonly string SharedAccessKeyConfigName = "SharedAccessKey";
|
||||
static readonly string EntityPathConfigName = "EntityPath";
|
||||
|
||||
public ServiceBusConnectionStringBuilder(string connectionString)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(connectionString))
|
||||
{
|
||||
this.ParseConnectionString(connectionString);
|
||||
}
|
||||
}
|
||||
|
||||
public Uri Endpoint { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the entity path value from the connection string
|
||||
/// </summary>
|
||||
public string EntityPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the shared access policy owner name from the connection string
|
||||
/// </summary>
|
||||
public string SasKeyName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the shared access policy key value from the connection string
|
||||
/// </summary>
|
||||
/// <value>Shared Access Signature key</value>
|
||||
public string SasKey { get; set; }
|
||||
|
||||
public string GetConnectionString(string namespaceName, string entityPath, string sharedAccessKeyName, string sharedAccessKey)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(namespaceName))
|
||||
{
|
||||
throw Fx.Exception.ArgumentNullOrWhiteSpace(nameof(namespaceName));
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(sharedAccessKeyName) || string.IsNullOrWhiteSpace(sharedAccessKey))
|
||||
{
|
||||
throw Fx.Exception.ArgumentNullOrWhiteSpace(string.IsNullOrWhiteSpace(sharedAccessKeyName) ? nameof(sharedAccessKeyName) : nameof(sharedAccessKey));
|
||||
}
|
||||
|
||||
if (namespaceName.Contains("."))
|
||||
{
|
||||
// It appears to be a fully qualified host name, use it.
|
||||
this.Endpoint = new Uri(EndpointScheme + "://" + namespaceName);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.Endpoint = new Uri(EndpointFormat.FormatInvariant(namespaceName));
|
||||
}
|
||||
|
||||
this.EntityPath = entityPath;
|
||||
this.SasKeyName = sharedAccessKeyName;
|
||||
this.SasKey = sharedAccessKey;
|
||||
|
||||
return this.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an interoperable connection string that can be used to connect to ServiceBus Namespace
|
||||
/// </summary>
|
||||
/// <returns>the connection string</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder connectionStringBuilder = new StringBuilder();
|
||||
if (this.Endpoint != null)
|
||||
{
|
||||
connectionStringBuilder.Append($"{EndpointConfigName}{KeyValueSeparator}{this.Endpoint}{KeyValuePairDelimiter}");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(this.EntityPath))
|
||||
{
|
||||
connectionStringBuilder.Append($"{EntityPathConfigName}{KeyValueSeparator}{this.EntityPath}{KeyValuePairDelimiter}");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(this.SasKeyName))
|
||||
{
|
||||
connectionStringBuilder.Append($"{SharedAccessKeyNameConfigName}{KeyValueSeparator}{this.SasKeyName}{KeyValuePairDelimiter}");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(this.SasKey))
|
||||
{
|
||||
connectionStringBuilder.Append($"{SharedAccessKeyConfigName}{KeyValueSeparator}{this.SasKey}");
|
||||
}
|
||||
|
||||
return connectionStringBuilder.ToString();
|
||||
}
|
||||
|
||||
void ParseConnectionString(string connectionString)
|
||||
{
|
||||
// First split based on ';'
|
||||
string[] keyValuePairs = connectionString.Split(new[] { KeyValuePairDelimiter }, StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var keyValuePair in keyValuePairs)
|
||||
{
|
||||
// Now split based on the _first_ '='
|
||||
string[] keyAndValue = keyValuePair.Split(new[] { KeyValueSeparator }, 2);
|
||||
string key = keyAndValue[0];
|
||||
if (keyAndValue.Length != 2)
|
||||
{
|
||||
throw Fx.Exception.Argument(nameof(connectionString), $"Value for the connection string parameter name '{key}' was not found.");
|
||||
}
|
||||
|
||||
string value = keyAndValue[1];
|
||||
if (key.Equals(EndpointConfigName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
this.Endpoint = new Uri(value);
|
||||
}
|
||||
else if (key.Equals(SharedAccessKeyNameConfigName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
this.SasKeyName = value;
|
||||
}
|
||||
else if (key.Equals(EntityPathConfigName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
this.EntityPath = value;
|
||||
}
|
||||
else if (key.Equals(SharedAccessKeyConfigName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
this.SasKey = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw Fx.Exception.Argument(nameof(connectionString), $"Illegal connection string parameter name '{key}'");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Azure.ServiceBus.Primitives
|
||||
{
|
||||
using System;
|
||||
|
||||
public class ServiceBusEntityConnection : ServiceBusConnection
|
||||
{
|
||||
public ServiceBusEntityConnection(string entityConnectionString)
|
||||
: this(entityConnectionString, ServiceBusConnection.DefaultOperationTimeout, RetryPolicy.Default)
|
||||
{
|
||||
}
|
||||
|
||||
public ServiceBusEntityConnection(string entityConnectionString, TimeSpan operationTimeout, RetryPolicy retryPolicy)
|
||||
: base(operationTimeout, retryPolicy)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(entityConnectionString))
|
||||
{
|
||||
throw Fx.Exception.ArgumentNullOrWhiteSpace(nameof(entityConnectionString));
|
||||
}
|
||||
|
||||
ServiceBusConnectionStringBuilder builder = new ServiceBusConnectionStringBuilder(entityConnectionString);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(builder.EntityPath))
|
||||
{
|
||||
throw Fx.Exception.Argument(nameof(entityConnectionString), "EntityConnectionString should contain EntityPath.");
|
||||
}
|
||||
|
||||
this.InitializeConnection(builder);
|
||||
this.EntityPath = builder.EntityPath;
|
||||
}
|
||||
|
||||
public string EntityPath { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Azure.ServiceBus.Primitives
|
||||
{
|
||||
using System;
|
||||
|
||||
public class ServiceBusNamespaceConnection : ServiceBusConnection
|
||||
{
|
||||
public ServiceBusNamespaceConnection(string namespaceConnectionString)
|
||||
: this(namespaceConnectionString, ServiceBusConnection.DefaultOperationTimeout, RetryPolicy.Default)
|
||||
{
|
||||
}
|
||||
|
||||
public ServiceBusNamespaceConnection(string namespaceConnectionString, TimeSpan operationTimeout, RetryPolicy retryPolicy)
|
||||
: base(operationTimeout, retryPolicy)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(namespaceConnectionString))
|
||||
{
|
||||
throw Fx.Exception.ArgumentNullOrWhiteSpace(nameof(namespaceConnectionString));
|
||||
}
|
||||
|
||||
ServiceBusConnectionStringBuilder builder = new ServiceBusConnectionStringBuilder(namespaceConnectionString);
|
||||
if (!string.IsNullOrWhiteSpace(builder.EntityPath))
|
||||
{
|
||||
throw Fx.Exception.Argument(nameof(namespaceConnectionString), "NamespaceConnectionString should not contain EntityPath.");
|
||||
}
|
||||
|
||||
this.InitializeConnection(builder);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -77,7 +77,7 @@ namespace Microsoft.Azure.ServiceBus
|
|||
protected override Task<SecurityToken> OnGetTokenAsync(string appliesTo, string action, TimeSpan timeout)
|
||||
{
|
||||
string tokenString = this.BuildSignature(appliesTo);
|
||||
var securityToken = new SharedAccessSignatureToken(tokenString);
|
||||
SharedAccessSignatureToken securityToken = new SharedAccessSignatureToken(tokenString);
|
||||
return Task.FromResult<SecurityToken>(securityToken);
|
||||
}
|
||||
|
||||
|
@ -139,7 +139,7 @@ namespace Microsoft.Azure.ServiceBus
|
|||
|
||||
static string Sign(string requestString, byte[] encodedSharedAccessKey)
|
||||
{
|
||||
using (var hmac = new HMACSHA256(encodedSharedAccessKey))
|
||||
using (HMACSHA256 hmac = new HMACSHA256(encodedSharedAccessKey))
|
||||
{
|
||||
return Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(requestString)));
|
||||
}
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Azure.ServiceBus.Primitives
|
||||
{
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.Amqp;
|
||||
|
||||
/// <summary>
|
||||
/// Provides an adapter from TokenProvider to ICbsTokenProvider for AMQP CBS usage.
|
||||
/// </summary>
|
||||
sealed class TokenProviderAdapter : ICbsTokenProvider
|
||||
{
|
||||
readonly TokenProvider tokenProvider;
|
||||
readonly TimeSpan operationTimeout;
|
||||
|
||||
public TokenProviderAdapter(TokenProvider tokenProvider, TimeSpan operationTimeout)
|
||||
{
|
||||
Fx.Assert(tokenProvider != null, "tokenProvider cannot be null");
|
||||
this.tokenProvider = tokenProvider;
|
||||
this.operationTimeout = operationTimeout;
|
||||
}
|
||||
|
||||
public async Task<CbsToken> GetTokenAsync(Uri namespaceAddress, string appliesTo, string[] requiredClaims)
|
||||
{
|
||||
string claim = requiredClaims?.FirstOrDefault();
|
||||
SecurityToken token = await this.tokenProvider.GetTokenAsync(appliesTo, claim, this.operationTimeout).ConfigureAwait(false);
|
||||
return new CbsToken(token.TokenValue, CbsConstants.ServiceBusSasTokenType, token.ExpiresAtUtc);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
|
@ -18,4 +19,12 @@ using System.Runtime.InteropServices;
|
|||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("a042adf0-ef65-4f87-b634-322a409f3d61")]
|
||||
[assembly: Guid("a042adf0-ef65-4f87-b634-322a409f3d61")]
|
||||
|
||||
// Friend Assemblies
|
||||
[assembly: InternalsVisibleTo("Microsoft.Azure.ServiceBus.UnitTests,PublicKey=" +
|
||||
"0024000004800000940000000602000000240000525341310004000001000100f9bc4236ac3b7c" +
|
||||
"ffc0f828bf2d60d34b447873db1fa3ce194d8ad43e134f944d40ab9a13c668f0c3c5af98a767e7" +
|
||||
"cc831d9e02390dfb7252077bb8f5efd8f8c011c529040583ba9f6bc4fe31f8d5deebd3add91163" +
|
||||
"7dd4e7096a409f032ca61ee270f8fca44156a6ee9bcbc6e23e650ba41d40280778985de18c4f33" +
|
||||
"b3841cc6")]
|
|
@ -6,6 +6,7 @@ namespace Microsoft.Azure.ServiceBus
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.ServiceBus.Primitives;
|
||||
|
||||
/// <summary>
|
||||
/// Anchor class - all Queue client operations start here.
|
||||
|
@ -16,25 +17,32 @@ namespace Microsoft.Azure.ServiceBus
|
|||
MessageSender innerSender;
|
||||
MessageReceiver innerReceiver;
|
||||
|
||||
internal QueueClient(ServiceBusConnectionSettings connectionSettings, ReceiveMode receiveMode)
|
||||
: base($"{nameof(QueueClient)}{ClientEntity.GetNextId()}({connectionSettings.EntityPath})")
|
||||
protected QueueClient(ServiceBusConnection serviceBusConnection, string entityPath, ReceiveMode receiveMode)
|
||||
: base($"{nameof(QueueClient)}{ClientEntity.GetNextId()}({entityPath})")
|
||||
{
|
||||
this.ConnectionSettings = connectionSettings;
|
||||
this.QueueName = connectionSettings.EntityPath;
|
||||
this.ServiceBusConnection = serviceBusConnection;
|
||||
this.QueueName = entityPath;
|
||||
this.Mode = receiveMode;
|
||||
}
|
||||
|
||||
public string QueueName { get; }
|
||||
|
||||
public ServiceBusConnectionSettings ConnectionSettings { get; }
|
||||
|
||||
public ReceiveMode Mode { get; private set; }
|
||||
|
||||
public int PrefetchCount { get; set; }
|
||||
public int PrefetchCount
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.InnerReceiver.PrefetchCount;
|
||||
}
|
||||
|
||||
protected object ThisLock { get; } = new object();
|
||||
set
|
||||
{
|
||||
this.InnerReceiver.PrefetchCount = value;
|
||||
}
|
||||
}
|
||||
|
||||
MessageSender InnerSender
|
||||
internal MessageSender InnerSender
|
||||
{
|
||||
get
|
||||
{
|
||||
|
@ -53,7 +61,7 @@ namespace Microsoft.Azure.ServiceBus
|
|||
}
|
||||
}
|
||||
|
||||
MessageReceiver InnerReceiver
|
||||
internal MessageReceiver InnerReceiver
|
||||
{
|
||||
get
|
||||
{
|
||||
|
@ -72,67 +80,65 @@ namespace Microsoft.Azure.ServiceBus
|
|||
}
|
||||
}
|
||||
|
||||
public static QueueClient Create(string connectionString)
|
||||
protected object ThisLock { get; } = new object();
|
||||
|
||||
protected ServiceBusConnection ServiceBusConnection { get; }
|
||||
|
||||
public static QueueClient CreateFromConnectionString(string entityConnectionString)
|
||||
{
|
||||
return Create(connectionString, ReceiveMode.PeekLock);
|
||||
return CreateFromConnectionString(entityConnectionString, ReceiveMode.PeekLock);
|
||||
}
|
||||
|
||||
public static QueueClient Create(string connectionString, ReceiveMode mode)
|
||||
public static QueueClient CreateFromConnectionString(string entityConnectionString, ReceiveMode mode)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(connectionString))
|
||||
if (string.IsNullOrWhiteSpace(entityConnectionString))
|
||||
{
|
||||
throw Fx.Exception.ArgumentNullOrWhiteSpace(nameof(connectionString));
|
||||
throw Fx.Exception.ArgumentNullOrWhiteSpace(nameof(entityConnectionString));
|
||||
}
|
||||
|
||||
var connectionSettings = new ServiceBusConnectionSettings(connectionString);
|
||||
return Create(connectionSettings, mode);
|
||||
ServiceBusEntityConnection entityConnection = new ServiceBusEntityConnection(entityConnectionString);
|
||||
return entityConnection.CreateQueueClient(entityConnection.EntityPath, mode);
|
||||
}
|
||||
|
||||
public static QueueClient Create(string connectionString, string path)
|
||||
public static QueueClient Create(ServiceBusNamespaceConnection namespaceConnection, string entityPath)
|
||||
{
|
||||
return Create(connectionString, path, ReceiveMode.PeekLock);
|
||||
return QueueClient.Create(namespaceConnection, entityPath, ReceiveMode.PeekLock);
|
||||
}
|
||||
|
||||
public static QueueClient Create(string connectionString, string path, ReceiveMode mode)
|
||||
public static QueueClient Create(ServiceBusNamespaceConnection namespaceConnection, string entityPath, ReceiveMode mode)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(connectionString))
|
||||
if (namespaceConnection == null)
|
||||
{
|
||||
throw Fx.Exception.ArgumentNullOrWhiteSpace(nameof(connectionString));
|
||||
throw Fx.Exception.Argument(nameof(namespaceConnection), "Namespace Connection is null. Create a connection using the NamespaceConnection class");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
if (string.IsNullOrWhiteSpace(entityPath))
|
||||
{
|
||||
throw Fx.Exception.ArgumentNullOrWhiteSpace(nameof(path));
|
||||
throw Fx.Exception.Argument(nameof(namespaceConnection), "Entity Path is null");
|
||||
}
|
||||
|
||||
var connectionSettings = new ServiceBusConnectionSettings(connectionString) { EntityPath = path };
|
||||
|
||||
return Create(connectionSettings, mode);
|
||||
return namespaceConnection.CreateQueueClient(entityPath, mode);
|
||||
}
|
||||
|
||||
public static QueueClient Create(ServiceBusConnectionSettings connectionSettings)
|
||||
public static QueueClient Create(ServiceBusEntityConnection entityConnection)
|
||||
{
|
||||
return Create(connectionSettings, ReceiveMode.PeekLock);
|
||||
return QueueClient.Create(entityConnection, ReceiveMode.PeekLock);
|
||||
}
|
||||
|
||||
public static QueueClient Create(ServiceBusConnectionSettings connectionSettings, ReceiveMode mode)
|
||||
public static QueueClient Create(ServiceBusEntityConnection entityConnection, ReceiveMode mode)
|
||||
{
|
||||
if (connectionSettings == null)
|
||||
if (entityConnection == null)
|
||||
{
|
||||
throw Fx.Exception.ArgumentNull(nameof(connectionSettings));
|
||||
throw Fx.Exception.Argument(nameof(entityConnection), "Namespace Connection is null. Create a connection using the NamespaceConnection class");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(connectionSettings.EntityPath))
|
||||
{
|
||||
throw Fx.Exception.ArgumentNullOrWhiteSpace(nameof(connectionSettings.EntityPath));
|
||||
}
|
||||
|
||||
return connectionSettings.CreateQueueClient(mode);
|
||||
return entityConnection.CreateQueueClient(entityConnection.EntityPath, mode);
|
||||
}
|
||||
|
||||
public sealed override Task CloseAsync()
|
||||
public sealed override async Task CloseAsync()
|
||||
{
|
||||
return this.OnCloseAsync();
|
||||
await this.InnerReceiver.CloseAsync().ConfigureAwait(false);
|
||||
await this.OnCloseAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -143,7 +149,7 @@ namespace Microsoft.Azure.ServiceBus
|
|||
/// <returns>A Task that completes when the send operations is done.</returns>
|
||||
public Task SendAsync(BrokeredMessage brokeredMessage)
|
||||
{
|
||||
return this.SendAsync(new[] { brokeredMessage });
|
||||
return this.SendAsync(new BrokeredMessage[] { brokeredMessage });
|
||||
}
|
||||
|
||||
public async Task SendAsync(IEnumerable<BrokeredMessage> brokeredMessages)
|
||||
|
@ -183,9 +189,33 @@ namespace Microsoft.Azure.ServiceBus
|
|||
}
|
||||
}
|
||||
|
||||
public async Task<BrokeredMessage> ReceiveBySequenceNumberAsync(long sequenceNumber)
|
||||
{
|
||||
IList<BrokeredMessage> messages = await this.ReceiveBySequenceNumberAsync(new long[] { sequenceNumber });
|
||||
if (messages != null && messages.Count > 0)
|
||||
{
|
||||
return messages[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<IList<BrokeredMessage>> ReceiveBySequenceNumberAsync(IEnumerable<long> sequenceNumbers)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await this.InnerReceiver.ReceiveBySequenceNumberAsync(sequenceNumbers).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// TODO: Log Receive Exception
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public Task CompleteAsync(Guid lockToken)
|
||||
{
|
||||
return this.CompleteAsync(new[] { lockToken });
|
||||
return this.CompleteAsync(new Guid[] { lockToken });
|
||||
}
|
||||
|
||||
public async Task CompleteAsync(IEnumerable<Guid> lockTokens)
|
||||
|
@ -203,7 +233,7 @@ namespace Microsoft.Azure.ServiceBus
|
|||
|
||||
public Task AbandonAsync(Guid lockToken)
|
||||
{
|
||||
return this.AbandonAsync(new[] { lockToken });
|
||||
return this.AbandonAsync(new Guid[] { lockToken });
|
||||
}
|
||||
|
||||
public async Task AbandonAsync(IEnumerable<Guid> lockTokens)
|
||||
|
@ -219,9 +249,30 @@ namespace Microsoft.Azure.ServiceBus
|
|||
}
|
||||
}
|
||||
|
||||
public Task<MessageSession> AcceptMessageSessionAsync()
|
||||
{
|
||||
return this.AcceptMessageSessionAsync(null);
|
||||
}
|
||||
|
||||
public async Task<MessageSession> AcceptMessageSessionAsync(string sessionId)
|
||||
{
|
||||
MessageSession session = null;
|
||||
try
|
||||
{
|
||||
session = await this.OnAcceptMessageSessionAsync(sessionId).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// TODO: Log Complete Exception
|
||||
throw;
|
||||
}
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
public Task DeferAsync(Guid lockToken)
|
||||
{
|
||||
return this.DeferAsync(new[] { lockToken });
|
||||
return this.DeferAsync(new Guid[] { lockToken });
|
||||
}
|
||||
|
||||
public async Task DeferAsync(IEnumerable<Guid> lockTokens)
|
||||
|
@ -239,7 +290,7 @@ namespace Microsoft.Azure.ServiceBus
|
|||
|
||||
public Task DeadLetterAsync(Guid lockToken)
|
||||
{
|
||||
return this.DeadLetterAsync(new[] { lockToken });
|
||||
return this.DeadLetterAsync(new Guid[] { lockToken });
|
||||
}
|
||||
|
||||
public async Task DeadLetterAsync(IEnumerable<Guid> lockTokens)
|
||||
|
@ -255,19 +306,34 @@ namespace Microsoft.Azure.ServiceBus
|
|||
}
|
||||
}
|
||||
|
||||
internal MessageSender CreateMessageSender()
|
||||
public async Task<DateTime> RenewMessageLockAsync(Guid lockToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await this.InnerReceiver.RenewLockAsync(lockToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// TODO: Log Complete Exception
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
protected MessageSender CreateMessageSender()
|
||||
{
|
||||
return this.OnCreateMessageSender();
|
||||
}
|
||||
|
||||
internal MessageReceiver CreateMessageReceiver()
|
||||
protected MessageReceiver CreateMessageReceiver()
|
||||
{
|
||||
return this.OnCreateMessageReceiver();
|
||||
}
|
||||
|
||||
internal abstract MessageSender OnCreateMessageSender();
|
||||
protected abstract MessageSender OnCreateMessageSender();
|
||||
|
||||
internal abstract MessageReceiver OnCreateMessageReceiver();
|
||||
protected abstract MessageReceiver OnCreateMessageReceiver();
|
||||
|
||||
protected abstract Task<MessageSession> OnAcceptMessageSessionAsync(string sessionId);
|
||||
|
||||
protected abstract Task OnCloseAsync();
|
||||
}
|
||||
|
|
|
@ -59,6 +59,15 @@ namespace Microsoft.Azure.ServiceBus {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to No session-id was specified for a session receiver..
|
||||
/// </summary>
|
||||
public static string AmqpFieldSessionId {
|
||||
get {
|
||||
return ResourceManager.GetString("AmqpFieldSessionId", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The received message (delivery-id:{0}, size:{1} bytes) exceeds the limit ({2} bytes) currently allowed on the link..
|
||||
/// </summary>
|
||||
|
|
|
@ -117,6 +117,9 @@
|
|||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="AmqpFieldSessionId" xml:space="preserve">
|
||||
<value>No session-id was specified for a session receiver.</value>
|
||||
</data>
|
||||
<data name="AmqpMessageSizeExceeded" xml:space="preserve">
|
||||
<value>The received message (delivery-id:{0}, size:{1} bytes) exceeds the limit ({2} bytes) currently allowed on the link.</value>
|
||||
</data>
|
||||
|
|
|
@ -0,0 +1,290 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Azure.ServiceBus
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.ServiceBus.Primitives;
|
||||
|
||||
public abstract class SubscriptionClient : ClientEntity
|
||||
{
|
||||
MessageReceiver innerReceiver;
|
||||
|
||||
protected SubscriptionClient(ServiceBusConnection serviceBusConnection, string topicPath, string name, ReceiveMode receiveMode)
|
||||
: base($"{nameof(SubscriptionClient)}{ClientEntity.GetNextId()}({name})")
|
||||
{
|
||||
this.ServiceBusConnection = serviceBusConnection;
|
||||
this.TopicPath = topicPath;
|
||||
this.Name = name;
|
||||
this.SubscriptionPath = EntityNameHelper.FormatSubscriptionPath(this.TopicPath, this.Name);
|
||||
this.Mode = receiveMode;
|
||||
}
|
||||
|
||||
public string TopicPath { get; private set; }
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public ReceiveMode Mode { get; private set; }
|
||||
|
||||
public int PrefetchCount
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.InnerReceiver.PrefetchCount;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
this.InnerReceiver.PrefetchCount = value;
|
||||
}
|
||||
}
|
||||
|
||||
internal string SubscriptionPath { get; private set; }
|
||||
|
||||
internal MessageReceiver InnerReceiver
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.innerReceiver == null)
|
||||
{
|
||||
lock (this.ThisLock)
|
||||
{
|
||||
if (this.innerReceiver == null)
|
||||
{
|
||||
this.innerReceiver = this.CreateMessageReceiver();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.innerReceiver;
|
||||
}
|
||||
}
|
||||
|
||||
protected object ThisLock { get; } = new object();
|
||||
|
||||
protected ServiceBusConnection ServiceBusConnection { get; }
|
||||
|
||||
public static SubscriptionClient CreateFromConnectionString(string topicEntityConnectionString, string subscriptionName)
|
||||
{
|
||||
return CreateFromConnectionString(topicEntityConnectionString, subscriptionName, ReceiveMode.PeekLock);
|
||||
}
|
||||
|
||||
public static SubscriptionClient CreateFromConnectionString(string topicEntityConnectionString, string subscriptionName, ReceiveMode mode)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(topicEntityConnectionString))
|
||||
{
|
||||
throw Fx.Exception.ArgumentNullOrWhiteSpace(nameof(topicEntityConnectionString));
|
||||
}
|
||||
|
||||
ServiceBusEntityConnection topicConnection = new ServiceBusEntityConnection(topicEntityConnectionString);
|
||||
return topicConnection.CreateSubscriptionClient(topicConnection.EntityPath, subscriptionName, mode);
|
||||
}
|
||||
|
||||
public static SubscriptionClient Create(ServiceBusNamespaceConnection namespaceConnection, string topicPath, string subscriptionName)
|
||||
{
|
||||
return SubscriptionClient.Create(namespaceConnection, topicPath, subscriptionName, ReceiveMode.PeekLock);
|
||||
}
|
||||
|
||||
public static SubscriptionClient Create(ServiceBusNamespaceConnection namespaceConnection, string topicPath, string subscriptionName, ReceiveMode mode)
|
||||
{
|
||||
if (namespaceConnection == null)
|
||||
{
|
||||
throw Fx.Exception.Argument(nameof(namespaceConnection), "Namespace Connection is null. Create a connection using the NamespaceConnection class");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(topicPath))
|
||||
{
|
||||
throw Fx.Exception.Argument(nameof(namespaceConnection), "Topic Path is null");
|
||||
}
|
||||
|
||||
return namespaceConnection.CreateSubscriptionClient(topicPath, subscriptionName, mode);
|
||||
}
|
||||
|
||||
public static SubscriptionClient Create(ServiceBusEntityConnection topicConnection, string subscriptionName)
|
||||
{
|
||||
return SubscriptionClient.Create(topicConnection, subscriptionName, ReceiveMode.PeekLock);
|
||||
}
|
||||
|
||||
public static SubscriptionClient Create(ServiceBusEntityConnection topicConnection, string subscriptionName, ReceiveMode mode)
|
||||
{
|
||||
if (topicConnection == null)
|
||||
{
|
||||
throw Fx.Exception.Argument(nameof(topicConnection), "Namespace Connection is null. Create a connection using the NamespaceConnection class");
|
||||
}
|
||||
|
||||
return topicConnection.CreateSubscriptionClient(topicConnection.EntityPath, subscriptionName, mode);
|
||||
}
|
||||
|
||||
public sealed override async Task CloseAsync()
|
||||
{
|
||||
await this.OnCloseAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<BrokeredMessage> ReceiveAsync()
|
||||
{
|
||||
IList<BrokeredMessage> messages = await this.ReceiveAsync(1).ConfigureAwait(false);
|
||||
if (messages != null && messages.Count > 0)
|
||||
{
|
||||
return messages[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<IList<BrokeredMessage>> ReceiveAsync(int maxMessageCount)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await this.InnerReceiver.ReceiveAsync(maxMessageCount).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// TODO: Log Receive Exception
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<BrokeredMessage> ReceiveBySequenceNumberAsync(long sequenceNumber)
|
||||
{
|
||||
IList<BrokeredMessage> messages = await this.ReceiveBySequenceNumberAsync(new long[] { sequenceNumber });
|
||||
if (messages != null && messages.Count > 0)
|
||||
{
|
||||
return messages[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<IList<BrokeredMessage>> ReceiveBySequenceNumberAsync(IEnumerable<long> sequenceNumbers)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await this.InnerReceiver.ReceiveBySequenceNumberAsync(sequenceNumbers).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// TODO: Log Receive Exception
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public Task CompleteAsync(Guid lockToken)
|
||||
{
|
||||
return this.CompleteAsync(new Guid[] { lockToken });
|
||||
}
|
||||
|
||||
public async Task CompleteAsync(IEnumerable<Guid> lockTokens)
|
||||
{
|
||||
try
|
||||
{
|
||||
await this.InnerReceiver.CompleteAsync(lockTokens).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// TODO: Log Complete Exception
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public Task AbandonAsync(Guid lockToken)
|
||||
{
|
||||
return this.AbandonAsync(new Guid[] { lockToken });
|
||||
}
|
||||
|
||||
public async Task AbandonAsync(IEnumerable<Guid> lockTokens)
|
||||
{
|
||||
try
|
||||
{
|
||||
await this.InnerReceiver.AbandonAsync(lockTokens).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// TODO: Log Complete Exception
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public Task<MessageSession> AcceptMessageSessionAsync()
|
||||
{
|
||||
return this.AcceptMessageSessionAsync(null);
|
||||
}
|
||||
|
||||
public async Task<MessageSession> AcceptMessageSessionAsync(string sessionId)
|
||||
{
|
||||
MessageSession session = null;
|
||||
try
|
||||
{
|
||||
session = await this.OnAcceptMessageSessionAsync(sessionId).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// TODO: Log Complete Exception
|
||||
throw;
|
||||
}
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
public Task DeferAsync(Guid lockToken)
|
||||
{
|
||||
return this.DeferAsync(new Guid[] { lockToken });
|
||||
}
|
||||
|
||||
public async Task DeferAsync(IEnumerable<Guid> lockTokens)
|
||||
{
|
||||
try
|
||||
{
|
||||
await this.InnerReceiver.DeferAsync(lockTokens).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// TODO: Log Complete Exception
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public Task DeadLetterAsync(Guid lockToken)
|
||||
{
|
||||
return this.DeadLetterAsync(new Guid[] { lockToken });
|
||||
}
|
||||
|
||||
public async Task DeadLetterAsync(IEnumerable<Guid> lockTokens)
|
||||
{
|
||||
try
|
||||
{
|
||||
await this.InnerReceiver.DeadLetterAsync(lockTokens).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// TODO: Log Complete Exception
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<DateTime> RenewMessageLockAsync(Guid lockToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await this.InnerReceiver.RenewLockAsync(lockToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// TODO: Log Complete Exception
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
protected MessageReceiver CreateMessageReceiver()
|
||||
{
|
||||
return this.OnCreateMessageReceiver();
|
||||
}
|
||||
|
||||
protected abstract MessageReceiver OnCreateMessageReceiver();
|
||||
|
||||
protected abstract Task<MessageSession> OnAcceptMessageSessionAsync(string sessionId);
|
||||
|
||||
protected abstract Task OnCloseAsync();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Azure.ServiceBus
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.ServiceBus.Primitives;
|
||||
|
||||
public abstract class TopicClient : ClientEntity
|
||||
{
|
||||
MessageSender innerSender;
|
||||
|
||||
protected TopicClient(ServiceBusConnection serviceBusConnection, string entityPath)
|
||||
: base($"{nameof(TopicClient)}{ClientEntity.GetNextId()}({entityPath})")
|
||||
{
|
||||
this.ServiceBusConnection = serviceBusConnection;
|
||||
this.TopicName = entityPath;
|
||||
}
|
||||
|
||||
public string TopicName { get; }
|
||||
|
||||
internal MessageSender InnerSender
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.innerSender == null)
|
||||
{
|
||||
lock (this.ThisLock)
|
||||
{
|
||||
if (this.innerSender == null)
|
||||
{
|
||||
this.innerSender = this.CreateMessageSender();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.innerSender;
|
||||
}
|
||||
}
|
||||
|
||||
protected ServiceBusConnection ServiceBusConnection { get; }
|
||||
|
||||
protected object ThisLock { get; } = new object();
|
||||
|
||||
public static TopicClient CreateFromConnectionString(string entityConnectionString)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(entityConnectionString))
|
||||
{
|
||||
throw Fx.Exception.ArgumentNullOrWhiteSpace(nameof(entityConnectionString));
|
||||
}
|
||||
|
||||
ServiceBusEntityConnection entityConnection = new ServiceBusEntityConnection(entityConnectionString);
|
||||
return entityConnection.CreateTopicClient(entityConnection.EntityPath);
|
||||
}
|
||||
|
||||
public static TopicClient Create(ServiceBusNamespaceConnection namespaceConnection, string entityPath)
|
||||
{
|
||||
if (namespaceConnection == null)
|
||||
{
|
||||
throw Fx.Exception.Argument(nameof(namespaceConnection), "Namespace Connection is null. Create a connection using the NamespaceConnection class");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(entityPath))
|
||||
{
|
||||
throw Fx.Exception.Argument(nameof(namespaceConnection), "Entity Path is null");
|
||||
}
|
||||
|
||||
return namespaceConnection.CreateTopicClient(entityPath);
|
||||
}
|
||||
|
||||
public static TopicClient Create(ServiceBusEntityConnection entityConnection)
|
||||
{
|
||||
return TopicClient.Create(entityConnection, ReceiveMode.PeekLock);
|
||||
}
|
||||
|
||||
public static TopicClient Create(ServiceBusEntityConnection entityConnection, ReceiveMode mode)
|
||||
{
|
||||
if (entityConnection == null)
|
||||
{
|
||||
throw Fx.Exception.Argument(nameof(entityConnection), "Namespace Connection is null. Create a connection using the NamespaceConnection class");
|
||||
}
|
||||
|
||||
return entityConnection.CreateTopicClient(entityConnection.EntityPath);
|
||||
}
|
||||
|
||||
public sealed override async Task CloseAsync()
|
||||
{
|
||||
await this.OnCloseAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send <see cref="BrokeredMessage"/> to Queue.
|
||||
/// <see cref="SendAsync(BrokeredMessage)"/> sends the <see cref="BrokeredMessage"/> to a Service Gateway, which in-turn will forward the BrokeredMessage to the queue.
|
||||
/// </summary>
|
||||
/// <param name="brokeredMessage">the <see cref="BrokeredMessage"/> to be sent.</param>
|
||||
/// <returns>A Task that completes when the send operations is done.</returns>
|
||||
public Task SendAsync(BrokeredMessage brokeredMessage)
|
||||
{
|
||||
return this.SendAsync(new[] { brokeredMessage });
|
||||
}
|
||||
|
||||
public async Task SendAsync(IEnumerable<BrokeredMessage> brokeredMessages)
|
||||
{
|
||||
try
|
||||
{
|
||||
await this.InnerSender.SendAsync(brokeredMessages).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// TODO: Log Send Exception
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
protected MessageSender CreateMessageSender()
|
||||
{
|
||||
return this.OnCreateMessageSender();
|
||||
}
|
||||
|
||||
protected abstract MessageSender OnCreateMessageSender();
|
||||
|
||||
protected abstract Task OnCloseAsync();
|
||||
}
|
||||
}
|
|
@ -19,15 +19,6 @@ namespace Microsoft.Azure.ServiceBus.UnitTests
|
|||
}
|
||||
}
|
||||
|
||||
public class When_BrokeredMessage_message_is_given_a_null_id_generator
|
||||
{
|
||||
[Fact]
|
||||
public void Should_throw_an_exception()
|
||||
{
|
||||
Assert.Throws<ArgumentNullException>(() => BrokeredMessage.SetMessageIdGenerator(null));
|
||||
}
|
||||
}
|
||||
|
||||
public class When_BrokeredMessage_id_generator_throws
|
||||
{
|
||||
[Fact]
|
||||
|
@ -42,6 +33,8 @@ namespace Microsoft.Azure.ServiceBus.UnitTests
|
|||
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => new BrokeredMessage());
|
||||
Assert.Equal(exceptionToThrow, exception.InnerException);
|
||||
|
||||
BrokeredMessage.SetMessageIdGenerator(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -58,6 +51,8 @@ namespace Microsoft.Azure.ServiceBus.UnitTests
|
|||
|
||||
Assert.Equal("id-1", message1.MessageId);
|
||||
Assert.Equal("id-2", message2.MessageId);
|
||||
|
||||
BrokeredMessage.SetMessageIdGenerator(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Azure.ServiceBus.UnitTests
|
||||
{
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.ServiceBus.Primitives;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
public class NonPartitionedQueueClientTests : QueueClientTestBase
|
||||
{
|
||||
public NonPartitionedQueueClientTests(ITestOutputHelper output)
|
||||
: base(output)
|
||||
{
|
||||
this.ConnectionString = Environment.GetEnvironmentVariable("NONPARTITIONEDQUEUECLIENTCONNECTIONSTRING");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(this.ConnectionString))
|
||||
{
|
||||
throw new InvalidOperationException("QUEUECLIENTCONNECTIONSTRING environment variable was not found!");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
async Task PeekLockTest()
|
||||
{
|
||||
await this.QueueClientPeekLockTestCase(messageCount: 10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
async Task ReceiveDeleteTest()
|
||||
{
|
||||
await this.QueueClientReceiveDeleteTestCase(messageCount: 10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
async Task PeekLockWithAbandonTest()
|
||||
{
|
||||
await this.QueueClientPeekLockWithAbandonTestCase(messageCount: 10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
async Task PeekLockWithDeadLetterTest()
|
||||
{
|
||||
await this.QueueClientPeekLockWithDeadLetterTestCase(messageCount: 10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
async Task PeekLockDeferTest()
|
||||
{
|
||||
await this.QueueClientPeekLockDeferTestCase(messageCount: 10);
|
||||
}
|
||||
|
||||
// Request Response Tests
|
||||
[Fact]
|
||||
async Task BasicRenewLockTest()
|
||||
{
|
||||
await this.QueueClientRenewLockTestCase(messageCount: 1);
|
||||
}
|
||||
|
||||
// TODO: Add this to BrokeredMessageTests after merge
|
||||
[Fact]
|
||||
async Task BrokeredMessageOperationsTest()
|
||||
{
|
||||
// Create QueueClient with ReceiveDelete,
|
||||
// Send and Receive a message, Try to Complete/Abandon/Defer/DeadLetter should throw InvalidOperationException()
|
||||
QueueClient queueClient = QueueClient.CreateFromConnectionString(this.ConnectionString, ReceiveMode.ReceiveAndDelete);
|
||||
await TestUtility.SendMessagesAsync(queueClient.InnerSender, 1, this.Output);
|
||||
BrokeredMessage message = await queueClient.ReceiveAsync();
|
||||
Assert.NotNull((object)message);
|
||||
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(async () => await message.CompleteAsync());
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(async () => await message.AbandonAsync());
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(async () => await message.DeferAsync());
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(async () => await message.DeadLetterAsync());
|
||||
|
||||
// Create a PeekLock queueClient and do rest of the operations
|
||||
// Send a Message, Receive/ Abandon and Complete it using BrokeredMessage methods
|
||||
queueClient = QueueClient.CreateFromConnectionString(this.ConnectionString);
|
||||
await TestUtility.SendMessagesAsync(queueClient.InnerSender, 1, this.Output);
|
||||
message = await queueClient.ReceiveAsync();
|
||||
Assert.NotNull((object)message);
|
||||
await message.AbandonAsync();
|
||||
await Task.Delay(TimeSpan.FromMilliseconds(100));
|
||||
message = await queueClient.ReceiveAsync();
|
||||
await message.CompleteAsync();
|
||||
|
||||
// Send a Message, Receive / DeadLetter using BrokeredMessage methods
|
||||
await TestUtility.SendMessagesAsync(queueClient.InnerSender, 1, this.Output);
|
||||
message = await queueClient.ReceiveAsync();
|
||||
await message.DeadLetterAsync();
|
||||
ServiceBusConnectionStringBuilder builder = new ServiceBusConnectionStringBuilder(this.ConnectionString);
|
||||
builder.EntityPath = EntityNameHelper.FormatDeadLetterPath(queueClient.QueueName);
|
||||
QueueClient deadLetterQueueClient = QueueClient.CreateFromConnectionString(builder.ToString());
|
||||
message = await deadLetterQueueClient.ReceiveAsync();
|
||||
await message.CompleteAsync();
|
||||
|
||||
// Send a Message, Receive/Defer using BrokeredMessage methods
|
||||
await TestUtility.SendMessagesAsync(queueClient.InnerSender, 1, this.Output);
|
||||
message = await queueClient.ReceiveAsync();
|
||||
long deferredSequenceNumber = message.SequenceNumber;
|
||||
await message.DeferAsync();
|
||||
|
||||
var deferredMessage = await queueClient.ReceiveBySequenceNumberAsync(deferredSequenceNumber);
|
||||
await deferredMessage.CompleteAsync();
|
||||
|
||||
queueClient.Close();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Azure.ServiceBus.UnitTests
|
||||
{
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
public class NonPartitionedQueueSessionTests : QueueSessionTestBase
|
||||
{
|
||||
public NonPartitionedQueueSessionTests(ITestOutputHelper output)
|
||||
: base(output)
|
||||
{
|
||||
this.ConnectionString = Environment.GetEnvironmentVariable("NONPARTITIONEDSESSIONQUEUECONNECTIONSTRING");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(this.ConnectionString))
|
||||
{
|
||||
throw new InvalidOperationException("SESSIONQUEUECLIENTCONNECTIONSTRING environment variable was not found!");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
async Task SessionTest()
|
||||
{
|
||||
await this.SessionTestCase();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
async Task GetAndSetSessionStateTest()
|
||||
{
|
||||
await this.GetAndSetSessionStateTestCase();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
async Task SessionRenewLockTest()
|
||||
{
|
||||
await this.SessionRenewLockTestCase();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Azure.ServiceBus.UnitTests
|
||||
{
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.ServiceBus.Primitives;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
public class NonPartitionedTopicClientTests : TopicClientTestBase
|
||||
{
|
||||
public NonPartitionedTopicClientTests(ITestOutputHelper output)
|
||||
: base(output)
|
||||
{
|
||||
this.ConnectionString = Environment.GetEnvironmentVariable("NONPARTITIONEDTOPICCLIENTCONNECTIONSTRING");
|
||||
this.SubscriptionName = Environment.GetEnvironmentVariable("SUBSCRIPTIONNAME");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(this.ConnectionString))
|
||||
{
|
||||
throw new InvalidOperationException("TopicCLIENTCONNECTIONSTRING environment variable was not found!");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(this.SubscriptionName))
|
||||
{
|
||||
throw new InvalidOperationException("SUBSCRIPTIONNAME environment variable was not found!");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
async Task PeekLockTest()
|
||||
{
|
||||
await this.TopicClientPeekLockTestCase(messageCount: 10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
async Task ReceiveDeleteTest()
|
||||
{
|
||||
await this.TopicClientReceiveDeleteTestCase(messageCount: 10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
async Task PeekLockWithAbandonTest()
|
||||
{
|
||||
await this.TopicClientPeekLockWithAbandonTestCase(messageCount: 10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
async Task PeekLockWithDeadLetterTest()
|
||||
{
|
||||
await this.TopicClientPeekLockWithDeadLetterTestCase(messageCount: 10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
async Task PeekLockDeferTest()
|
||||
{
|
||||
await this.TopicClientPeekLockDeferTestCase(messageCount: 10);
|
||||
}
|
||||
|
||||
// Request Response Tests
|
||||
[Fact]
|
||||
async Task BasicRenewLockTest()
|
||||
{
|
||||
await this.TopicClientRenewLockTestCase(messageCount: 1);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Azure.ServiceBus.UnitTests
|
||||
{
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
public class NonPartitionedTopicSessionTests : TopicSessionTestBase
|
||||
{
|
||||
public NonPartitionedTopicSessionTests(ITestOutputHelper output)
|
||||
: base(output)
|
||||
{
|
||||
this.ConnectionString = Environment.GetEnvironmentVariable("NONPARTITIONEDTOPICCLIENTCONNECTIONSTRING");
|
||||
this.SubscriptionName = Environment.GetEnvironmentVariable("SESSIONFULSUBSCRIPTIONNAME");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(this.ConnectionString))
|
||||
{
|
||||
throw new InvalidOperationException("TOPICCLIENTCONNECTIONSTRING environment variable was not found!");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(this.SubscriptionName))
|
||||
{
|
||||
throw new InvalidOperationException("SUBSCRIPTIONNAME environment variable was not found!");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
async Task SessionTest()
|
||||
{
|
||||
await this.SessionTestCase();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
async Task GetAndSetSessionStateTest()
|
||||
{
|
||||
await this.GetAndSetSessionStateTestCase();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
async Task SessionRenewLockTest()
|
||||
{
|
||||
await this.SessionRenewLockTestCase();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Azure.ServiceBus.UnitTests
|
||||
{
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.ServiceBus.Primitives;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
public class PartitionedQueueClientTests : QueueClientTestBase
|
||||
{
|
||||
public PartitionedQueueClientTests(ITestOutputHelper output)
|
||||
: base(output)
|
||||
{
|
||||
this.ConnectionString = Environment.GetEnvironmentVariable("PARTITIONEDQUEUECLIENTCONNECTIONSTRING");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(this.ConnectionString))
|
||||
{
|
||||
throw new InvalidOperationException("QUEUECLIENTCONNECTIONSTRING environment variable was not found!");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
async Task PeekLockTest()
|
||||
{
|
||||
await this.QueueClientPeekLockTestCase(messageCount: 10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
async Task ReceiveDeleteTest()
|
||||
{
|
||||
await this.QueueClientReceiveDeleteTestCase(messageCount: 10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
async Task PeekLockWithAbandonTest()
|
||||
{
|
||||
await this.QueueClientPeekLockWithAbandonTestCase(messageCount: 10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
async Task PeekLockWithDeadLetterTest()
|
||||
{
|
||||
await this.QueueClientPeekLockWithDeadLetterTestCase(messageCount: 10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
async Task PeekLockDeferTest()
|
||||
{
|
||||
await this.QueueClientPeekLockDeferTestCase(messageCount: 10);
|
||||
}
|
||||
|
||||
// Request Response Tests
|
||||
[Fact]
|
||||
async Task BasicRenewLockTest()
|
||||
{
|
||||
await this.QueueClientRenewLockTestCase(messageCount: 1);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Azure.ServiceBus.UnitTests
|
||||
{
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
public class PartitionedQueueSessionTests : QueueSessionTestBase
|
||||
{
|
||||
public PartitionedQueueSessionTests(ITestOutputHelper output)
|
||||
: base(output)
|
||||
{
|
||||
this.ConnectionString = Environment.GetEnvironmentVariable("PARTITIONEDSESSIONQUEUECONNECTIONSTRING");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(this.ConnectionString))
|
||||
{
|
||||
throw new InvalidOperationException("SESSIONQUEUECLIENTCONNECTIONSTRING environment variable was not found!");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
async Task SessionTest()
|
||||
{
|
||||
await this.SessionTestCase();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
async Task GetAndSetSessionStateTest()
|
||||
{
|
||||
await this.GetAndSetSessionStateTestCase();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
async Task SessionRenewLockTest()
|
||||
{
|
||||
await this.SessionRenewLockTestCase();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Azure.ServiceBus.UnitTests
|
||||
{
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.ServiceBus.Primitives;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
public class PartitionedTopicClientTests : TopicClientTestBase
|
||||
{
|
||||
public PartitionedTopicClientTests(ITestOutputHelper output)
|
||||
: base(output)
|
||||
{
|
||||
this.ConnectionString = Environment.GetEnvironmentVariable("PARTITIONEDTOPICCLIENTCONNECTIONSTRING");
|
||||
this.SubscriptionName = Environment.GetEnvironmentVariable("SUBSCRIPTIONNAME");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(this.ConnectionString))
|
||||
{
|
||||
throw new InvalidOperationException("TOPICCLIENTCONNECTIONSTRING environment variable was not found!");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(this.SubscriptionName))
|
||||
{
|
||||
throw new InvalidOperationException("SUBSCRIPTIONNAME environment variable was not found!");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
async Task PeekLockTest()
|
||||
{
|
||||
await this.TopicClientPeekLockTestCase(messageCount: 10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
async Task ReceiveDeleteTest()
|
||||
{
|
||||
await this.TopicClientReceiveDeleteTestCase(messageCount: 10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
async Task PeekLockWithAbandonTest()
|
||||
{
|
||||
await this.TopicClientPeekLockWithAbandonTestCase(messageCount: 10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
async Task PeekLockWithDeadLetterTest()
|
||||
{
|
||||
await this.TopicClientPeekLockWithDeadLetterTestCase(messageCount: 10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
async Task PeekLockDeferTest()
|
||||
{
|
||||
await this.TopicClientPeekLockDeferTestCase(messageCount: 10);
|
||||
}
|
||||
|
||||
// Request Response Tests
|
||||
[Fact]
|
||||
async Task BasicRenewLockTest()
|
||||
{
|
||||
await this.TopicClientRenewLockTestCase(messageCount: 1);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Azure.ServiceBus.UnitTests
|
||||
{
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
public class PartitionedTopicSessionTests : TopicSessionTestBase
|
||||
{
|
||||
public PartitionedTopicSessionTests(ITestOutputHelper output)
|
||||
: base(output)
|
||||
{
|
||||
this.ConnectionString = Environment.GetEnvironmentVariable("PARTITIONEDTOPICCLIENTCONNECTIONSTRING");
|
||||
this.SubscriptionName = Environment.GetEnvironmentVariable("SESSIONFULSUBSCRIPTIONNAME");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(this.ConnectionString))
|
||||
{
|
||||
throw new InvalidOperationException("TOPICCLIENTCONNECTIONSTRING environment variable was not found!");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(this.SubscriptionName))
|
||||
{
|
||||
throw new InvalidOperationException("SUBSCRIPTIONNAME environment variable was not found!");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
async Task SessionTest()
|
||||
{
|
||||
await this.SessionTestCase();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
async Task GetAndSetSessionStateTest()
|
||||
{
|
||||
await this.GetAndSetSessionStateTestCase();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
async Task SessionRenewLockTest()
|
||||
{
|
||||
await this.SessionRenewLockTestCase();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using Xunit;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
|
@ -18,4 +19,6 @@ using System.Runtime.InteropServices;
|
|||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("bda98e20-3a26-4a5a-9371-cb02b73e3434")]
|
||||
[assembly: Guid("bda98e20-3a26-4a5a-9371-cb02b73e3434")]
|
||||
|
||||
[assembly: CollectionBehavior(DisableTestParallelization = true)]
|
|
@ -0,0 +1,104 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Azure.ServiceBus.UnitTests
|
||||
{
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.ServiceBus.Primitives;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
public abstract class QueueClientTestBase : SenderReceiverClientTestBase
|
||||
{
|
||||
protected QueueClientTestBase(ITestOutputHelper output)
|
||||
: base(output)
|
||||
{
|
||||
}
|
||||
|
||||
protected string ConnectionString { get; set; }
|
||||
|
||||
public async Task QueueClientPeekLockTestCase(int messageCount)
|
||||
{
|
||||
QueueClient queueClient = QueueClient.CreateFromConnectionString(this.ConnectionString);
|
||||
try
|
||||
{
|
||||
await this.PeekLockTestCase(queueClient.InnerSender, queueClient.InnerReceiver, messageCount);
|
||||
}
|
||||
finally
|
||||
{
|
||||
queueClient.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task QueueClientReceiveDeleteTestCase(int messageCount)
|
||||
{
|
||||
QueueClient queueClient = QueueClient.CreateFromConnectionString(this.ConnectionString, ReceiveMode.ReceiveAndDelete);
|
||||
try
|
||||
{
|
||||
await this.ReceiveDeleteTestCase(queueClient.InnerSender, queueClient.InnerReceiver, messageCount);
|
||||
}
|
||||
finally
|
||||
{
|
||||
queueClient.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task QueueClientPeekLockWithAbandonTestCase(int messageCount)
|
||||
{
|
||||
QueueClient queueClient = QueueClient.CreateFromConnectionString(this.ConnectionString);
|
||||
try
|
||||
{
|
||||
await this.PeekLockWithAbandonTestCase(queueClient.InnerSender, queueClient.InnerReceiver, messageCount);
|
||||
}
|
||||
finally
|
||||
{
|
||||
queueClient.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task QueueClientPeekLockWithDeadLetterTestCase(int messageCount)
|
||||
{
|
||||
QueueClient queueClient = QueueClient.CreateFromConnectionString(this.ConnectionString);
|
||||
|
||||
// Create DLQ Client To Receive DeadLetteredMessages
|
||||
ServiceBusConnectionStringBuilder builder = new ServiceBusConnectionStringBuilder(this.ConnectionString);
|
||||
builder.EntityPath = EntityNameHelper.FormatDeadLetterPath(queueClient.QueueName);
|
||||
QueueClient deadLetterQueueClient = QueueClient.CreateFromConnectionString(builder.ToString());
|
||||
|
||||
try
|
||||
{
|
||||
await this.PeekLockWithDeadLetterTestCase(queueClient.InnerSender, queueClient.InnerReceiver, deadLetterQueueClient.InnerReceiver, messageCount);
|
||||
}
|
||||
finally
|
||||
{
|
||||
deadLetterQueueClient.Close();
|
||||
queueClient.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task QueueClientPeekLockDeferTestCase(int messageCount)
|
||||
{
|
||||
QueueClient queueClient = QueueClient.CreateFromConnectionString(this.ConnectionString);
|
||||
try
|
||||
{
|
||||
await this.PeekLockDeferTestCase(queueClient.InnerSender, queueClient.InnerReceiver, messageCount);
|
||||
}
|
||||
finally
|
||||
{
|
||||
queueClient.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task QueueClientRenewLockTestCase(int messageCount)
|
||||
{
|
||||
QueueClient queueClient = QueueClient.CreateFromConnectionString(this.ConnectionString);
|
||||
try
|
||||
{
|
||||
await this.RenewLockTestCase(queueClient.InnerSender, queueClient.InnerReceiver, messageCount);
|
||||
}
|
||||
finally
|
||||
{
|
||||
queueClient.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,276 +0,0 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Azure.ServiceBus.UnitTests
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
public class QueueClientTests
|
||||
{
|
||||
const int MaxAttemptsCount = 5;
|
||||
readonly string connectionString;
|
||||
ITestOutputHelper output;
|
||||
|
||||
public QueueClientTests(ITestOutputHelper output)
|
||||
{
|
||||
this.output = output;
|
||||
this.connectionString = Environment.GetEnvironmentVariable("QUEUECLIENTCONNECTIONSTRING");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(this.connectionString))
|
||||
{
|
||||
throw new InvalidOperationException("QUEUECLIENTCONNECTIONSTRING environment variable was not found!");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
async Task BrokeredMessageOperationsTest()
|
||||
{
|
||||
// Create QueueClient with ReceiveDelete,
|
||||
// Send and Receive a message, Try to Complete/Abandon/Defer/DeadLetter should throw InvalidOperationException()
|
||||
QueueClient queueClient = QueueClient.Create(this.connectionString, ReceiveMode.ReceiveAndDelete);
|
||||
await this.SendMessagesAsync(queueClient, 1);
|
||||
BrokeredMessage message = await queueClient.ReceiveAsync();
|
||||
Assert.NotNull(message);
|
||||
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(async () => await message.CompleteAsync());
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(async () => await message.AbandonAsync());
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(async () => await message.DeferAsync());
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(async () => await message.DeadLetterAsync());
|
||||
|
||||
// Create a PeekLock queueClient and do rest of the operations
|
||||
// Send a Message, Receive/Abandon and Complete it using BrokeredMessage methods
|
||||
queueClient = QueueClient.Create(this.connectionString);
|
||||
await this.SendMessagesAsync(queueClient, 1);
|
||||
message = await queueClient.ReceiveAsync();
|
||||
Assert.NotNull(message);
|
||||
await message.AbandonAsync();
|
||||
await Task.Delay(TimeSpan.FromMilliseconds(100));
|
||||
message = await queueClient.ReceiveAsync();
|
||||
await message.CompleteAsync();
|
||||
|
||||
// Send a Message, Receive/DeadLetter using BrokeredMessage methods
|
||||
await this.SendMessagesAsync(queueClient, 1);
|
||||
message = await queueClient.ReceiveAsync();
|
||||
await message.DeadLetterAsync();
|
||||
string entityPath = EntityNameHelper.FormatDeadLetterPath(queueClient.QueueName);
|
||||
QueueClient deadLetterQueueClient = QueueClient.Create(this.connectionString, entityPath);
|
||||
message = await deadLetterQueueClient.ReceiveAsync();
|
||||
await message.CompleteAsync();
|
||||
|
||||
// Send a Message, Receive/Defer using BrokeredMessage methods
|
||||
await this.SendMessagesAsync(queueClient, 1);
|
||||
message = await queueClient.ReceiveAsync();
|
||||
await message.DeferAsync();
|
||||
|
||||
// TODO: Once ReceivebySequence is implemented, Receive and Complete this message
|
||||
}
|
||||
|
||||
[Fact]
|
||||
async Task BasicPeekLockTest()
|
||||
{
|
||||
const int MessageCount = 10;
|
||||
|
||||
// Create QueueClient With PeekLock
|
||||
QueueClient queueClient = QueueClient.Create(this.connectionString);
|
||||
|
||||
// Send messages
|
||||
await this.SendMessagesAsync(queueClient, MessageCount);
|
||||
|
||||
// Receive messages
|
||||
IEnumerable<BrokeredMessage> receivedMessages = await this.ReceiveMessagesAsync(queueClient, MessageCount);
|
||||
|
||||
// Complete Messages
|
||||
await this.CompleteMessagesAsync(queueClient, receivedMessages);
|
||||
|
||||
Assert.True(receivedMessages.Count() == MessageCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
async Task BasicReceiveDeleteTest()
|
||||
{
|
||||
const int MessageCount = 10;
|
||||
|
||||
// Create QueueClient With ReceiveAndDelete
|
||||
QueueClient queueClient = QueueClient.Create(this.connectionString, ReceiveMode.ReceiveAndDelete);
|
||||
|
||||
// Send messages
|
||||
await this.SendMessagesAsync(queueClient, MessageCount);
|
||||
|
||||
// Receive messages
|
||||
IEnumerable<BrokeredMessage> receivedMessages = await this.ReceiveMessagesAsync(queueClient, MessageCount);
|
||||
|
||||
Assert.True(receivedMessages.Count() == MessageCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
async Task PeekLockWithAbandonTest()
|
||||
{
|
||||
const int MessageCount = 10;
|
||||
|
||||
// Create QueueClient With PeekLock
|
||||
QueueClient queueClient = QueueClient.Create(this.connectionString);
|
||||
|
||||
// Send messages
|
||||
await this.SendMessagesAsync(queueClient, MessageCount);
|
||||
|
||||
// Receive 5 messages and Abandon them
|
||||
int abandonMessagesCount = 5;
|
||||
IEnumerable<BrokeredMessage> receivedMessages = await this.ReceiveMessagesAsync(queueClient, abandonMessagesCount);
|
||||
Assert.True(receivedMessages.Count() == abandonMessagesCount);
|
||||
|
||||
await this.AbandonMessagesAsync(queueClient, receivedMessages);
|
||||
|
||||
// Receive all 10 messages, 5 of them should have DeliveryCount = 2
|
||||
receivedMessages = await this.ReceiveMessagesAsync(queueClient, MessageCount);
|
||||
Assert.True(receivedMessages.Count() == MessageCount);
|
||||
|
||||
// 5 of these messages should have deliveryCount = 2
|
||||
int messagesWithDeliveryCount2 = receivedMessages.Count(message => message.DeliveryCount == 2);
|
||||
Assert.True(messagesWithDeliveryCount2 == abandonMessagesCount);
|
||||
|
||||
// Complete Messages
|
||||
await this.CompleteMessagesAsync(queueClient, receivedMessages);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
async Task PeekLockWithDeadLetterTest()
|
||||
{
|
||||
const int MessageCount = 10;
|
||||
IEnumerable<BrokeredMessage> receivedMessages;
|
||||
|
||||
// Create QueueClient With PeekLock
|
||||
QueueClient queueClient = QueueClient.Create(this.connectionString);
|
||||
|
||||
// Send messages
|
||||
await this.SendMessagesAsync(queueClient, MessageCount);
|
||||
|
||||
// Receive 5 messages and Deadletter them
|
||||
int deadLetterMessageCount = 5;
|
||||
receivedMessages = await this.ReceiveMessagesAsync(queueClient, deadLetterMessageCount);
|
||||
Assert.True(receivedMessages.Count() == deadLetterMessageCount);
|
||||
|
||||
await this.DeadLetterMessagesAsync(queueClient, receivedMessages);
|
||||
|
||||
// Receive and Complete 5 other regular messages
|
||||
receivedMessages = await this.ReceiveMessagesAsync(queueClient, MessageCount - deadLetterMessageCount);
|
||||
await this.CompleteMessagesAsync(queueClient, receivedMessages);
|
||||
|
||||
// TODO: After implementing Receive(WithTimeSpan), Add Try another Receive, We should not get anything.
|
||||
// IEnumerable<BrokeredMessage> dummyMessages = await this.ReceiveMessagesAsync(queueClient, 10);
|
||||
// Assert.True(dummyMessages == null);
|
||||
|
||||
// Create DLQ Client and Receive DeadLetteredMessages
|
||||
var entityPath = EntityNameHelper.FormatDeadLetterPath(queueClient.QueueName);
|
||||
QueueClient deadLetterQueueClient = QueueClient.Create(this.connectionString, entityPath);
|
||||
|
||||
// Receive 5 DLQ messages and Complete them
|
||||
receivedMessages = await this.ReceiveMessagesAsync(deadLetterQueueClient, deadLetterMessageCount);
|
||||
Assert.True(receivedMessages.Count() == deadLetterMessageCount);
|
||||
await this.CompleteMessagesAsync(deadLetterQueueClient, receivedMessages);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
async Task PeekLockDeferTest()
|
||||
{
|
||||
const int MessageCount = 10;
|
||||
|
||||
// Create QueueClient With PeekLock
|
||||
QueueClient queueClient = QueueClient.Create(this.connectionString);
|
||||
|
||||
// Send messages
|
||||
await this.SendMessagesAsync(queueClient, MessageCount);
|
||||
|
||||
// Receive 5 messages And Defer them
|
||||
int deferMessagesCount = 5;
|
||||
IEnumerable<BrokeredMessage> receivedMessages = await this.ReceiveMessagesAsync(queueClient, deferMessagesCount);
|
||||
Assert.True(receivedMessages.Count() == deferMessagesCount);
|
||||
|
||||
await this.DeferMessagesAsync(queueClient, receivedMessages);
|
||||
|
||||
// Receive and Complete 5 other regular messages
|
||||
receivedMessages = await this.ReceiveMessagesAsync(queueClient, MessageCount - deferMessagesCount);
|
||||
await this.CompleteMessagesAsync(queueClient, receivedMessages);
|
||||
|
||||
Assert.True(receivedMessages.Count() == MessageCount - deferMessagesCount);
|
||||
|
||||
// Once Request response link is implemented, Call ReceiveBySequenceNumber() here and complete the rest of the 5 messages
|
||||
}
|
||||
|
||||
async Task SendMessagesAsync(QueueClient queueClient, int messageCount, [CallerMemberName] string invokingMethod = "")
|
||||
{
|
||||
if (messageCount == 0)
|
||||
{
|
||||
await Task.FromResult(false);
|
||||
}
|
||||
|
||||
List<BrokeredMessage> messagesToSend = new List<BrokeredMessage>();
|
||||
for (int i = 0; i < messageCount; i++)
|
||||
{
|
||||
BrokeredMessage message = new BrokeredMessage("test" + i);
|
||||
message.Label = $"test{i}-{invokingMethod}";
|
||||
messagesToSend.Add(message);
|
||||
}
|
||||
|
||||
await queueClient.SendAsync(messagesToSend);
|
||||
this.Log(string.Format("Sent {0} messages", messageCount));
|
||||
}
|
||||
|
||||
async Task<IEnumerable<BrokeredMessage>> ReceiveMessagesAsync(QueueClient queueClient, int messageCount)
|
||||
{
|
||||
int receiveAttempts = 0;
|
||||
List<BrokeredMessage> messagesToReturn = new List<BrokeredMessage>();
|
||||
|
||||
while (receiveAttempts++ < QueueClientTests.MaxAttemptsCount && messagesToReturn.Count < messageCount)
|
||||
{
|
||||
var messages = await queueClient.ReceiveAsync(messageCount);
|
||||
if (messages != null)
|
||||
{
|
||||
messagesToReturn.AddRange(messages);
|
||||
}
|
||||
}
|
||||
|
||||
this.Log(string.Format("Received {0} messages", messagesToReturn.Count));
|
||||
|
||||
return messagesToReturn;
|
||||
}
|
||||
|
||||
async Task CompleteMessagesAsync(QueueClient queueClient, IEnumerable<BrokeredMessage> messages)
|
||||
{
|
||||
await queueClient.CompleteAsync(messages.Select(message => message.LockToken));
|
||||
this.Log(string.Format("Completed {0} messages", messages.Count()));
|
||||
}
|
||||
|
||||
async Task AbandonMessagesAsync(QueueClient queueClient, IEnumerable<BrokeredMessage> messages)
|
||||
{
|
||||
await queueClient.AbandonAsync(messages.Select(message => message.LockToken));
|
||||
this.Log(string.Format("Abandoned {0} messages", messages.Count()));
|
||||
}
|
||||
|
||||
async Task DeadLetterMessagesAsync(QueueClient queueClient, IEnumerable<BrokeredMessage> messages)
|
||||
{
|
||||
await queueClient.DeadLetterAsync(messages.Select(message => message.LockToken));
|
||||
this.Log(string.Format("Deadlettered {0} messages", messages.Count()));
|
||||
}
|
||||
|
||||
async Task DeferMessagesAsync(QueueClient queueClient, IEnumerable<BrokeredMessage> messages)
|
||||
{
|
||||
await queueClient.DeferAsync(messages.Select(message => message.LockToken));
|
||||
this.Log(string.Format("Deferred {0} messages", messages.Count()));
|
||||
}
|
||||
|
||||
void Log(string message)
|
||||
{
|
||||
var formattedMessage = string.Format("{0} {1}", DateTime.Now.TimeOfDay, message);
|
||||
this.output.WriteLine(formattedMessage);
|
||||
Debug.WriteLine(formattedMessage);
|
||||
Console.WriteLine(formattedMessage);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,173 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Azure.ServiceBus.UnitTests
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
public abstract class QueueSessionTestBase
|
||||
{
|
||||
protected QueueSessionTestBase(ITestOutputHelper output)
|
||||
{
|
||||
this.Output = output;
|
||||
}
|
||||
|
||||
protected string ConnectionString { get; set; }
|
||||
|
||||
protected ITestOutputHelper Output { get; set; }
|
||||
|
||||
public async Task SessionTestCase()
|
||||
{
|
||||
QueueClient queueClient = QueueClient.CreateFromConnectionString(this.ConnectionString);
|
||||
try
|
||||
{
|
||||
string messageId1 = "test-message1";
|
||||
string sessionId1 = "sessionId1";
|
||||
await queueClient.SendAsync(new BrokeredMessage() { MessageId = messageId1, SessionId = sessionId1 });
|
||||
TestUtility.Log(this.Output, $"Sent Message: {messageId1} to Session: {sessionId1}");
|
||||
|
||||
string messageId2 = "test-message2";
|
||||
string sessionId2 = "sessionId2";
|
||||
await queueClient.SendAsync(new BrokeredMessage() { MessageId = messageId2, SessionId = sessionId2 });
|
||||
TestUtility.Log(this.Output, $"Sent Message: {messageId2} to Session: {sessionId2}");
|
||||
|
||||
// Receive Message, Complete and Close with SessionId - sessionId 1
|
||||
await this.AcceptAndCompleteSessionsAsync(queueClient, sessionId1, messageId1);
|
||||
|
||||
// Receive Message, Complete and Close with SessionId - sessionId 2
|
||||
await this.AcceptAndCompleteSessionsAsync(queueClient, sessionId2, messageId2);
|
||||
|
||||
// Receive Message, Complete and Close - With Null SessionId specified
|
||||
string messageId3 = "test-message3";
|
||||
string sessionId3 = "sessionId3";
|
||||
await queueClient.SendAsync(new BrokeredMessage() { MessageId = messageId3, SessionId = sessionId3 });
|
||||
|
||||
await this.AcceptAndCompleteSessionsAsync(queueClient, null, messageId3);
|
||||
}
|
||||
finally
|
||||
{
|
||||
queueClient.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task GetAndSetSessionStateTestCase()
|
||||
{
|
||||
QueueClient queueClient = QueueClient.CreateFromConnectionString(this.ConnectionString);
|
||||
try
|
||||
{
|
||||
string messageId = "test-message1";
|
||||
string sessionId = Guid.NewGuid().ToString();
|
||||
await queueClient.SendAsync(new BrokeredMessage() { MessageId = messageId, SessionId = sessionId });
|
||||
TestUtility.Log(this.Output, $"Sent Message: {messageId} to Session: {sessionId}");
|
||||
|
||||
MessageSession sessionReceiver = await queueClient.AcceptMessageSessionAsync(sessionId);
|
||||
Assert.NotNull((object)sessionReceiver);
|
||||
BrokeredMessage message = await sessionReceiver.ReceiveAsync();
|
||||
TestUtility.Log(this.Output, $"Received Message: {message.MessageId} from Session: {sessionReceiver.SessionId}");
|
||||
Assert.True(message.MessageId == messageId);
|
||||
|
||||
string sessionStateString = "Received Message From Session!";
|
||||
Stream sessionState = new MemoryStream(Encoding.UTF8.GetBytes(sessionStateString));
|
||||
await sessionReceiver.SetStateAsync(sessionState);
|
||||
TestUtility.Log(this.Output, $"Set Session State: {sessionStateString} for Session: {sessionReceiver.SessionId}");
|
||||
|
||||
Stream returnedSessionState = await sessionReceiver.GetStateAsync();
|
||||
using (StreamReader reader = new StreamReader(returnedSessionState, Encoding.UTF8))
|
||||
{
|
||||
string returnedSessionStateString = reader.ReadToEnd();
|
||||
TestUtility.Log(this.Output, $"Get Session State Returned: {returnedSessionStateString} for Session: {sessionReceiver.SessionId}");
|
||||
Assert.Equal(sessionStateString, returnedSessionStateString);
|
||||
}
|
||||
|
||||
// Complete message using Session Receiver
|
||||
await sessionReceiver.CompleteAsync(new Guid[] { message.LockToken });
|
||||
TestUtility.Log(this.Output, $"Completed Message: {message.MessageId} for Session: {sessionReceiver.SessionId}");
|
||||
|
||||
sessionStateString = "Completed Message On Session!";
|
||||
sessionState = new MemoryStream(Encoding.UTF8.GetBytes(sessionStateString));
|
||||
await sessionReceiver.SetStateAsync(sessionState);
|
||||
TestUtility.Log(this.Output, $"Set Session State: {sessionStateString} for Session: {sessionReceiver.SessionId}");
|
||||
|
||||
returnedSessionState = await sessionReceiver.GetStateAsync();
|
||||
using (StreamReader reader = new StreamReader(returnedSessionState, Encoding.UTF8))
|
||||
{
|
||||
string returnedSessionStateString = reader.ReadToEnd();
|
||||
TestUtility.Log(this.Output, $"Get Session State Returned: {returnedSessionStateString} for Session: {sessionReceiver.SessionId}");
|
||||
Assert.Equal(sessionStateString, returnedSessionStateString);
|
||||
}
|
||||
|
||||
await sessionReceiver.CloseAsync();
|
||||
}
|
||||
finally
|
||||
{
|
||||
queueClient.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SessionRenewLockTestCase()
|
||||
{
|
||||
QueueClient queueClient = QueueClient.CreateFromConnectionString(this.ConnectionString);
|
||||
try
|
||||
{
|
||||
string messageId = "test-message1";
|
||||
string sessionId = Guid.NewGuid().ToString();
|
||||
await queueClient.SendAsync(new BrokeredMessage() { MessageId = messageId, SessionId = sessionId });
|
||||
TestUtility.Log(this.Output, $"Sent Message: {messageId} to Session: {sessionId}");
|
||||
|
||||
MessageSession sessionReceiver = await queueClient.AcceptMessageSessionAsync(sessionId);
|
||||
Assert.NotNull((object)sessionReceiver);
|
||||
DateTime initialSessionLockedUntilTime = sessionReceiver.LockedUntilUtc;
|
||||
TestUtility.Log(this.Output, $"Session LockedUntilUTC: {initialSessionLockedUntilTime} for Session: {sessionReceiver.SessionId}");
|
||||
BrokeredMessage message = await sessionReceiver.ReceiveAsync();
|
||||
TestUtility.Log(this.Output, $"Received Message: {message.MessageId} from Session: {sessionReceiver.SessionId}");
|
||||
Assert.True(message.MessageId == messageId);
|
||||
|
||||
TestUtility.Log(this.Output, "Sleeping 10 seconds...");
|
||||
Thread.Sleep(TimeSpan.FromSeconds(10));
|
||||
|
||||
await sessionReceiver.RenewLockAsync();
|
||||
DateTime firstLockedUntilUtcTime = sessionReceiver.LockedUntilUtc;
|
||||
TestUtility.Log(this.Output, $"After Renew Session LockedUntilUTC: {firstLockedUntilUtcTime} for Session: {sessionReceiver.SessionId}");
|
||||
Assert.True(firstLockedUntilUtcTime >= initialSessionLockedUntilTime + TimeSpan.FromSeconds(10));
|
||||
|
||||
TestUtility.Log(this.Output, "Sleeping 5 seconds...");
|
||||
Thread.Sleep(TimeSpan.FromSeconds(5));
|
||||
|
||||
await sessionReceiver.RenewLockAsync();
|
||||
TestUtility.Log(this.Output, $"After Second Renew Session LockedUntilUTC: {sessionReceiver.LockedUntilUtc} for Session: {sessionReceiver.SessionId}");
|
||||
Assert.True(sessionReceiver.LockedUntilUtc >= firstLockedUntilUtcTime + TimeSpan.FromSeconds(5));
|
||||
await message.CompleteAsync();
|
||||
TestUtility.Log(this.Output, $"Completed Message: {message.MessageId} for Session: {sessionReceiver.SessionId}");
|
||||
await sessionReceiver.CloseAsync();
|
||||
}
|
||||
finally
|
||||
{
|
||||
queueClient.Close();
|
||||
}
|
||||
}
|
||||
|
||||
// Session Helpers
|
||||
async Task AcceptAndCompleteSessionsAsync(QueueClient queueClient, string sessionId, string messageId)
|
||||
{
|
||||
MessageSession sessionReceiver = await queueClient.AcceptMessageSessionAsync(sessionId);
|
||||
{
|
||||
if (sessionId != null)
|
||||
{
|
||||
Assert.True(sessionReceiver.SessionId == sessionId);
|
||||
}
|
||||
BrokeredMessage message = await sessionReceiver.ReceiveAsync();
|
||||
Assert.True(message.MessageId == messageId);
|
||||
TestUtility.Log(this.Output, $"Received Message: {message.MessageId} from Session: {sessionReceiver.SessionId}");
|
||||
await message.CompleteAsync();
|
||||
TestUtility.Log(this.Output, $"Completed Message: {message.MessageId} for Session: {sessionReceiver.SessionId}");
|
||||
await sessionReceiver.CloseAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Azure.ServiceBus.UnitTests
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.ServiceBus.Primitives;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
public abstract class SenderReceiverClientTestBase
|
||||
{
|
||||
protected SenderReceiverClientTestBase(ITestOutputHelper output)
|
||||
{
|
||||
this.Output = output;
|
||||
}
|
||||
|
||||
protected ITestOutputHelper Output { get; set; }
|
||||
|
||||
public async Task PeekLockTestCase(MessageSender messageSender, MessageReceiver messageReceiver, int messageCount)
|
||||
{
|
||||
await TestUtility.SendMessagesAsync(messageSender, messageCount, this.Output);
|
||||
IEnumerable<BrokeredMessage> receivedMessages = await TestUtility.ReceiveMessagesAsync(messageReceiver, messageCount, this.Output);
|
||||
await TestUtility.CompleteMessagesAsync(messageReceiver, receivedMessages, this.Output);
|
||||
}
|
||||
|
||||
public async Task ReceiveDeleteTestCase(MessageSender messageSender, MessageReceiver messageReceiver, int messageCount)
|
||||
{
|
||||
await TestUtility.SendMessagesAsync(messageSender, messageCount, this.Output);
|
||||
IEnumerable<BrokeredMessage> receivedMessages = await TestUtility.ReceiveMessagesAsync(messageReceiver, messageCount, this.Output);
|
||||
Assert.True(messageCount == receivedMessages.Count());
|
||||
}
|
||||
|
||||
public async Task PeekLockWithAbandonTestCase(MessageSender messageSender, MessageReceiver messageReceiver, int messageCount)
|
||||
{
|
||||
// Send messages
|
||||
await TestUtility.SendMessagesAsync(messageSender, messageCount, this.Output);
|
||||
|
||||
// Receive 5 messages and Abandon them
|
||||
int abandonMessagesCount = 5;
|
||||
IEnumerable<BrokeredMessage> receivedMessages = await TestUtility.ReceiveMessagesAsync(messageReceiver, abandonMessagesCount, this.Output);
|
||||
Assert.True(receivedMessages.Count() == abandonMessagesCount);
|
||||
|
||||
await TestUtility.AbandonMessagesAsync(messageReceiver, receivedMessages, this.Output);
|
||||
|
||||
// Receive all 10 messages
|
||||
receivedMessages = await TestUtility.ReceiveMessagesAsync(messageReceiver, messageCount, this.Output);
|
||||
Assert.True(receivedMessages.Count() == messageCount);
|
||||
|
||||
// TODO: Some reason for partitioned entities the delivery count is incorrect. Investigate and enable
|
||||
// 5 of these messages should have deliveryCount = 2
|
||||
int messagesWithDeliveryCount2 = receivedMessages.Where(message => message.DeliveryCount == 2).Count();
|
||||
TestUtility.Log(this.Output, $"Messages with Delivery Count 2: {messagesWithDeliveryCount2}");
|
||||
Assert.True(messagesWithDeliveryCount2 == abandonMessagesCount);
|
||||
|
||||
// Complete Messages
|
||||
await TestUtility.CompleteMessagesAsync(messageReceiver, receivedMessages, this.Output);
|
||||
}
|
||||
|
||||
public async Task PeekLockWithDeadLetterTestCase(MessageSender messageSender, MessageReceiver messageReceiver, MessageReceiver deadLetterReceiver, int messageCount)
|
||||
{
|
||||
// Send messages
|
||||
await TestUtility.SendMessagesAsync(messageSender, messageCount, this.Output);
|
||||
|
||||
// Receive 5 messages and Deadletter them
|
||||
int deadLetterMessageCount = 5;
|
||||
IEnumerable<BrokeredMessage> receivedMessages = await TestUtility.ReceiveMessagesAsync(messageReceiver, deadLetterMessageCount, this.Output);
|
||||
Assert.True(receivedMessages.Count() == deadLetterMessageCount);
|
||||
|
||||
await TestUtility.DeadLetterMessagesAsync(messageReceiver, receivedMessages, this.Output);
|
||||
|
||||
// Receive and Complete 5 other regular messages
|
||||
receivedMessages = await TestUtility.ReceiveMessagesAsync(messageReceiver, messageCount - deadLetterMessageCount, this.Output);
|
||||
await TestUtility.CompleteMessagesAsync(messageReceiver, receivedMessages, this.Output);
|
||||
|
||||
// TODO: After implementing Receive(WithTimeSpan), Add Try another Receive, We should not get anything.
|
||||
// IEnumerable<BrokeredMessage> dummyMessages = await this.ReceiveMessagesAsync(queueClient, 10);
|
||||
// Assert.True(dummyMessages == null);
|
||||
|
||||
// Receive 5 DLQ messages and Complete them
|
||||
receivedMessages = await TestUtility.ReceiveMessagesAsync(deadLetterReceiver, deadLetterMessageCount, this.Output);
|
||||
Assert.True(receivedMessages.Count() == deadLetterMessageCount);
|
||||
await TestUtility.CompleteMessagesAsync(deadLetterReceiver, receivedMessages, this.Output);
|
||||
}
|
||||
|
||||
public async Task PeekLockDeferTestCase(MessageSender messageSender, MessageReceiver messageReceiver, int messageCount)
|
||||
{
|
||||
// Send messages
|
||||
await TestUtility.SendMessagesAsync(messageSender, messageCount, this.Output);
|
||||
|
||||
// Receive 5 messages And Defer them
|
||||
int deferMessagesCount = 5;
|
||||
IEnumerable<BrokeredMessage> receivedMessages = await TestUtility.ReceiveMessagesAsync(messageReceiver, deferMessagesCount, this.Output);
|
||||
Assert.True(receivedMessages.Count() == deferMessagesCount);
|
||||
var sequenceNumbers = receivedMessages.Select(receivedMessage => receivedMessage.SequenceNumber);
|
||||
await TestUtility.DeferMessagesAsync(messageReceiver, receivedMessages, this.Output);
|
||||
|
||||
// Receive and Complete 5 other regular messages
|
||||
receivedMessages = await TestUtility.ReceiveMessagesAsync(messageReceiver, messageCount - deferMessagesCount, this.Output);
|
||||
await TestUtility.CompleteMessagesAsync(messageReceiver, receivedMessages, this.Output);
|
||||
Assert.True(receivedMessages.Count() == messageCount - deferMessagesCount);
|
||||
|
||||
// Receive / Abandon deferred messages
|
||||
receivedMessages = await messageReceiver.ReceiveBySequenceNumberAsync(sequenceNumbers);
|
||||
Assert.True(receivedMessages.Count() == 5);
|
||||
await TestUtility.DeferMessagesAsync(messageReceiver, receivedMessages, this.Output);
|
||||
|
||||
// Receive Again and Check delivery count
|
||||
receivedMessages = await messageReceiver.ReceiveBySequenceNumberAsync(sequenceNumbers);
|
||||
int count = receivedMessages.Where((message) => message.DeliveryCount == 3).Count();
|
||||
Assert.True(count == receivedMessages.Count());
|
||||
|
||||
// Complete messages
|
||||
await TestUtility.CompleteMessagesAsync(messageReceiver, receivedMessages, this.Output);
|
||||
}
|
||||
|
||||
public async Task RenewLockTestCase(MessageSender messageSender, MessageReceiver messageReceiver, int messageCount)
|
||||
{
|
||||
// Send messages
|
||||
await TestUtility.SendMessagesAsync(messageSender, messageCount, this.Output);
|
||||
|
||||
// Receive messages
|
||||
IEnumerable<BrokeredMessage> receivedMessages = await TestUtility.ReceiveMessagesAsync(messageReceiver, messageCount, this.Output);
|
||||
|
||||
BrokeredMessage message = receivedMessages.First();
|
||||
DateTime firstLockedUntilUtcTime = message.LockedUntilUtc;
|
||||
TestUtility.Log(this.Output, $"MessageLockedUntil: {firstLockedUntilUtcTime}");
|
||||
|
||||
TestUtility.Log(this.Output, "Sleeping 10 seconds...");
|
||||
Thread.Sleep(TimeSpan.FromSeconds(10));
|
||||
|
||||
DateTime lockedUntilUtcTime = await messageReceiver.RenewLockAsync(receivedMessages.First().LockToken);
|
||||
TestUtility.Log(this.Output, $"After First Renewal: {lockedUntilUtcTime}");
|
||||
Assert.True(lockedUntilUtcTime >= firstLockedUntilUtcTime + TimeSpan.FromSeconds(10));
|
||||
|
||||
TestUtility.Log(this.Output, "Sleeping 5 seconds...");
|
||||
Thread.Sleep(TimeSpan.FromSeconds(5));
|
||||
|
||||
lockedUntilUtcTime = await messageReceiver.RenewLockAsync(receivedMessages.First().LockToken);
|
||||
TestUtility.Log(this.Output, $"After Second Renewal: {lockedUntilUtcTime}");
|
||||
Assert.True(lockedUntilUtcTime >= firstLockedUntilUtcTime + TimeSpan.FromSeconds(5));
|
||||
|
||||
// Complete Messages
|
||||
await TestUtility.CompleteMessagesAsync(messageReceiver, receivedMessages, this.Output);
|
||||
|
||||
Assert.True(receivedMessages.Count() == messageCount);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Azure.ServiceBus.UnitTests
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
public class TestUtility
|
||||
{
|
||||
const int MaxAttemptsCount = 5;
|
||||
|
||||
public static void Log(ITestOutputHelper output, string message)
|
||||
{
|
||||
var formattedMessage = string.Format("{0} {1}", DateTime.Now.TimeOfDay, message);
|
||||
output.WriteLine(formattedMessage);
|
||||
Debug.WriteLine(formattedMessage);
|
||||
Console.WriteLine(formattedMessage);
|
||||
}
|
||||
|
||||
public static async Task SendMessagesAsync(MessageSender messageSender, int messageCount, ITestOutputHelper output)
|
||||
{
|
||||
if (messageCount == 0)
|
||||
{
|
||||
await Task.FromResult(false);
|
||||
}
|
||||
|
||||
List<BrokeredMessage> messagesToSend = new List<BrokeredMessage>();
|
||||
for (int i = 0; i < messageCount; i++)
|
||||
{
|
||||
BrokeredMessage message = new BrokeredMessage("test" + i);
|
||||
message.Label = "test" + i;
|
||||
messagesToSend.Add(message);
|
||||
}
|
||||
|
||||
await messageSender.SendAsync(messagesToSend);
|
||||
Log(output, string.Format("Sent {0} messages", messageCount));
|
||||
}
|
||||
|
||||
public static async Task<IEnumerable<BrokeredMessage>> ReceiveMessagesAsync(MessageReceiver messageReceiver, int messageCount, ITestOutputHelper output)
|
||||
{
|
||||
int receiveAttempts = 0;
|
||||
List<BrokeredMessage> messagesToReturn = new List<BrokeredMessage>();
|
||||
|
||||
while (receiveAttempts++ < TestUtility.MaxAttemptsCount && messagesToReturn.Count < messageCount)
|
||||
{
|
||||
var messages = await messageReceiver.ReceiveAsync(messageCount);
|
||||
if (messages != null)
|
||||
{
|
||||
messagesToReturn.AddRange(messages);
|
||||
}
|
||||
}
|
||||
|
||||
Log(output, string.Format("Received {0} messages", messagesToReturn.Count));
|
||||
return messagesToReturn;
|
||||
}
|
||||
|
||||
public static async Task CompleteMessagesAsync(MessageReceiver messageReceiver, IEnumerable<BrokeredMessage> messages, ITestOutputHelper output)
|
||||
{
|
||||
await messageReceiver.CompleteAsync(messages.Select(message => message.LockToken));
|
||||
Log(output, string.Format("Completed {0} messages", messages.Count()));
|
||||
}
|
||||
|
||||
public static async Task AbandonMessagesAsync(MessageReceiver messageReceiver, IEnumerable<BrokeredMessage> messages, ITestOutputHelper output)
|
||||
{
|
||||
await messageReceiver.AbandonAsync(messages.Select(message => message.LockToken));
|
||||
Log(output, string.Format("Abandoned {0} messages", messages.Count()));
|
||||
}
|
||||
|
||||
public static async Task DeadLetterMessagesAsync(MessageReceiver messageReceiver, IEnumerable<BrokeredMessage> messages, ITestOutputHelper output)
|
||||
{
|
||||
await messageReceiver.DeadLetterAsync(messages.Select(message => message.LockToken));
|
||||
Log(output, string.Format("Deadlettered {0} messages", messages.Count()));
|
||||
}
|
||||
|
||||
public static async Task DeferMessagesAsync(MessageReceiver messageReceiver, IEnumerable<BrokeredMessage> messages, ITestOutputHelper output)
|
||||
{
|
||||
await messageReceiver.DeferAsync(messages.Select(message => message.LockToken));
|
||||
Log(output, string.Format("Deferred {0} messages", messages.Count()));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Azure.ServiceBus.UnitTests
|
||||
{
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.ServiceBus.Primitives;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
public abstract class TopicClientTestBase : SenderReceiverClientTestBase
|
||||
{
|
||||
protected TopicClientTestBase(ITestOutputHelper output)
|
||||
: base(output)
|
||||
{
|
||||
}
|
||||
|
||||
protected string ConnectionString { get; set; }
|
||||
|
||||
protected string SubscriptionName { get; set; }
|
||||
|
||||
public async Task TopicClientPeekLockTestCase(int messageCount)
|
||||
{
|
||||
TopicClient topicClient = TopicClient.CreateFromConnectionString(this.ConnectionString);
|
||||
SubscriptionClient subscriptionClient = SubscriptionClient.CreateFromConnectionString(this.ConnectionString, this.SubscriptionName);
|
||||
try
|
||||
{
|
||||
await this.PeekLockTestCase(topicClient.InnerSender, subscriptionClient.InnerReceiver, messageCount);
|
||||
}
|
||||
finally
|
||||
{
|
||||
subscriptionClient.Close();
|
||||
topicClient.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task TopicClientReceiveDeleteTestCase(int messageCount)
|
||||
{
|
||||
TopicClient topicClient = TopicClient.CreateFromConnectionString(this.ConnectionString);
|
||||
SubscriptionClient subscriptionClient = SubscriptionClient.CreateFromConnectionString(this.ConnectionString, this.SubscriptionName, ReceiveMode.ReceiveAndDelete);
|
||||
try
|
||||
{
|
||||
await this.ReceiveDeleteTestCase(topicClient.InnerSender, subscriptionClient.InnerReceiver, messageCount);
|
||||
}
|
||||
finally
|
||||
{
|
||||
subscriptionClient.Close();
|
||||
topicClient.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task TopicClientPeekLockWithAbandonTestCase(int messageCount)
|
||||
{
|
||||
TopicClient topicClient = TopicClient.CreateFromConnectionString(this.ConnectionString);
|
||||
SubscriptionClient subscriptionClient = SubscriptionClient.CreateFromConnectionString(this.ConnectionString, this.SubscriptionName);
|
||||
try
|
||||
{
|
||||
await this.PeekLockWithAbandonTestCase(topicClient.InnerSender, subscriptionClient.InnerReceiver, messageCount);
|
||||
}
|
||||
finally
|
||||
{
|
||||
subscriptionClient.Close();
|
||||
topicClient.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task TopicClientPeekLockWithDeadLetterTestCase(int messageCount)
|
||||
{
|
||||
TopicClient topicClient = TopicClient.CreateFromConnectionString(this.ConnectionString);
|
||||
SubscriptionClient subscriptionClient = SubscriptionClient.CreateFromConnectionString(this.ConnectionString, this.SubscriptionName);
|
||||
|
||||
// Create DLQ Client To Receive DeadLetteredMessages
|
||||
ServiceBusConnectionStringBuilder builder = new ServiceBusConnectionStringBuilder(this.ConnectionString);
|
||||
string subscriptionDeadletterPath = EntityNameHelper.FormatDeadLetterPath(this.SubscriptionName);
|
||||
SubscriptionClient deadLetterSubscriptionClient = SubscriptionClient.CreateFromConnectionString(this.ConnectionString, subscriptionDeadletterPath);
|
||||
|
||||
try
|
||||
{
|
||||
await this.PeekLockWithDeadLetterTestCase(topicClient.InnerSender, subscriptionClient.InnerReceiver, deadLetterSubscriptionClient.InnerReceiver, messageCount);
|
||||
}
|
||||
finally
|
||||
{
|
||||
deadLetterSubscriptionClient.Close();
|
||||
topicClient.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task TopicClientPeekLockDeferTestCase(int messageCount)
|
||||
{
|
||||
TopicClient topicClient = TopicClient.CreateFromConnectionString(this.ConnectionString);
|
||||
SubscriptionClient subscriptionClient = SubscriptionClient.CreateFromConnectionString(this.ConnectionString, this.SubscriptionName);
|
||||
try
|
||||
{
|
||||
await this.PeekLockDeferTestCase(topicClient.InnerSender, subscriptionClient.InnerReceiver, messageCount);
|
||||
}
|
||||
finally
|
||||
{
|
||||
subscriptionClient.Close();
|
||||
topicClient.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task TopicClientRenewLockTestCase(int messageCount)
|
||||
{
|
||||
TopicClient topicClient = TopicClient.CreateFromConnectionString(this.ConnectionString);
|
||||
SubscriptionClient subscriptionClient = SubscriptionClient.CreateFromConnectionString(this.ConnectionString, this.SubscriptionName);
|
||||
try
|
||||
{
|
||||
await this.RenewLockTestCase(topicClient.InnerSender, subscriptionClient.InnerReceiver, messageCount);
|
||||
}
|
||||
finally
|
||||
{
|
||||
subscriptionClient.Close();
|
||||
topicClient.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,181 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Microsoft.Azure.ServiceBus.UnitTests
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
public class TopicSessionTestBase
|
||||
{
|
||||
protected TopicSessionTestBase(ITestOutputHelper output)
|
||||
{
|
||||
this.Output = output;
|
||||
}
|
||||
|
||||
protected string ConnectionString { get; set; }
|
||||
|
||||
protected string SubscriptionName { get; set; }
|
||||
|
||||
protected ITestOutputHelper Output { get; set; }
|
||||
|
||||
public async Task SessionTestCase()
|
||||
{
|
||||
TopicClient topicClient = TopicClient.CreateFromConnectionString(this.ConnectionString);
|
||||
SubscriptionClient subscriptionClient = SubscriptionClient.CreateFromConnectionString(this.ConnectionString, this.SubscriptionName);
|
||||
try
|
||||
{
|
||||
string messageId1 = "test-message1";
|
||||
string sessionId1 = "sessionId1";
|
||||
await topicClient.SendAsync(new BrokeredMessage() { MessageId = messageId1, SessionId = sessionId1 });
|
||||
TestUtility.Log(this.Output, $"Sent Message: {messageId1} to Session: {sessionId1}");
|
||||
|
||||
string messageId2 = "test-message2";
|
||||
string sessionId2 = "sessionId2";
|
||||
await topicClient.SendAsync(new BrokeredMessage() { MessageId = messageId2, SessionId = sessionId2 });
|
||||
TestUtility.Log(this.Output, $"Sent Message: {messageId2} to Session: {sessionId2}");
|
||||
|
||||
// Receive Message, Complete and Close with SessionId - sessionId 1
|
||||
await this.AcceptAndCompleteSessionsAsync(subscriptionClient, sessionId1, messageId1);
|
||||
|
||||
// Receive Message, Complete and Close with SessionId - sessionId 2
|
||||
await this.AcceptAndCompleteSessionsAsync(subscriptionClient, sessionId2, messageId2);
|
||||
|
||||
// Receive Message, Complete and Close - With Null SessionId specified
|
||||
string messageId3 = "test-message3";
|
||||
string sessionId3 = "sessionId3";
|
||||
await topicClient.SendAsync(new BrokeredMessage() { MessageId = messageId3, SessionId = sessionId3 });
|
||||
|
||||
await this.AcceptAndCompleteSessionsAsync(subscriptionClient, null, messageId3);
|
||||
}
|
||||
finally
|
||||
{
|
||||
subscriptionClient.Close();
|
||||
topicClient.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task GetAndSetSessionStateTestCase()
|
||||
{
|
||||
TopicClient topicClient = TopicClient.CreateFromConnectionString(this.ConnectionString);
|
||||
SubscriptionClient subscriptionClient = SubscriptionClient.CreateFromConnectionString(this.ConnectionString, this.SubscriptionName);
|
||||
try
|
||||
{
|
||||
string messageId = "test-message1";
|
||||
string sessionId = Guid.NewGuid().ToString();
|
||||
await topicClient.SendAsync(new BrokeredMessage() { MessageId = messageId, SessionId = sessionId });
|
||||
TestUtility.Log(this.Output, $"Sent Message: {messageId} to Session: {sessionId}");
|
||||
|
||||
MessageSession sessionReceiver = await subscriptionClient.AcceptMessageSessionAsync(sessionId);
|
||||
Assert.NotNull((object)sessionReceiver);
|
||||
BrokeredMessage message = await sessionReceiver.ReceiveAsync();
|
||||
TestUtility.Log(this.Output, $"Received Message: {message.MessageId} from Session: {sessionReceiver.SessionId}");
|
||||
Assert.True(message.MessageId == messageId);
|
||||
|
||||
string sessionStateString = "Received Message From Session!";
|
||||
Stream sessionState = new MemoryStream(Encoding.UTF8.GetBytes(sessionStateString));
|
||||
await sessionReceiver.SetStateAsync(sessionState);
|
||||
TestUtility.Log(this.Output, $"Set Session State: {sessionStateString} for Session: {sessionReceiver.SessionId}");
|
||||
|
||||
Stream returnedSessionState = await sessionReceiver.GetStateAsync();
|
||||
using (StreamReader reader = new StreamReader(returnedSessionState, Encoding.UTF8))
|
||||
{
|
||||
string returnedSessionStateString = reader.ReadToEnd();
|
||||
TestUtility.Log(this.Output, $"Get Session State Returned: {returnedSessionStateString} for Session: {sessionReceiver.SessionId}");
|
||||
Assert.Equal(sessionStateString, returnedSessionStateString);
|
||||
}
|
||||
|
||||
// Complete message using Session Receiver
|
||||
await sessionReceiver.CompleteAsync(new Guid[] { message.LockToken });
|
||||
TestUtility.Log(this.Output, $"Completed Message: {message.MessageId} for Session: {sessionReceiver.SessionId}");
|
||||
|
||||
sessionStateString = "Completed Message On Session!";
|
||||
sessionState = new MemoryStream(Encoding.UTF8.GetBytes(sessionStateString));
|
||||
await sessionReceiver.SetStateAsync(sessionState);
|
||||
TestUtility.Log(this.Output, $"Set Session State: {sessionStateString} for Session: {sessionReceiver.SessionId}");
|
||||
|
||||
returnedSessionState = await sessionReceiver.GetStateAsync();
|
||||
using (StreamReader reader = new StreamReader(returnedSessionState, Encoding.UTF8))
|
||||
{
|
||||
string returnedSessionStateString = reader.ReadToEnd();
|
||||
TestUtility.Log(this.Output, $"Get Session State Returned: {returnedSessionStateString} for Session: {sessionReceiver.SessionId}");
|
||||
Assert.Equal(sessionStateString, returnedSessionStateString);
|
||||
}
|
||||
|
||||
await sessionReceiver.CloseAsync();
|
||||
}
|
||||
finally
|
||||
{
|
||||
subscriptionClient.Close();
|
||||
topicClient.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SessionRenewLockTestCase()
|
||||
{
|
||||
TopicClient topicClient = TopicClient.CreateFromConnectionString(this.ConnectionString);
|
||||
SubscriptionClient subscriptionClient = SubscriptionClient.CreateFromConnectionString(this.ConnectionString, this.SubscriptionName);
|
||||
try
|
||||
{
|
||||
string messageId = "test-message1";
|
||||
string sessionId = Guid.NewGuid().ToString();
|
||||
await topicClient.SendAsync(new BrokeredMessage() { MessageId = messageId, SessionId = sessionId });
|
||||
TestUtility.Log(this.Output, $"Sent Message: {messageId} to Session: {sessionId}");
|
||||
|
||||
MessageSession sessionReceiver = await subscriptionClient.AcceptMessageSessionAsync(sessionId);
|
||||
Assert.NotNull((object)sessionReceiver);
|
||||
DateTime initialSessionLockedUntilTime = sessionReceiver.LockedUntilUtc;
|
||||
TestUtility.Log(this.Output, $"Session LockedUntilUTC: {initialSessionLockedUntilTime} for Session: {sessionReceiver.SessionId}");
|
||||
BrokeredMessage message = await sessionReceiver.ReceiveAsync();
|
||||
TestUtility.Log(this.Output, $"Received Message: {message.MessageId} from Session: {sessionReceiver.SessionId}");
|
||||
Assert.True(message.MessageId == messageId);
|
||||
|
||||
TestUtility.Log(this.Output, "Sleeping 10 seconds...");
|
||||
Thread.Sleep(TimeSpan.FromSeconds(10));
|
||||
|
||||
await sessionReceiver.RenewLockAsync();
|
||||
DateTime firstLockedUntilUtcTime = sessionReceiver.LockedUntilUtc;
|
||||
TestUtility.Log(this.Output, $"After Renew Session LockedUntilUTC: {firstLockedUntilUtcTime} for Session: {sessionReceiver.SessionId}");
|
||||
Assert.True(firstLockedUntilUtcTime >= initialSessionLockedUntilTime + TimeSpan.FromSeconds(10));
|
||||
|
||||
TestUtility.Log(this.Output, "Sleeping 5 seconds...");
|
||||
Thread.Sleep(TimeSpan.FromSeconds(5));
|
||||
|
||||
await sessionReceiver.RenewLockAsync();
|
||||
TestUtility.Log(this.Output, $"After Second Renew Session LockedUntilUTC: {sessionReceiver.LockedUntilUtc} for Session: {sessionReceiver.SessionId}");
|
||||
Assert.True(sessionReceiver.LockedUntilUtc >= firstLockedUntilUtcTime + TimeSpan.FromSeconds(5));
|
||||
await message.CompleteAsync();
|
||||
TestUtility.Log(this.Output, $"Completed Message: {message.MessageId} for Session: {sessionReceiver.SessionId}");
|
||||
await sessionReceiver.CloseAsync();
|
||||
}
|
||||
finally
|
||||
{
|
||||
subscriptionClient.Close();
|
||||
topicClient.Close();
|
||||
}
|
||||
}
|
||||
|
||||
// Session Helpers
|
||||
async Task AcceptAndCompleteSessionsAsync(SubscriptionClient subscriptionClient, string sessionId, string messageId)
|
||||
{
|
||||
MessageSession sessionReceiver = await subscriptionClient.AcceptMessageSessionAsync(sessionId);
|
||||
{
|
||||
if (sessionId != null)
|
||||
{
|
||||
Assert.True(sessionReceiver.SessionId == sessionId);
|
||||
}
|
||||
BrokeredMessage message = await sessionReceiver.ReceiveAsync();
|
||||
Assert.True(message.MessageId == messageId);
|
||||
TestUtility.Log(this.Output, $"Received Message: {message.MessageId} from Session: {sessionReceiver.SessionId}");
|
||||
await message.CompleteAsync();
|
||||
TestUtility.Log(this.Output, $"Completed Message: {message.MessageId} for Session: {sessionReceiver.SessionId}");
|
||||
await sessionReceiver.CloseAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Двоичный файл не отображается.
|
@ -4,6 +4,7 @@
|
|||
|
||||
"buildOptions": {
|
||||
"warningsAsErrors": true,
|
||||
"keyFile": "keyfile.snk",
|
||||
"additionalArguments": [ "/ruleset:./../../build/StyleCop.Analyzers.ruleset", "/additionalfile:./../../build/stylecop.json" ]
|
||||
},
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче