#519 Expose a connection limit option
This commit is contained in:
parent
7c19149d2a
commit
f1901516c6
|
|
@ -146,9 +146,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Options.Authentication.SetUrlGroupSecurity(UrlGroup);
|
Options.Apply(UrlGroup, RequestQueue);
|
||||||
Options.Timeouts.SetUrlGroupTimeouts(UrlGroup);
|
|
||||||
Options.SetRequestQueueLimit(RequestQueue);
|
|
||||||
|
|
||||||
_requestQueue.AttachToUrlGroup();
|
_requestQueue.AttachToUrlGroup();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,9 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
|
|
||||||
// The native request queue
|
// The native request queue
|
||||||
private long _requestQueueLength = DefaultRequestQueueLength;
|
private long _requestQueueLength = DefaultRequestQueueLength;
|
||||||
|
private long? _maxConnections;
|
||||||
private RequestQueue _requestQueue;
|
private RequestQueue _requestQueue;
|
||||||
|
private UrlGroup _urlGroup;
|
||||||
|
|
||||||
public HttpSysOptions()
|
public HttpSysOptions()
|
||||||
{
|
{
|
||||||
|
|
@ -54,6 +56,29 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool ThrowWriteExceptions { get; set; }
|
public bool ThrowWriteExceptions { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the maximum number of concurrent connections to accept, -1 for infinite, or null to
|
||||||
|
/// use the machine wide setting from the registry. The default value is null.
|
||||||
|
/// </summary>
|
||||||
|
public long? MaxConnections
|
||||||
|
{
|
||||||
|
get => _maxConnections;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value.HasValue && value < -1)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(value), value, string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.HasValue && _urlGroup != null)
|
||||||
|
{
|
||||||
|
_urlGroup.SetMaxConnections(value.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
_maxConnections = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the maximum number of requests that will be queued up in Http.Sys.
|
/// Gets or sets the maximum number of requests that will be queued up in Http.Sys.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -79,13 +104,23 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void SetRequestQueueLimit(RequestQueue requestQueue)
|
internal void Apply(UrlGroup urlGroup, RequestQueue requestQueue)
|
||||||
{
|
{
|
||||||
|
_urlGroup = urlGroup;
|
||||||
_requestQueue = requestQueue;
|
_requestQueue = requestQueue;
|
||||||
|
|
||||||
|
if (_maxConnections.HasValue)
|
||||||
|
{
|
||||||
|
_urlGroup.SetMaxConnections(_maxConnections.Value);
|
||||||
|
}
|
||||||
|
|
||||||
if (_requestQueueLength != DefaultRequestQueueLength)
|
if (_requestQueueLength != DefaultRequestQueueLength)
|
||||||
{
|
{
|
||||||
_requestQueue.SetLengthLimit(_requestQueueLength);
|
_requestQueue.SetLengthLimit(_requestQueueLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Authentication.SetUrlGroupSecurity(urlGroup);
|
||||||
|
Timeouts.SetUrlGroupTimeouts(urlGroup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -544,6 +544,13 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
HttpRequestAuthTypeKerberos
|
HttpRequestAuthTypeKerberos
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal enum HTTP_QOS_SETTING_TYPE
|
||||||
|
{
|
||||||
|
HttpQosSettingTypeBandwidth,
|
||||||
|
HttpQosSettingTypeConnectionLimit,
|
||||||
|
HttpQosSettingTypeFlowRate
|
||||||
|
}
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
internal struct HTTP_SERVER_AUTHENTICATION_INFO
|
internal struct HTTP_SERVER_AUTHENTICATION_INFO
|
||||||
{
|
{
|
||||||
|
|
@ -604,6 +611,20 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
internal IntPtr RequestQueueHandle;
|
internal IntPtr RequestQueueHandle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
internal struct HTTP_CONNECTION_LIMIT_INFO
|
||||||
|
{
|
||||||
|
internal HTTP_FLAGS Flags;
|
||||||
|
internal uint MaxConnections;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
internal struct HTTP_QOS_SETTING_INFO
|
||||||
|
{
|
||||||
|
internal HTTP_QOS_SETTING_TYPE QosType;
|
||||||
|
internal IntPtr QosSetting;
|
||||||
|
}
|
||||||
|
|
||||||
// see http.w for definitions
|
// see http.w for definitions
|
||||||
[Flags]
|
[Flags]
|
||||||
internal enum HTTP_FLAGS : uint
|
internal enum HTTP_FLAGS : uint
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,16 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.HttpSys
|
namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
{
|
{
|
||||||
internal class UrlGroup : IDisposable
|
internal class UrlGroup : IDisposable
|
||||||
{
|
{
|
||||||
|
private static readonly int QosInfoSize =
|
||||||
|
Marshal.SizeOf<HttpApi.HTTP_QOS_SETTING_INFO>();
|
||||||
|
|
||||||
private ServerSession _serverSession;
|
private ServerSession _serverSession;
|
||||||
private ILogger _logger;
|
private ILogger _logger;
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
@ -33,6 +37,19 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
|
|
||||||
internal ulong Id { get; private set; }
|
internal ulong Id { get; private set; }
|
||||||
|
|
||||||
|
internal unsafe void SetMaxConnections(long maxConnections)
|
||||||
|
{
|
||||||
|
var connectionLimit = new HttpApi.HTTP_CONNECTION_LIMIT_INFO();
|
||||||
|
connectionLimit.Flags = HttpApi.HTTP_FLAGS.HTTP_PROPERTY_FLAG_PRESENT;
|
||||||
|
connectionLimit.MaxConnections = (uint)maxConnections;
|
||||||
|
|
||||||
|
var qosSettings = new HttpApi.HTTP_QOS_SETTING_INFO();
|
||||||
|
qosSettings.QosType = HttpApi.HTTP_QOS_SETTING_TYPE.HttpQosSettingTypeConnectionLimit;
|
||||||
|
qosSettings.QosSetting = new IntPtr(&connectionLimit);
|
||||||
|
|
||||||
|
SetProperty(HttpApi.HTTP_SERVER_PROPERTY.HttpServerQosProperty, new IntPtr(&qosSettings), (uint)QosInfoSize);
|
||||||
|
}
|
||||||
|
|
||||||
internal void SetProperty(HttpApi.HTTP_SERVER_PROPERTY property, IntPtr info, uint infosize, bool throwOnError = true)
|
internal void SetProperty(HttpApi.HTTP_SERVER_PROPERTY property, IntPtr info, uint infosize, bool throwOnError = true)
|
||||||
{
|
{
|
||||||
Debug.Assert(info != IntPtr.Zero, "SetUrlGroupProperty called with invalid pointer");
|
Debug.Assert(info != IntPtr.Zero, "SetUrlGroupProperty called with invalid pointer");
|
||||||
|
|
|
||||||
|
|
@ -300,6 +300,112 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[ConditionalFact]
|
||||||
|
public void Server_SetConnectionLimitArgumentValidation_Success()
|
||||||
|
{
|
||||||
|
var server = Utilities.CreatePump();
|
||||||
|
|
||||||
|
Assert.Null(server.Listener.Options.MaxConnections);
|
||||||
|
Assert.Throws<ArgumentOutOfRangeException>(() => server.Listener.Options.MaxConnections = -2);
|
||||||
|
Assert.Null(server.Listener.Options.MaxConnections);
|
||||||
|
server.Listener.Options.MaxConnections = null;
|
||||||
|
server.Listener.Options.MaxConnections = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConditionalFact]
|
||||||
|
public async Task Server_SetConnectionLimit_Success()
|
||||||
|
{
|
||||||
|
// This is just to get a dynamic port
|
||||||
|
string address;
|
||||||
|
using (Utilities.CreateHttpServer(out address, httpContext => Task.FromResult(0))) { }
|
||||||
|
|
||||||
|
var server = Utilities.CreatePump();
|
||||||
|
server.Listener.Options.UrlPrefixes.Add(UrlPrefix.Create(address));
|
||||||
|
Assert.Null(server.Listener.Options.MaxConnections);
|
||||||
|
server.Listener.Options.MaxConnections = 3;
|
||||||
|
|
||||||
|
using (server)
|
||||||
|
{
|
||||||
|
await server.StartAsync(new DummyApplication(), CancellationToken.None);
|
||||||
|
|
||||||
|
using (var client1 = await SendHungRequestAsync("GET", address))
|
||||||
|
using (var client2 = await SendHungRequestAsync("GET", address))
|
||||||
|
{
|
||||||
|
using (var client3 = await SendHungRequestAsync("GET", address))
|
||||||
|
{
|
||||||
|
// Maxed out, refuses connection and throws
|
||||||
|
await Assert.ThrowsAsync<HttpRequestException>(() => SendRequestAsync(address));
|
||||||
|
}
|
||||||
|
|
||||||
|
// A connection has been closed, try again.
|
||||||
|
string responseText = await SendRequestAsync(address);
|
||||||
|
Assert.Equal(string.Empty, responseText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConditionalFact]
|
||||||
|
public async Task Server_SetConnectionLimitChangeAfterStarted_Success()
|
||||||
|
{
|
||||||
|
// This is just to get a dynamic port
|
||||||
|
string address;
|
||||||
|
using (Utilities.CreateHttpServer(out address, httpContext => Task.FromResult(0))) { }
|
||||||
|
|
||||||
|
var server = Utilities.CreatePump();
|
||||||
|
server.Listener.Options.UrlPrefixes.Add(UrlPrefix.Create(address));
|
||||||
|
Assert.Null(server.Listener.Options.MaxConnections);
|
||||||
|
server.Listener.Options.MaxConnections = 3;
|
||||||
|
|
||||||
|
using (server)
|
||||||
|
{
|
||||||
|
await server.StartAsync(new DummyApplication(), CancellationToken.None);
|
||||||
|
|
||||||
|
using (var client1 = await SendHungRequestAsync("GET", address))
|
||||||
|
using (var client2 = await SendHungRequestAsync("GET", address))
|
||||||
|
using (var client3 = await SendHungRequestAsync("GET", address))
|
||||||
|
{
|
||||||
|
// Maxed out, refuses connection and throws
|
||||||
|
await Assert.ThrowsAsync<HttpRequestException>(() => SendRequestAsync(address));
|
||||||
|
|
||||||
|
server.Listener.Options.MaxConnections = 4;
|
||||||
|
|
||||||
|
string responseText = await SendRequestAsync(address);
|
||||||
|
Assert.Equal(string.Empty, responseText);
|
||||||
|
|
||||||
|
server.Listener.Options.MaxConnections = 2;
|
||||||
|
|
||||||
|
// Maxed out, refuses connection and throws
|
||||||
|
await Assert.ThrowsAsync<HttpRequestException>(() => SendRequestAsync(address));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConditionalFact]
|
||||||
|
public async Task Server_SetConnectionLimitInfinite_Success()
|
||||||
|
{
|
||||||
|
// This is just to get a dynamic port
|
||||||
|
string address;
|
||||||
|
using (Utilities.CreateHttpServer(out address, httpContext => Task.FromResult(0))) { }
|
||||||
|
|
||||||
|
var server = Utilities.CreatePump();
|
||||||
|
server.Listener.Options.UrlPrefixes.Add(UrlPrefix.Create(address));
|
||||||
|
server.Listener.Options.MaxConnections = -1; // infinite
|
||||||
|
|
||||||
|
using (server)
|
||||||
|
{
|
||||||
|
await server.StartAsync(new DummyApplication(), CancellationToken.None);
|
||||||
|
|
||||||
|
using (var client1 = await SendHungRequestAsync("GET", address))
|
||||||
|
using (var client2 = await SendHungRequestAsync("GET", address))
|
||||||
|
using (var client3 = await SendHungRequestAsync("GET", address))
|
||||||
|
{
|
||||||
|
// Doesn't max out
|
||||||
|
string responseText = await SendRequestAsync(address);
|
||||||
|
Assert.Equal(string.Empty, responseText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<string> SendRequestAsync(string uri)
|
private async Task<string> SendRequestAsync(string uri)
|
||||||
{
|
{
|
||||||
using (HttpClient client = new HttpClient())
|
using (HttpClient client = new HttpClient())
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue