Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add ICustomHeaderProvider for ConnectionFactory #2161

Merged
merged 2 commits into from
Feb 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Collections.Generic;

using Microsoft.Extensions.Logging;

namespace Microsoft.Azure.SignalR.AspNet;

#nullable enable

internal class ConnectionFactory : ConnectionFactoryBase
{
public ConnectionFactory(IServerNameProvider nameProvider, ILoggerFactory loggerFactory) : base(nameProvider, loggerFactory)
{
}

protected override void SetCustomHeaders(IDictionary<string, string> headers)
{
return;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System;
using System.Buffers;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
Expand All @@ -22,8 +21,6 @@ internal partial class ServiceConnection : ServiceConnectionBase
{
private const string ReconnectMessage = "asrs:reconnect";

private static readonly Dictionary<string, string> CustomHeader = new() { { Constants.AsrsUserAgent, ProductInfo.GetProductInfo() } };

private static readonly TimeSpan CloseApplicationTimeout = TimeSpan.FromSeconds(5);

private readonly ConcurrentDictionary<string, ClientConnectionContext> _clientConnections = new(StringComparer.Ordinal);
Expand Down Expand Up @@ -81,8 +78,7 @@ public override Task CloseClientConnections(CancellationToken token)

protected override Task<ConnectionContext> CreateConnection(string target = null)
{
return _connectionFactory.ConnectAsync(HubEndpoint, TransferFormat.Binary, ConnectionId, target,
headers: CustomHeader);
return _connectionFactory.ConnectAsync(HubEndpoint, TransferFormat.Binary, ConnectionId, target);
}

protected override Task DisposeConnection(ConnectionContext connection)
Expand Down
241 changes: 120 additions & 121 deletions src/Microsoft.Azure.SignalR.AspNet/ServiceOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,141 +10,140 @@

using Microsoft.Owin;

namespace Microsoft.Azure.SignalR.AspNet
namespace Microsoft.Azure.SignalR.AspNet;

/// <summary>
/// Configurable options when using Azure SignalR Service.
/// </summary>
public class ServiceOptions : IServiceEndpointOptions
{
/// <summary>
/// Configurable options when using Azure SignalR Service.
/// </summary>
public class ServiceOptions : IServiceEndpointOptions
public ServiceOptions()
{
/// <summary>
/// Gets or sets the connection string of Azure SignalR Service instance.
/// </summary>
public string ConnectionString { get; set; }

/// <summary>
/// Gets or sets the initial number of connections per hub from SDK to Azure SignalR Service. Default value is 5.
/// Usually keep it as the default value is enough. During runtime, the SDK might start new server connections for performance tuning or load balancing.
/// When you have big number of clients, you can give it a larger number for better throughput.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Please use InitialHubServerConnectionCount instead.")]
public int ConnectionCount
var count = ConfigurationManager.ConnectionStrings.Count;
string connectionString = null;
var endpoints = new List<ServiceEndpoint>();
var connectionStringKeyPrefix = $"{Constants.Keys.ConnectionStringDefaultKey}:";
for (var i = 0; i < count; i++)
{
get => InitialHubServerConnectionCount;
set => InitialHubServerConnectionCount = value;
var setting = ConfigurationManager.ConnectionStrings[i];

if (setting.Name == Constants.Keys.ConnectionStringDefaultKey)
{
connectionString = setting.ConnectionString;
}
else if (setting.Name.StartsWith(connectionStringKeyPrefix) && !string.IsNullOrEmpty(setting.ConnectionString))
{
endpoints.Add(new ServiceEndpoint(setting.Name, setting.ConnectionString));
}
}

/// <summary>
/// Gets or sets the initial number of connections per hub from SDK to Azure SignalR Service.
/// Default value is 5.
/// Usually keep it as the default value is enough. When you have big number of clients, you can give it a larger number for better throughput.
/// During runtime, the SDK might start new server connections for performance tuning or load balancing.
/// </summary>
public int InitialHubServerConnectionCount { get; set; } = 5;

/// <summary>
/// Specifies the max server connection count allowed per hub from SDK to Azure SignalR Service.
/// During runtime, the SDK might start new server connections for performance tuning or load balancing.
/// By default a new server connection starts whenever needed.
/// When the max allowed server connection count is configured, the SDK does not start new connections when server connection count reaches the limit.
/// </summary>
public int? MaxHubServerConnectionCount { get; set; }

/// <summary>
/// Gets applicationName, which will be used as a prefix to apply to each hub name
/// </summary>
internal string ApplicationName { get; set; }
string IServiceEndpointOptions.ApplicationName => ApplicationName;

/// <summary>
/// Gets or sets the func to generate claims from <see cref="IOwinContext" />.
/// The claims will be included in the auto-generated token for clients.
/// </summary>
public Func<IOwinContext, IEnumerable<Claim>> ClaimsProvider { get; set; } = null;

/// <summary>
/// Gets or sets the func to set diagnostic client filter from <see cref="IOwinContext" />.
/// The clients will be regarded as diagnostic client only if the function returns true.
/// </summary>
public Func<IOwinContext, bool> DiagnosticClientFilter { get; set; } = null;

/// <summary>
/// Gets or sets the lifetime of auto-generated access token, which will be used to authenticate with Azure SignalR Service.
/// Default value is one hour.
/// </summary>
public TimeSpan AccessTokenLifetime { get; set; } = Constants.Periods.DefaultAccessTokenLifetime;

/// <summary>
/// Gets or sets the access token generate algorithm, supports HmacSha256 or HmacSha512
/// Default value is HmacSha256
/// </summary>
public AccessTokenAlgorithm AccessTokenAlgorithm { get; set; } = AccessTokenAlgorithm.HS256;

/// <summary>
/// Customize the multiple endpoints used
/// </summary>
public ServiceEndpoint[] Endpoints { get; set; }

/// <summary>
/// Specifies the mode for server sticky, when client is always routed to the server which it first /negotiate with, we call it "server sticky mode".
/// By default this mode is disabled
/// </summary>
public ServerStickyMode ServerStickyMode { get; set; }

/// <summary>
/// Gets or sets the proxy used when ServiceEndpoint will attempt to connect to Azure SignalR.
/// </summary>
public IWebProxy Proxy { get; set; }

/// <summary>
/// Gets or sets the interval in seconds used by the Azure SignalR Service to timeout idle LongPolling connections
/// Default value is 5, limited to [1, 300].
/// </summary>
public int? MaxPollIntervalInSeconds { get; set; }

public ServiceOptions()
// Fallback to use AppSettings
if (string.IsNullOrEmpty(connectionString) && endpoints.Count == 0)
{
var count = ConfigurationManager.ConnectionStrings.Count;
string connectionString = null;
var endpoints = new List<ServiceEndpoint>();
var connectionStringKeyPrefix = $"{Constants.Keys.ConnectionStringDefaultKey}:";
for (var i = 0; i < count; i++)
foreach (var key in ConfigurationManager.AppSettings.AllKeys)
{
var setting = ConfigurationManager.ConnectionStrings[i];

if (setting.Name == Constants.Keys.ConnectionStringDefaultKey)
if (key == Constants.Keys.ConnectionStringDefaultKey)
{
connectionString = setting.ConnectionString;
connectionString = ConfigurationManager.AppSettings[key];
}
else if (setting.Name.StartsWith(connectionStringKeyPrefix) && !string.IsNullOrEmpty(setting.ConnectionString))
else if (key.StartsWith(connectionStringKeyPrefix))
{
endpoints.Add(new ServiceEndpoint(setting.Name, setting.ConnectionString));
}
}

// Fallback to use AppSettings
if (string.IsNullOrEmpty(connectionString) && endpoints.Count == 0)
{
foreach (var key in ConfigurationManager.AppSettings.AllKeys)
{
if (key == Constants.Keys.ConnectionStringDefaultKey)
{
connectionString = ConfigurationManager.AppSettings[key];
}
else if (key.StartsWith(connectionStringKeyPrefix))
var value = ConfigurationManager.AppSettings[key];
if (!string.IsNullOrEmpty(value))
{
var value = ConfigurationManager.AppSettings[key];
if (!string.IsNullOrEmpty(value))
{
endpoints.Add(new ServiceEndpoint(key, value));
}
endpoints.Add(new ServiceEndpoint(key, value));
}
}
}

ConnectionString = connectionString;
Endpoints = endpoints.ToArray();
}

ConnectionString = connectionString;
Endpoints = endpoints.ToArray();
}

/// <summary>
/// Gets or sets the access token generate algorithm, supports HmacSha256 or HmacSha512
/// Default value is HmacSha256
/// </summary>
public AccessTokenAlgorithm AccessTokenAlgorithm { get; set; } = AccessTokenAlgorithm.HS256;

/// <summary>
/// Gets or sets the lifetime of auto-generated access token, which will be used to authenticate with Azure SignalR Service.
/// Default value is one hour.
/// </summary>
public TimeSpan AccessTokenLifetime { get; set; } = Constants.Periods.DefaultAccessTokenLifetime;

/// <summary>
/// Gets or sets the interval in seconds used by the Azure SignalR Service to timeout idle LongPolling connections
/// Default value is 5, limited to [1, 300].
/// </summary>
public int? MaxPollIntervalInSeconds { get; set; }

/// <summary>
/// Gets or sets the proxy used when ServiceEndpoint will attempt to connect to Azure SignalR.
/// </summary>
public IWebProxy Proxy { get; set; }

/// <summary>
/// Specifies the mode for server sticky, when client is always routed to the server which it first /negotiate with, we call it "server sticky mode".
/// By default this mode is disabled
/// </summary>
public ServerStickyMode ServerStickyMode { get; set; }

string IServiceEndpointOptions.ApplicationName => ApplicationName;

/// <summary>
/// Gets or sets the func to generate claims from <see cref="IOwinContext" />.
/// The claims will be included in the auto-generated token for clients.
/// </summary>
public Func<IOwinContext, IEnumerable<Claim>> ClaimsProvider { get; set; } = null;

/// <summary>
/// Gets or sets the initial number of connections per hub from SDK to Azure SignalR Service. Default value is 5.
/// Usually keep it as the default value is enough. During runtime, the SDK might start new server connections for performance tuning or load balancing.
/// When you have big number of clients, you can give it a larger number for better throughput.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Please use InitialHubServerConnectionCount instead.")]
public int ConnectionCount
{
get => InitialHubServerConnectionCount;
set => InitialHubServerConnectionCount = value;
}

/// <summary>
/// Gets or sets the connection string of Azure SignalR Service instance.
/// </summary>
public string ConnectionString { get; set; }
/// <summary>
/// Gets or sets the func to set diagnostic client filter from <see cref="IOwinContext" />.
/// The clients will be regarded as diagnostic client only if the function returns true.
/// </summary>
public Func<IOwinContext, bool> DiagnosticClientFilter { get; set; } = null;

/// <summary>
/// Customize the multiple endpoints used
/// </summary>
public ServiceEndpoint[] Endpoints { get; set; }

/// <summary>
/// Gets or sets the initial number of connections per hub from SDK to Azure SignalR Service.
/// Default value is 5.
/// Usually keep it as the default value is enough. When you have big number of clients, you can give it a larger number for better throughput.
/// During runtime, the SDK might start new server connections for performance tuning or load balancing.
/// </summary>
public int InitialHubServerConnectionCount { get; set; } = 5;

/// <summary>
/// Specifies the max server connection count allowed per hub from SDK to Azure SignalR Service.
/// During runtime, the SDK might start new server connections for performance tuning or load balancing.
/// By default a new server connection starts whenever needed.
/// When the max allowed server connection count is configured, the SDK does not start new connections when server connection count reaches the limit.
/// </summary>
public int? MaxHubServerConnectionCount { get; set; }

/// <summary>
/// Gets applicationName, which will be used as a prefix to apply to each hub name
/// </summary>
internal string ApplicationName { get; set; }
}
10 changes: 6 additions & 4 deletions src/Microsoft.Azure.SignalR.Common/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,11 +131,13 @@ public static class Protocol

public static class Headers
{
public const string AsrsHeaderPrefix = "X-ASRS-";
public const string AsrsHeaderPrefix = "ASRS-";

public const string AsrsServerId = AsrsHeaderPrefix + "Server-Id";
public const string AsrsInternalHeaderPrefix = "X-ASRS-";

public const string AsrsMessageTracingId = AsrsHeaderPrefix + "Message-Tracing-Id";
public const string AsrsServerId = AsrsInternalHeaderPrefix + "Server-Id";

public const string AsrsMessageTracingId = AsrsInternalHeaderPrefix + "Message-Tracing-Id";

public const string MicrosoftErrorCode = "x-ms-error-code";
}
Expand Down Expand Up @@ -163,4 +165,4 @@ public static class HttpClientNames

public const string InternalDefault = "InternalDefault";
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

Expand All @@ -17,8 +16,7 @@ Task<ConnectionContext> ConnectAsync(HubServiceEndpoint endpoint,
TransferFormat transferFormat,
string connectionId,
string target,
CancellationToken cancellationToken = default,
IDictionary<string, string>? headers = null);
CancellationToken cancellationToken = default);

// Current plan for IAsyncDisposable is that DisposeAsync will NOT take a CancellationToken
// https://github.com/dotnet/csharplang/blob/195efa07806284d7b57550e7447dc8bd39c156bf/proposals/async-streams.md#iasyncdisposable
Expand Down
Loading
Loading