Skip to content
This repository was archived by the owner on Mar 19, 2019. It is now read-only.

Commit f190151

Browse files
committed
#519 Expose a connection limit option
1 parent 7c19149 commit f190151

File tree

5 files changed

+181
-4
lines changed

5 files changed

+181
-4
lines changed

src/Microsoft.AspNetCore.Server.HttpSys/HttpSysListener.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,9 +146,7 @@ public void Start()
146146
return;
147147
}
148148

149-
Options.Authentication.SetUrlGroupSecurity(UrlGroup);
150-
Options.Timeouts.SetUrlGroupTimeouts(UrlGroup);
151-
Options.SetRequestQueueLimit(RequestQueue);
149+
Options.Apply(UrlGroup, RequestQueue);
152150

153151
_requestQueue.AttachToUrlGroup();
154152

src/Microsoft.AspNetCore.Server.HttpSys/HttpSysOptions.cs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ public class HttpSysOptions
1212

1313
// The native request queue
1414
private long _requestQueueLength = DefaultRequestQueueLength;
15+
private long? _maxConnections;
1516
private RequestQueue _requestQueue;
17+
private UrlGroup _urlGroup;
1618

1719
public HttpSysOptions()
1820
{
@@ -54,6 +56,29 @@ public HttpSysOptions()
5456
/// </summary>
5557
public bool ThrowWriteExceptions { get; set; }
5658

59+
/// <summary>
60+
/// Gets or sets the maximum number of concurrent connections to accept, -1 for infinite, or null to
61+
/// use the machine wide setting from the registry. The default value is null.
62+
/// </summary>
63+
public long? MaxConnections
64+
{
65+
get => _maxConnections;
66+
set
67+
{
68+
if (value.HasValue && value < -1)
69+
{
70+
throw new ArgumentOutOfRangeException(nameof(value), value, string.Empty);
71+
}
72+
73+
if (value.HasValue && _urlGroup != null)
74+
{
75+
_urlGroup.SetMaxConnections(value.Value);
76+
}
77+
78+
_maxConnections = value;
79+
}
80+
}
81+
5782
/// <summary>
5883
/// Gets or sets the maximum number of requests that will be queued up in Http.Sys.
5984
/// </summary>
@@ -79,13 +104,23 @@ public long RequestQueueLimit
79104
}
80105
}
81106

82-
internal void SetRequestQueueLimit(RequestQueue requestQueue)
107+
internal void Apply(UrlGroup urlGroup, RequestQueue requestQueue)
83108
{
109+
_urlGroup = urlGroup;
84110
_requestQueue = requestQueue;
111+
112+
if (_maxConnections.HasValue)
113+
{
114+
_urlGroup.SetMaxConnections(_maxConnections.Value);
115+
}
116+
85117
if (_requestQueueLength != DefaultRequestQueueLength)
86118
{
87119
_requestQueue.SetLengthLimit(_requestQueueLength);
88120
}
121+
122+
Authentication.SetUrlGroupSecurity(urlGroup);
123+
Timeouts.SetUrlGroupTimeouts(urlGroup);
89124
}
90125
}
91126
}

src/Microsoft.AspNetCore.Server.HttpSys/NativeInterop/HttpApi.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,13 @@ internal enum HTTP_REQUEST_AUTH_TYPE
544544
HttpRequestAuthTypeKerberos
545545
}
546546

547+
internal enum HTTP_QOS_SETTING_TYPE
548+
{
549+
HttpQosSettingTypeBandwidth,
550+
HttpQosSettingTypeConnectionLimit,
551+
HttpQosSettingTypeFlowRate
552+
}
553+
547554
[StructLayout(LayoutKind.Sequential)]
548555
internal struct HTTP_SERVER_AUTHENTICATION_INFO
549556
{
@@ -604,6 +611,20 @@ internal struct HTTP_BINDING_INFO
604611
internal IntPtr RequestQueueHandle;
605612
}
606613

614+
[StructLayout(LayoutKind.Sequential)]
615+
internal struct HTTP_CONNECTION_LIMIT_INFO
616+
{
617+
internal HTTP_FLAGS Flags;
618+
internal uint MaxConnections;
619+
}
620+
621+
[StructLayout(LayoutKind.Sequential)]
622+
internal struct HTTP_QOS_SETTING_INFO
623+
{
624+
internal HTTP_QOS_SETTING_TYPE QosType;
625+
internal IntPtr QosSetting;
626+
}
627+
607628
// see http.w for definitions
608629
[Flags]
609630
internal enum HTTP_FLAGS : uint

src/Microsoft.AspNetCore.Server.HttpSys/NativeInterop/UrlGroup.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,16 @@
33

44
using System;
55
using System.Diagnostics;
6+
using System.Runtime.InteropServices;
67
using Microsoft.Extensions.Logging;
78

89
namespace Microsoft.AspNetCore.Server.HttpSys
910
{
1011
internal class UrlGroup : IDisposable
1112
{
13+
private static readonly int QosInfoSize =
14+
Marshal.SizeOf<HttpApi.HTTP_QOS_SETTING_INFO>();
15+
1216
private ServerSession _serverSession;
1317
private ILogger _logger;
1418
private bool _disposed;
@@ -33,6 +37,19 @@ internal unsafe UrlGroup(ServerSession serverSession, ILogger logger)
3337

3438
internal ulong Id { get; private set; }
3539

40+
internal unsafe void SetMaxConnections(long maxConnections)
41+
{
42+
var connectionLimit = new HttpApi.HTTP_CONNECTION_LIMIT_INFO();
43+
connectionLimit.Flags = HttpApi.HTTP_FLAGS.HTTP_PROPERTY_FLAG_PRESENT;
44+
connectionLimit.MaxConnections = (uint)maxConnections;
45+
46+
var qosSettings = new HttpApi.HTTP_QOS_SETTING_INFO();
47+
qosSettings.QosType = HttpApi.HTTP_QOS_SETTING_TYPE.HttpQosSettingTypeConnectionLimit;
48+
qosSettings.QosSetting = new IntPtr(&connectionLimit);
49+
50+
SetProperty(HttpApi.HTTP_SERVER_PROPERTY.HttpServerQosProperty, new IntPtr(&qosSettings), (uint)QosInfoSize);
51+
}
52+
3653
internal void SetProperty(HttpApi.HTTP_SERVER_PROPERTY property, IntPtr info, uint infosize, bool throwOnError = true)
3754
{
3855
Debug.Assert(info != IntPtr.Zero, "SetUrlGroupProperty called with invalid pointer");

test/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests/ServerTests.cs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,112 @@ public async Task Server_SetQueueLimit_Success()
300300
}
301301
}
302302

303+
[ConditionalFact]
304+
public void Server_SetConnectionLimitArgumentValidation_Success()
305+
{
306+
var server = Utilities.CreatePump();
307+
308+
Assert.Null(server.Listener.Options.MaxConnections);
309+
Assert.Throws<ArgumentOutOfRangeException>(() => server.Listener.Options.MaxConnections = -2);
310+
Assert.Null(server.Listener.Options.MaxConnections);
311+
server.Listener.Options.MaxConnections = null;
312+
server.Listener.Options.MaxConnections = 3;
313+
}
314+
315+
[ConditionalFact]
316+
public async Task Server_SetConnectionLimit_Success()
317+
{
318+
// This is just to get a dynamic port
319+
string address;
320+
using (Utilities.CreateHttpServer(out address, httpContext => Task.FromResult(0))) { }
321+
322+
var server = Utilities.CreatePump();
323+
server.Listener.Options.UrlPrefixes.Add(UrlPrefix.Create(address));
324+
Assert.Null(server.Listener.Options.MaxConnections);
325+
server.Listener.Options.MaxConnections = 3;
326+
327+
using (server)
328+
{
329+
await server.StartAsync(new DummyApplication(), CancellationToken.None);
330+
331+
using (var client1 = await SendHungRequestAsync("GET", address))
332+
using (var client2 = await SendHungRequestAsync("GET", address))
333+
{
334+
using (var client3 = await SendHungRequestAsync("GET", address))
335+
{
336+
// Maxed out, refuses connection and throws
337+
await Assert.ThrowsAsync<HttpRequestException>(() => SendRequestAsync(address));
338+
}
339+
340+
// A connection has been closed, try again.
341+
string responseText = await SendRequestAsync(address);
342+
Assert.Equal(string.Empty, responseText);
343+
}
344+
}
345+
}
346+
347+
[ConditionalFact]
348+
public async Task Server_SetConnectionLimitChangeAfterStarted_Success()
349+
{
350+
// This is just to get a dynamic port
351+
string address;
352+
using (Utilities.CreateHttpServer(out address, httpContext => Task.FromResult(0))) { }
353+
354+
var server = Utilities.CreatePump();
355+
server.Listener.Options.UrlPrefixes.Add(UrlPrefix.Create(address));
356+
Assert.Null(server.Listener.Options.MaxConnections);
357+
server.Listener.Options.MaxConnections = 3;
358+
359+
using (server)
360+
{
361+
await server.StartAsync(new DummyApplication(), CancellationToken.None);
362+
363+
using (var client1 = await SendHungRequestAsync("GET", address))
364+
using (var client2 = await SendHungRequestAsync("GET", address))
365+
using (var client3 = await SendHungRequestAsync("GET", address))
366+
{
367+
// Maxed out, refuses connection and throws
368+
await Assert.ThrowsAsync<HttpRequestException>(() => SendRequestAsync(address));
369+
370+
server.Listener.Options.MaxConnections = 4;
371+
372+
string responseText = await SendRequestAsync(address);
373+
Assert.Equal(string.Empty, responseText);
374+
375+
server.Listener.Options.MaxConnections = 2;
376+
377+
// Maxed out, refuses connection and throws
378+
await Assert.ThrowsAsync<HttpRequestException>(() => SendRequestAsync(address));
379+
}
380+
}
381+
}
382+
383+
[ConditionalFact]
384+
public async Task Server_SetConnectionLimitInfinite_Success()
385+
{
386+
// This is just to get a dynamic port
387+
string address;
388+
using (Utilities.CreateHttpServer(out address, httpContext => Task.FromResult(0))) { }
389+
390+
var server = Utilities.CreatePump();
391+
server.Listener.Options.UrlPrefixes.Add(UrlPrefix.Create(address));
392+
server.Listener.Options.MaxConnections = -1; // infinite
393+
394+
using (server)
395+
{
396+
await server.StartAsync(new DummyApplication(), CancellationToken.None);
397+
398+
using (var client1 = await SendHungRequestAsync("GET", address))
399+
using (var client2 = await SendHungRequestAsync("GET", address))
400+
using (var client3 = await SendHungRequestAsync("GET", address))
401+
{
402+
// Doesn't max out
403+
string responseText = await SendRequestAsync(address);
404+
Assert.Equal(string.Empty, responseText);
405+
}
406+
}
407+
}
408+
303409
private async Task<string> SendRequestAsync(string uri)
304410
{
305411
using (HttpClient client = new HttpClient())

0 commit comments

Comments
 (0)