#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;
|
||||
}
|
||||
|
||||
Options.Authentication.SetUrlGroupSecurity(UrlGroup);
|
||||
Options.Timeouts.SetUrlGroupTimeouts(UrlGroup);
|
||||
Options.SetRequestQueueLimit(RequestQueue);
|
||||
Options.Apply(UrlGroup, RequestQueue);
|
||||
|
||||
_requestQueue.AttachToUrlGroup();
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,9 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
|
||||
// The native request queue
|
||||
private long _requestQueueLength = DefaultRequestQueueLength;
|
||||
private long? _maxConnections;
|
||||
private RequestQueue _requestQueue;
|
||||
private UrlGroup _urlGroup;
|
||||
|
||||
public HttpSysOptions()
|
||||
{
|
||||
|
|
@ -54,6 +56,29 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
/// </summary>
|
||||
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>
|
||||
/// Gets or sets the maximum number of requests that will be queued up in Http.Sys.
|
||||
/// </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;
|
||||
|
||||
if (_maxConnections.HasValue)
|
||||
{
|
||||
_urlGroup.SetMaxConnections(_maxConnections.Value);
|
||||
}
|
||||
|
||||
if (_requestQueueLength != DefaultRequestQueueLength)
|
||||
{
|
||||
_requestQueue.SetLengthLimit(_requestQueueLength);
|
||||
}
|
||||
|
||||
Authentication.SetUrlGroupSecurity(urlGroup);
|
||||
Timeouts.SetUrlGroupTimeouts(urlGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -544,6 +544,13 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
HttpRequestAuthTypeKerberos
|
||||
}
|
||||
|
||||
internal enum HTTP_QOS_SETTING_TYPE
|
||||
{
|
||||
HttpQosSettingTypeBandwidth,
|
||||
HttpQosSettingTypeConnectionLimit,
|
||||
HttpQosSettingTypeFlowRate
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct HTTP_SERVER_AUTHENTICATION_INFO
|
||||
{
|
||||
|
|
@ -604,6 +611,20 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
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
|
||||
[Flags]
|
||||
internal enum HTTP_FLAGS : uint
|
||||
|
|
|
|||
|
|
@ -3,12 +3,16 @@
|
|||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.HttpSys
|
||||
{
|
||||
internal class UrlGroup : IDisposable
|
||||
{
|
||||
private static readonly int QosInfoSize =
|
||||
Marshal.SizeOf<HttpApi.HTTP_QOS_SETTING_INFO>();
|
||||
|
||||
private ServerSession _serverSession;
|
||||
private ILogger _logger;
|
||||
private bool _disposed;
|
||||
|
|
@ -33,6 +37,19 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
|||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
using (HttpClient client = new HttpClient())
|
||||
|
|
|
|||
Loading…
Reference in New Issue