diff --git a/eng/ProjectReferences.props b/eng/ProjectReferences.props index 067ba79788..e274d05a71 100644 --- a/eng/ProjectReferences.props +++ b/eng/ProjectReferences.props @@ -79,6 +79,7 @@ + diff --git a/src/Middleware/RequestQueue/ref/Microsoft.AspNetCore.RequestQueue.csproj b/src/Middleware/RequestQueue/ref/Microsoft.AspNetCore.RequestQueue.csproj new file mode 100644 index 0000000000..05f6a42637 --- /dev/null +++ b/src/Middleware/RequestQueue/ref/Microsoft.AspNetCore.RequestQueue.csproj @@ -0,0 +1,14 @@ + + + + netcoreapp3.0 + + + + + + + + + + diff --git a/src/Middleware/RequestQueue/ref/Microsoft.AspNetCore.RequestQueue.netcoreapp3.0.cs b/src/Middleware/RequestQueue/ref/Microsoft.AspNetCore.RequestQueue.netcoreapp3.0.cs new file mode 100644 index 0000000000..dc22bca076 --- /dev/null +++ b/src/Middleware/RequestQueue/ref/Microsoft.AspNetCore.RequestQueue.netcoreapp3.0.cs @@ -0,0 +1,51 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Builder +{ + public static partial class HstsBuilderExtensions + { + public static Microsoft.AspNetCore.Builder.IApplicationBuilder UseHsts(this Microsoft.AspNetCore.Builder.IApplicationBuilder app) { throw null; } + } + public static partial class HstsServicesExtensions + { + public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddHsts(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action configureOptions) { throw null; } + } + public static partial class HttpsPolicyBuilderExtensions + { + public static Microsoft.AspNetCore.Builder.IApplicationBuilder UseHttpsRedirection(this Microsoft.AspNetCore.Builder.IApplicationBuilder app) { throw null; } + } + public static partial class HttpsRedirectionServicesExtensions + { + public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddHttpsRedirection(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action configureOptions) { throw null; } + } +} +namespace Microsoft.AspNetCore.HttpsPolicy +{ + public partial class HstsMiddleware + { + public HstsMiddleware(Microsoft.AspNetCore.Http.RequestDelegate next, Microsoft.Extensions.Options.IOptions options) { } + public HstsMiddleware(Microsoft.AspNetCore.Http.RequestDelegate next, Microsoft.Extensions.Options.IOptions options, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } + public System.Threading.Tasks.Task Invoke(Microsoft.AspNetCore.Http.HttpContext context) { throw null; } + } + public partial class HstsOptions + { + public HstsOptions() { } + public System.Collections.Generic.IList ExcludedHosts { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public bool IncludeSubDomains { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.TimeSpan MaxAge { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public bool Preload { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + } + public partial class HttpsRedirectionMiddleware + { + public HttpsRedirectionMiddleware(Microsoft.AspNetCore.Http.RequestDelegate next, Microsoft.Extensions.Options.IOptions options, Microsoft.Extensions.Configuration.IConfiguration config, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } + public HttpsRedirectionMiddleware(Microsoft.AspNetCore.Http.RequestDelegate next, Microsoft.Extensions.Options.IOptions options, Microsoft.Extensions.Configuration.IConfiguration config, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, Microsoft.AspNetCore.Hosting.Server.Features.IServerAddressesFeature serverAddressesFeature) { } + public System.Threading.Tasks.Task Invoke(Microsoft.AspNetCore.Http.HttpContext context) { throw null; } + } + public partial class HttpsRedirectionOptions + { + public HttpsRedirectionOptions() { } + public int? HttpsPort { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public int RedirectStatusCode { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + } +} diff --git a/src/Middleware/RequestQueue/sample/RequestQueueSample.csproj b/src/Middleware/RequestQueue/sample/RequestQueueSample.csproj new file mode 100644 index 0000000000..57892c2249 --- /dev/null +++ b/src/Middleware/RequestQueue/sample/RequestQueueSample.csproj @@ -0,0 +1,13 @@ + + + + netcoreapp3.0 + + + + + + + + + diff --git a/src/Middleware/RequestQueue/sample/Startup.cs b/src/Middleware/RequestQueue/sample/Startup.cs new file mode 100644 index 0000000000..5768d045e6 --- /dev/null +++ b/src/Middleware/RequestQueue/sample/Startup.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.HttpsPolicy; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace RequestQueueSample +{ + public class Startup + { + // This method gets called by the runtime. Use this method to add services to the container. + // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 + public void ConfigureServices(IServiceCollection services) + { + } + + public void Configure(IApplicationBuilder app, IWebHostEnvironment environment) + { + app.Run(async context => + { + await context.Response.WriteAsync("Hello world!"); + }); + } + + // Entry point for the application. + public static void Main(string[] args) + { + var host = new WebHostBuilder() + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) // for the cert file + .ConfigureLogging(factory => + { + factory.SetMinimumLevel(LogLevel.Debug); + factory.AddConsole(); + }) + .UseStartup() + .Build(); + + host.Run(); + } + } +} diff --git a/src/Middleware/RequestQueue/src/Microsoft.AspNetCore.RequestQueue.csproj b/src/Middleware/RequestQueue/src/Microsoft.AspNetCore.RequestQueue.csproj new file mode 100644 index 0000000000..ddc77f3682 --- /dev/null +++ b/src/Middleware/RequestQueue/src/Microsoft.AspNetCore.RequestQueue.csproj @@ -0,0 +1,19 @@ + + + + ASP.NET Core middleware for queuing incoming HTTP requests, to avoid threadpool starvation. + netcoreapp3.0 + $(NoWarn);CS1591 + true + true + $(WarningsNotAsErrors);CS1591 + + + + + + + + + + diff --git a/src/Middleware/RequestQueue/src/SemaphoreWrapper.cs b/src/Middleware/RequestQueue/src/SemaphoreWrapper.cs new file mode 100644 index 0000000000..aff2f66919 --- /dev/null +++ b/src/Middleware/RequestQueue/src/SemaphoreWrapper.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.RequestQueue +{ + internal class SemaphoreWrapper + { + private static SemaphoreSlim semaphore; + + public SemaphoreWrapper(int qlength) + { + semaphore = new SemaphoreSlim(qlength); + } + + public Task EnterQueue() + { + return semaphore.WaitAsync(); + } + + public void LeaveQueue() + { + semaphore.Release(); + } + + public int SpotsLeft + { + get => semaphore.CurrentCount; + } + } +} diff --git a/src/Middleware/RequestQueue/test/Microsoft.AspNetCore.RequestQueue.Tests.csproj b/src/Middleware/RequestQueue/test/Microsoft.AspNetCore.RequestQueue.Tests.csproj new file mode 100644 index 0000000000..b1559a97fe --- /dev/null +++ b/src/Middleware/RequestQueue/test/Microsoft.AspNetCore.RequestQueue.Tests.csproj @@ -0,0 +1,10 @@ + + + + netcoreapp3.0 + + + + + + diff --git a/src/Middleware/RequestQueue/test/SemaphoreWrapperTests.cs b/src/Middleware/RequestQueue/test/SemaphoreWrapperTests.cs new file mode 100644 index 0000000000..33bf6ea861 --- /dev/null +++ b/src/Middleware/RequestQueue/test/SemaphoreWrapperTests.cs @@ -0,0 +1,56 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Xunit; +using System.Threading; +using System.Threading.Tasks; +using System; +using System.Runtime.CompilerServices; +using Microsoft.AspNetCore.Testing; + +namespace Microsoft.AspNetCore.RequestQueue.Tests +{ + public class SemaphoreWrapperTests + { + [Fact] + public async Task TestBehavior() + { + var s = new SemaphoreWrapper(1); + Assert.Equal(1, s.SpotsLeft); + + await s.EnterQueue(); + Assert.Equal(0, s.SpotsLeft); + + s.LeaveQueue(); + Assert.Equal(1, s.SpotsLeft); + } + + [Fact] + public void TestQueueLength() + { + var s = new SemaphoreWrapper(2); + + var t1 = s.EnterQueue(); + Assert.True(t1.IsCompleted); + + var t2 = s.EnterQueue(); + Assert.True(t2.IsCompleted); + + var t3 = s.EnterQueue(); + Assert.False(t3.IsCompleted); + } + + [Fact] + public async Task TestWaiting() + { + var s = new SemaphoreWrapper(1); + await s.EnterQueue(); + + var waitingTask = s.EnterQueue(); + Assert.False(waitingTask.IsCompleted); + + s.LeaveQueue(); + await waitingTask.TimeoutAfter(TimeSpan.FromSeconds(1)); + } + } +} diff --git a/src/Middleware/build.cmd b/src/Middleware/build.cmd new file mode 100644 index 0000000000..033fe6f614 --- /dev/null +++ b/src/Middleware/build.cmd @@ -0,0 +1,3 @@ +@ECHO OFF +SET RepoRoot=%~dp0..\.. +%RepoRoot%\build.cmd -projects %~dp0\**\*.*proj %*