├── global.json
├── test
└── LimitsMiddleware.AspNetCore.Tests
│ ├── xunit.runner.json
│ ├── HttpResponseExtensions.cs
│ ├── Helpers
│ ├── TaskExt.cs
│ └── EapTask.cs
│ ├── Properties
│ └── AssemblyInfo.cs
│ ├── project.json
│ ├── LimitsMiddleware.AspNetCore.Tests.xproj
│ ├── MinResponseDelayMiddlewareTests.cs
│ ├── MaxUrlLengthTests.cs
│ ├── MaxQueryStringTests.cs
│ ├── MaxBandwidthPerRequestTests.cs
│ ├── MaxConcurrentRequestsTests.cs
│ ├── ConnectionTimeoutTests.cs
│ ├── MaxBandwidthGlobalTests.cs
│ ├── MaxRequestContentLengthTests.cs
│ ├── RateLimiters
│ └── FixedTokenBucketTests.cs
│ └── Files
│ └── file_64KB.txt
├── src
└── LimitsMiddleware.AspNetCore
│ ├── RateLimiters
│ ├── GetUtcNow.cs
│ ├── SystemClock.cs
│ └── FixedTokenBucket.cs
│ ├── ContentLengthRequiredException.cs
│ ├── ContentLengthExceededException.cs
│ ├── InterlockedBooleanExtensions.cs
│ ├── Properties
│ └── AssemblyInfo.cs
│ ├── RequestContext.cs
│ ├── LimitsMiddleware.xproj
│ ├── LimitsMiddleware.AspNetCore.xproj
│ ├── project.json
│ ├── Limits.MaxUrlLength.cs
│ ├── Extensions
│ ├── ApplicationBuilderExtensions.MaxBandwidthGlobal.cs
│ ├── ApplicationBuilderExtensions.MaxUrlLength.cs
│ ├── ApplicationBuilderExtensions.MaxRequestContentLength.cs
│ ├── ApplicationBuilderExtensions.MaxQueryStringLength.cs
│ ├── ApplicationBuilderExtensions.MaxBandwidthPerRequest.cs
│ ├── ApplicationBuilderExtensions.ConnectionTimeout.cs
│ ├── ApplicationBuilderExtensions.MaxConcurrentRequests.cs
│ └── ApplicationBuilderExtensions.MinResponseDelay.cs
│ ├── InterlockedBoolean.cs
│ ├── Limits.MaxBandwidthGlobal.cs
│ ├── TimeoutStream.cs
│ ├── Limits.MaxQueryStringLength.cs
│ ├── Limits.ConnectionTimeout.cs
│ ├── Limits.MaxBandwidthPerRequest.cs
│ ├── ThrottledStream.cs
│ ├── ContentLengthLimitingStream.cs
│ ├── Limits.MaxConcurrentRequests.cs
│ ├── Limits.MinResponseDelay.cs
│ └── Limits.MaxRequestContentLength.cs
├── demo
└── LimitsMiddleware.AspNetCore.Demo
│ ├── web.config
│ ├── Properties
│ └── launchSettings.json
│ ├── Program.cs
│ ├── project.json
│ ├── LimitsMiddleware.Demo.xproj
│ └── Startup.cs
├── appveyor.yml
├── .gitattributes
├── LICENSE
├── README.md
├── LimitsMiddleware.sln
└── .gitignore
/global.json:
--------------------------------------------------------------------------------
1 | {
2 | "projects": [ "src", "test" ],
3 | "sdk": { "version": "1.0.0-preview2-003131" }
4 | }
--------------------------------------------------------------------------------
/test/LimitsMiddleware.AspNetCore.Tests/xunit.runner.json:
--------------------------------------------------------------------------------
1 | {
2 | "diagnosticMessages": true,
3 | "parallelizeTestCollections": false
4 | }
--------------------------------------------------------------------------------
/src/LimitsMiddleware.AspNetCore/RateLimiters/GetUtcNow.cs:
--------------------------------------------------------------------------------
1 | namespace LimitsMiddleware.RateLimiters
2 | {
3 | using System;
4 |
5 | internal delegate DateTimeOffset GetUtcNow();
6 | }
--------------------------------------------------------------------------------
/src/LimitsMiddleware.AspNetCore/ContentLengthRequiredException.cs:
--------------------------------------------------------------------------------
1 | namespace LimitsMiddleware
2 | {
3 | using System;
4 |
5 | internal class ContentLengthRequiredException : Exception
6 | {}
7 | }
--------------------------------------------------------------------------------
/src/LimitsMiddleware.AspNetCore/RateLimiters/SystemClock.cs:
--------------------------------------------------------------------------------
1 | // https://github.com/robertmircea/RateLimiters
2 |
3 | namespace LimitsMiddleware.RateLimiters
4 | {
5 | using System;
6 |
7 | internal static class SystemClock
8 | {
9 | internal static readonly GetUtcNow GetUtcNow = () => DateTimeOffset.UtcNow;
10 | }
11 | }
--------------------------------------------------------------------------------
/src/LimitsMiddleware.AspNetCore/ContentLengthExceededException.cs:
--------------------------------------------------------------------------------
1 | // https://github.com/robertmircea/RateLimiters
2 |
3 | namespace LimitsMiddleware
4 | {
5 | using System;
6 |
7 | internal class ContentLengthExceededException : Exception
8 | {
9 | public ContentLengthExceededException(string message) : base(message)
10 | {}
11 | }
12 | }
--------------------------------------------------------------------------------
/test/LimitsMiddleware.AspNetCore.Tests/HttpResponseExtensions.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Http;
2 |
3 | namespace LimitsMiddleware.Tests
4 | {
5 | public static class HttpResponseExtensions
6 | {
7 | public static bool IsSuccessStatusCode(this HttpResponse response) => (response.StatusCode >= 200) && (response.StatusCode <= 299);
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/LimitsMiddleware.AspNetCore/InterlockedBooleanExtensions.cs:
--------------------------------------------------------------------------------
1 | namespace LimitsMiddleware
2 | {
3 | internal static class InterlockedBooleanExtensions
4 | {
5 | internal static bool EnsureCalledOnce(this InterlockedBoolean interlockedBoolean)
6 | {
7 | return interlockedBoolean.CompareExchange(true, false);
8 | }
9 | }
10 | }
--------------------------------------------------------------------------------
/demo/LimitsMiddleware.AspNetCore.Demo/web.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/test/LimitsMiddleware.AspNetCore.Tests/Helpers/TaskExt.cs:
--------------------------------------------------------------------------------
1 | namespace LimitsMiddleware.Tests.Helpers
2 | {
3 | using System;
4 | using System.Threading.Tasks;
5 |
6 | // http://stackoverflow.com/a/22786826
7 |
8 | public static class TaskExt
9 | {
10 | public static EapTask> FromEvent()
11 | {
12 | var tcs = new TaskCompletionSource();
13 | var handler = new EventHandler((_, eventArgs) => tcs.TrySetResult(eventArgs));
14 | return new EapTask>(tcs, handler);
15 | }
16 | }
17 | }
--------------------------------------------------------------------------------
/demo/LimitsMiddleware.AspNetCore.Demo/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "iisSettings": {
3 | "windowsAuthentication": false,
4 | "anonymousAuthentication": true,
5 | "iisExpress": {
6 | "applicationUrl": "http://localhost:3749/",
7 | "sslPort": 0
8 | }
9 | },
10 | "profiles": {
11 | "IIS Express": {
12 | "commandName": "IISExpress",
13 | "launchBrowser": true,
14 | "environmentVariables": {
15 | "ASPNETCORE_ENVIRONMENT": "Development"
16 | }
17 | },
18 | "LimitsMiddleware.Demo": {
19 | "commandName": "Project",
20 | "launchBrowser": true,
21 | "launchUrl": "http://localhost:5000",
22 | "environmentVariables": {
23 | "ASPNETCORE_ENVIRONMENT": "Development"
24 | }
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/demo/LimitsMiddleware.AspNetCore.Demo/Program.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using Microsoft.AspNetCore.Hosting;
3 | using Microsoft.Extensions.Configuration;
4 |
5 | namespace LimitsMiddleware.Demo
6 | {
7 | public class Program
8 | {
9 | public static void Main()
10 | {
11 | var config = new ConfigurationBuilder()
12 | .AddJsonFile("appsettings.json")
13 | .SetBasePath(Directory.GetCurrentDirectory())
14 | .Build();
15 |
16 | var host = new WebHostBuilder()
17 | .UseKestrel()
18 | .UseConfiguration(config)
19 | .UseContentRoot(Directory.GetCurrentDirectory())
20 | .UseStartup()
21 | .Build();
22 |
23 | host.Run();
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/test/LimitsMiddleware.AspNetCore.Tests/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyConfiguration("")]
9 | [assembly: AssemblyCompany("")]
10 | [assembly: AssemblyProduct("LimitsMiddleware.Tests")]
11 | [assembly: AssemblyTrademark("")]
12 |
13 | // Setting ComVisible to false makes the types in this assembly not visible
14 | // to COM components. If you need to access a type in this assembly from
15 | // COM, set the ComVisible attribute to true on that type.
16 | [assembly: ComVisible(false)]
17 |
18 | // The following GUID is for the ID of the typelib if this project is exposed to COM
19 | [assembly: Guid("1e42d5f7-31eb-4c2b-afd6-841d2024e636")]
20 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | version: 3.2.0-alpha-{build}
2 | branches:
3 | only:
4 | - master
5 | pull_requests:
6 | do_not_increment_build_number: true
7 | environment:
8 | COVERALLS_REPO_TOKEN:
9 | secure: baW+hRvE5yKbwNrQtIGSC0X+g1z2DaD5YYc5RiLRqkHaLAlzMqgd8lEV0L1tT+IC
10 | COVER_FILTER: '+[Limits*]* -[*]*.Logging.* -[*Tests]*'
11 | build_script:
12 | - ps: .\build\build.ps1 $env:APPVEYOR_BUILD_VERSION $env:APPVEYOR_REPO_TAG_NAME
13 | test_script:
14 | - ps: |
15 | if ($true)
16 | {
17 | .\build\test.ps1
18 | .\build\cover.ps1 "$env:COVERALLS_REPO_TOKEN" "$env:COVER_FILTER"
19 | }
20 | artifacts:
21 | - path: '**\*.nupkg'
22 | deploy:
23 | - provider: NuGet
24 | api_key:
25 | secure: Uft/AgWL0ObDUb6hWLhsftRR1sNhLa5vONUcVa/2KjVAYZApxZD6ckJ+ABFQs3bB
26 | skip_symbols: true
27 | artifact: /.*\.nupkg/
28 | on:
29 | branch: master # release from master branch only
30 | appveyor_repo_tag: true # deploy on tag push only
31 |
--------------------------------------------------------------------------------
/test/LimitsMiddleware.AspNetCore.Tests/project.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.0.0",
3 | "testRunner": "xunit",
4 |
5 | "buildOptions": {
6 | "define": [ "LIBLOG_PORTABLE" ],
7 | "copyToOutput": {
8 | "include": [ "Files/*.*", "xunit.runner.json" ]
9 | }
10 | },
11 |
12 | "dependencies": {
13 | "coveralls.io": "1.3.4",
14 | "dotnet-test-xunit": "2.2.0-preview2-build1029",
15 | "LimitsMiddleware.AspNetCore": { "target": "project" },
16 | "Shouldly": "2.8.2",
17 | "OpenCover": "4.6.519",
18 | "xunit": "2.2.0-beta2-build3300",
19 | "Microsoft.AspNetCore.StaticFiles": "1.0.0",
20 | "Microsoft.AspNetCore.TestHost": "1.0.0",
21 | "System.Reflection": "4.1.0"
22 | },
23 |
24 | "frameworks": {
25 | "netcoreapp1.0": {
26 | "imports": [ "dotnet54" ],
27 | "dependencies": {
28 | "Microsoft.NETCore.App": {
29 | "type": "platform",
30 | "version": "1.0.1"
31 | }
32 | }
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/LimitsMiddleware.AspNetCore/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyConfiguration("")]
9 | [assembly: AssemblyCompany("")]
10 | [assembly: AssemblyProduct("LimitsMiddleware")]
11 | [assembly: AssemblyTrademark("")]
12 |
13 | // Setting ComVisible to false makes the types in this assembly not visible
14 | // to COM components. If you need to access a type in this assembly from
15 | // COM, set the ComVisible attribute to true on that type.
16 | [assembly: ComVisible(false)]
17 |
18 | // The following GUID is for the ID of the typelib if this project is exposed to COM
19 | [assembly: Guid("a61b64d6-3d62-472e-b9ba-2435166c6786")]
20 | [assembly:InternalsVisibleTo("LimitsMiddleware.AspNetCore.Tests")]
21 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Set default behaviour, in case users don't have core.autocrlf set.
2 | * text=auto
3 |
4 | # Explicitly declare text files we want to always be normalized and converted
5 | # to native line endings on checkout.
6 | *.c text
7 | *.h text
8 | *.cs text
9 | *.config text
10 | *.xml text
11 | *.manifest text
12 | *.bat text
13 | *.cmd text
14 | *.sh text
15 | *.txt text
16 | *.dat text
17 | *.rc text
18 | *.ps1 text
19 | *.psm1 text
20 | *.js text
21 | *.css text
22 | *.html text
23 | *.sln text
24 | *.DotSettings text
25 | *.csproj text
26 | *.ncrunchproject text
27 | *.fs text
28 | *.fsproj text
29 | *.liquid text
30 | *.boo text
31 | *.pp text
32 | *.targets text
33 | *.markdown text
34 | *.md text
35 | *.bat text
36 | *.xslt text
37 |
38 | # Declare files that will always have CRLF line endings on checkout.
39 |
40 | # Denote all files that are truly binary and should not be modified.
41 | *.ico binary
42 | *.gif binary
43 | *.png binary
44 | *.jpg binary
45 | *.dll binary
46 | *.exe binary
47 | *.pdb binary
48 |
--------------------------------------------------------------------------------
/src/LimitsMiddleware.AspNetCore/RequestContext.cs:
--------------------------------------------------------------------------------
1 | namespace LimitsMiddleware
2 | {
3 | using System;
4 | using Microsoft.AspNetCore.Http;
5 | using Microsoft.AspNetCore.Http.Extensions;
6 |
7 | public class RequestContext
8 | {
9 | private readonly HttpRequest _request;
10 |
11 | internal RequestContext(HttpRequest request)
12 | {
13 | _request = request;
14 | }
15 |
16 | public string Method => _request.Method;
17 |
18 | public Uri Uri => new Uri(_request.GetEncodedUrl());
19 |
20 | public IHeaderDictionary Headers => _request.Headers;
21 |
22 | public string Host => _request.Host.ToString();
23 |
24 | //public string LocalIpAddress => _request.LocalIpAddress;
25 |
26 | //public int? LocalPort => _request.LocalPort;
27 |
28 | //public string RemoteIpAddress => _request.RemoteIpAddress;
29 |
30 | //public int? RemotePort => _request.RemotePort;
31 |
32 | //public ClaimsPrincipal User => _request.User;
33 | }
34 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Luk Vermeulen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/LimitsMiddleware.AspNetCore/LimitsMiddleware.xproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 14.0
5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
6 |
7 |
8 |
9 |
10 | a61b64d6-3d62-472e-b9ba-2435166c6786
11 | LimitsMiddleware
12 | .\obj
13 | .\bin\
14 | v4.5.2
15 |
16 |
17 |
18 | 2.0
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/LimitsMiddleware.AspNetCore/LimitsMiddleware.AspNetCore.xproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 14.0
5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
6 |
7 |
8 |
9 |
10 | a61b64d6-3d62-472e-b9ba-2435166c6786
11 | LimitsMiddleware
12 | .\obj
13 | .\bin\
14 | v4.5.2
15 |
16 |
17 |
18 | 2.0
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/demo/LimitsMiddleware.AspNetCore.Demo/project.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "Microsoft.NETCore.App": {
4 | "version": "1.0.0",
5 | "type": "platform"
6 | },
7 | "Microsoft.AspNetCore.Diagnostics": "1.0.0",
8 | "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
9 | "Microsoft.AspNetCore.Server.Kestrel": "1.0.0",
10 | "Microsoft.Extensions.Logging.Console": "1.0.0",
11 | "Microsoft.Extensions.Configuration.Json": "1.1.0-alpha1-21868",
12 | "LimitsMiddleware.AspNetCore": { "target": "project" }
13 | },
14 |
15 | "tools": {
16 | "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final"
17 | },
18 |
19 | "frameworks": {
20 | "netcoreapp1.0": { }
21 | },
22 |
23 | "buildOptions": {
24 | "define": [ "LIBLOG_PORTABLE" ],
25 | "emitEntryPoint": true,
26 | "preserveCompilationContext": true
27 | },
28 |
29 | "runtimeOptions": {
30 | "configProperties": {
31 | "System.GC.Server": true
32 | }
33 | },
34 |
35 | "publishOptions": {
36 | "include": [
37 | "wwwroot",
38 | "web.config"
39 | ]
40 | },
41 |
42 | "scripts": {
43 | "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/test/LimitsMiddleware.AspNetCore.Tests/LimitsMiddleware.AspNetCore.Tests.xproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 14.0
5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
6 |
7 |
8 |
9 | 1e42d5f7-31eb-4c2b-afd6-841d2024e636
10 | LimitsMiddleware.Tests
11 | .\obj
12 | .\bin\
13 | v4.5.2
14 |
15 |
16 | 2.0
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/demo/LimitsMiddleware.AspNetCore.Demo/LimitsMiddleware.Demo.xproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 14.0
5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
6 |
7 |
8 |
9 |
10 | 962b06a8-4d78-4438-878d-34a0f86f2a5c
11 | LimitsMiddleware.Demo
12 | .\obj
13 | .\bin\
14 | v4.5.2
15 |
16 |
17 |
18 | 2.0
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/test/LimitsMiddleware.AspNetCore.Tests/Helpers/EapTask.cs:
--------------------------------------------------------------------------------
1 | namespace LimitsMiddleware.Tests.Helpers
2 | {
3 | using System;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 |
7 | // http://stackoverflow.com/a/22786826
8 |
9 | public sealed class EapTask
10 | where TEventHandler: class
11 | {
12 | private readonly TaskCompletionSource _completionSource;
13 | private readonly TEventHandler _eventHandler;
14 |
15 | public EapTask(TaskCompletionSource completionSource, TEventHandler eventHandler)
16 | {
17 | _completionSource = completionSource;
18 | _eventHandler = eventHandler;
19 | }
20 |
21 | public Task Start(Action subscribe, Action unsubscribe) => Start(subscribe, unsubscribe, CancellationToken.None);
22 |
23 | public async Task Start(Action subscribe, Action unsubscribe, CancellationToken cancellationToken)
24 | {
25 | subscribe(_eventHandler);
26 | try
27 | {
28 | using (cancellationToken.Register(() => _completionSource.SetCanceled()))
29 | {
30 | return await _completionSource.Task;
31 | }
32 | }
33 | finally
34 | {
35 | unsubscribe(_eventHandler);
36 | }
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/src/LimitsMiddleware.AspNetCore/project.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Limits Middleware for ASP.NET Core",
3 | "version": "1.0.0-alpha",
4 | "authors": [ "Luk Vermeulen" ],
5 | "copyright": "Copyright 2016",
6 | "description": "Middleware that applies limits to an aspnetcore pipeline. Currently supported are max bandwidth, max concurrent requests, connection timeout, max request content length, max querystring length and max url length.",
7 |
8 | "buildOptions": {
9 | "define": [ "LIBLOG_PORTABLE" ],
10 | "optimize": true,
11 | "outputName": "LimitsMiddlewareAspNetCore",
12 | "xmlDoc": false
13 | },
14 |
15 | "packOptions": {
16 | "owners": [ "Luk Vermeulen" ],
17 | "requireLicenseAcceptance": false,
18 | "summary": "Limits Middleware for ASP.NET Core",
19 | "tags": [ "middleware", "throttling", "limits", "aspnetcore" ],
20 | "projectUrl": "https://github.com/lvermeulen/LimitsMiddleware.aspnetcore",
21 | "licenseUrl": "https://github.com/lvermeulen/LimitsMiddleware.aspnetcore/blob/master/LICENSE",
22 | "iconUrl": "http://i.imgur.com/D7PUb42.png?1",
23 | "repository": {
24 | "type": "git",
25 | "url": "https://github.com/lvermeulen/LimitsMiddleware.aspnetcore"
26 | }
27 | },
28 |
29 | "dependencies": {
30 | "Microsoft.AspNetCore.Http.Abstractions": "1.0.0",
31 | "Microsoft.AspNetCore.Http": "1.0.0",
32 | "Microsoft.AspNetCore.Http.Extensions": "1.0.0",
33 | "Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0"
34 | },
35 |
36 | "frameworks": {
37 | "net451": { },
38 | "netstandard1.6": {
39 | "dependencies": {
40 | "NETStandard.Library": "1.6.0",
41 | "Microsoft.CSharp": "4.0.1",
42 | "System.Dynamic.Runtime": "4.0.11",
43 | "System.Globalization.Extensions": "4.0.1",
44 | "System.Security.Claims": "4.0.1"
45 | }
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/demo/LimitsMiddleware.AspNetCore.Demo/Startup.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using LimitsMiddleware.Extensions;
4 | using Microsoft.AspNetCore.Builder;
5 | using Microsoft.AspNetCore.WebUtilities;
6 | using Microsoft.Extensions.Primitives;
7 |
8 | namespace LimitsMiddleware.Demo
9 | {
10 | public class Startup
11 | {
12 | public void Configure(IApplicationBuilder app)
13 | {
14 | app
15 | .MaxUrlLength(100)
16 | .MaxQueryStringLength(80)
17 | .MaxConcurrentRequests(4)
18 |
19 | .MinResponseDelay(context =>
20 | {
21 | var queryParams = QueryHelpers.ParseQuery(context.Uri.Query);
22 | StringValues minResponseDelayParamValues;
23 | string minResponseDelayParam = queryParams.TryGetValue("minresponsedelay", out minResponseDelayParamValues)
24 | ? minResponseDelayParamValues.First()
25 | : null;
26 | int minResponseDelay;
27 | return int.TryParse(minResponseDelayParam, out minResponseDelay)
28 | ? TimeSpan.FromSeconds(minResponseDelay)
29 | : TimeSpan.Zero;
30 | })
31 |
32 | .MaxBandwidthPerRequest(context =>
33 | {
34 | var queryParams = QueryHelpers.ParseQuery(context.Uri.Query);
35 | StringValues maxBandWidthParamValues;
36 | string maxBandwidthParam = queryParams.TryGetValue("maxbandwidthperrequest", out maxBandWidthParamValues)
37 | ? maxBandWidthParamValues.First()
38 | : null;
39 | int maxBandwidth;
40 | return int.TryParse(maxBandwidthParam, out maxBandwidth)
41 | ? maxBandwidth
42 | : -1;
43 | })
44 |
45 | .MaxBandwidthGlobal(10 * 1024 * 1024);
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/test/LimitsMiddleware.AspNetCore.Tests/MinResponseDelayMiddlewareTests.cs:
--------------------------------------------------------------------------------
1 |
2 | namespace LimitsMiddleware.Tests
3 | {
4 | using System;
5 | using System.Diagnostics;
6 | using System.Net;
7 | using System.Net.Http;
8 | using System.Threading.Tasks;
9 | using Extensions;
10 | using Microsoft.AspNetCore.Builder;
11 | using Microsoft.AspNetCore.Hosting;
12 | using Microsoft.AspNetCore.TestHost;
13 | using Shouldly;
14 | using Xunit;
15 |
16 | public class MinResponseDelayMiddlewareTests
17 | {
18 | [Fact]
19 | public async Task When_response_delay_is_applied_then_time_to_receive_data_should_be_longer()
20 | {
21 | var stopwatch = new Stopwatch();
22 |
23 | using (var client = CreateHttpClient(() => TimeSpan.Zero))
24 | {
25 | stopwatch.Start();
26 |
27 | await client.GetAsync("http://example.com");
28 |
29 | stopwatch.Stop();
30 | }
31 |
32 | TimeSpan noLimitTimespan = stopwatch.Elapsed;
33 |
34 | using (var client = CreateHttpClient(() => TimeSpan.FromMilliseconds(10)))
35 | {
36 | stopwatch.Start();
37 |
38 | await client.GetAsync("http://example.com");
39 |
40 | stopwatch.Stop();
41 | }
42 |
43 | var limitTimespan = stopwatch.Elapsed;
44 |
45 | limitTimespan.ShouldBeGreaterThan(noLimitTimespan);
46 | }
47 |
48 | private static HttpClient CreateHttpClient(Func getMinDelay)
49 | {
50 | var builder = new WebHostBuilder().Configure(app => app
51 | .MinResponseDelay(getMinDelay)
52 | .Use((context, _) =>
53 | {
54 | context.Response.StatusCode = (int)HttpStatusCode.OK;
55 |
56 | return Task.FromResult(0);
57 | }));
58 |
59 | var server = new TestServer(builder);
60 | return server.CreateClient();
61 | }
62 | }
63 | }
--------------------------------------------------------------------------------
/src/LimitsMiddleware.AspNetCore/Limits.MaxUrlLength.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net;
3 | using LimitsMiddleware.Logging;
4 | using Microsoft.AspNetCore.Http;
5 | using Microsoft.AspNetCore.Http.Extensions;
6 |
7 | namespace LimitsMiddleware
8 | {
9 | using MidFunc = Func;
10 |
11 | public static partial class Limits
12 | {
13 | public static MidFunc MaxUrlLength(int maxUrlLength, string loggerName = null) => MaxUrlLength(() => maxUrlLength, loggerName);
14 |
15 | public static MidFunc MaxUrlLength(Func getMaxUrlLength, string loggerName = null) => MaxUrlLength(_ => getMaxUrlLength(), loggerName);
16 |
17 | public static MidFunc MaxUrlLength(Func getMaxUrlLength, string loggerName = null)
18 | {
19 | if (getMaxUrlLength == null)
20 | {
21 | throw new ArgumentNullException(nameof(getMaxUrlLength));
22 | }
23 |
24 | loggerName = string.IsNullOrWhiteSpace(loggerName)
25 | ? $"{nameof(LimitsMiddleware)}.{nameof(MaxUrlLength)}"
26 | : loggerName;
27 |
28 | var logger = LogProvider.GetLogger(loggerName);
29 |
30 | return
31 | next =>
32 | async context =>
33 | {
34 | int maxUrlLength = getMaxUrlLength(new RequestContext(context.Request));
35 | string unescapedUri = Uri.UnescapeDataString(new Uri(context.Request.GetEncodedUrl()).AbsoluteUri);
36 |
37 | logger.Debug("Checking request url length.");
38 | if (unescapedUri.Length > maxUrlLength)
39 | {
40 | logger.Info($"Url \"{unescapedUri}\"(Length: {unescapedUri.Length}) exceeds allowed length of {maxUrlLength}. Request rejected.");
41 | context.Response.StatusCode = (int)HttpStatusCode.RequestUriTooLong;
42 | }
43 | logger.Debug("Check passed. Request forwarded.");
44 | await next(context);
45 | };
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/test/LimitsMiddleware.AspNetCore.Tests/MaxUrlLengthTests.cs:
--------------------------------------------------------------------------------
1 | namespace LimitsMiddleware.Tests
2 | {
3 | using System.Net;
4 | using System.Net.Http;
5 | using System.Threading.Tasks;
6 | using Extensions;
7 | using Microsoft.AspNetCore.Builder;
8 | using Microsoft.AspNetCore.Hosting;
9 | using Microsoft.AspNetCore.TestHost;
10 | using Shouldly;
11 | using Xunit;
12 |
13 | public class MaxUrlLengthTests
14 | {
15 | [Fact]
16 | public async Task When_max_urlLength_is_20_and_a_url_with_length_18_should_be_served()
17 | {
18 | var client = CreateClient(20);
19 | var response = await client.GetAsync("http://example.com");
20 |
21 | response.StatusCode.ShouldBe(HttpStatusCode.OK);
22 | }
23 |
24 | [Fact]
25 | public async Task When_max_urlLength_is_20_and_a_url_with_length_39_is_coming_it_should_be_rejected()
26 | {
27 | var client = CreateClient(20);
28 | var response = await client.GetAsync("http://example.com/example/example.html");
29 |
30 | response.StatusCode.ShouldBe(HttpStatusCode.RequestUriTooLong);
31 | }
32 |
33 | [Fact]
34 | public async Task When_max_urlLength_is_30_and_a_url_with_escaped_length_42_but_unescaped_28_it_should_be_served()
35 | {
36 | var client = CreateClient(30);
37 | var response = await client.GetAsync("http://example.com?q=%48%49%50%51%52%53%54");
38 |
39 | response.StatusCode.ShouldBe(HttpStatusCode.OK);
40 | }
41 |
42 | private static HttpClient CreateClient(int length)
43 | {
44 | var builder = new WebHostBuilder().Configure(app => app
45 | .MaxQueryStringLength(length)
46 | .MaxUrlLength(length)
47 | .Use((context, next) =>
48 | {
49 | if (context.Response.IsSuccessStatusCode())
50 | {
51 | context.Response.StatusCode = (int)HttpStatusCode.OK;
52 | }
53 | return Task.FromResult(0);
54 | }));
55 |
56 | var server = new TestServer(builder);
57 | return server.CreateClient();
58 | }
59 | }
60 | }
--------------------------------------------------------------------------------
/src/LimitsMiddleware.AspNetCore/Extensions/ApplicationBuilderExtensions.MaxBandwidthGlobal.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.AspNetCore.Builder;
3 |
4 | namespace LimitsMiddleware.Extensions
5 | {
6 | public static partial class ApplicationBuilderExtensions
7 | {
8 | ///
9 | /// Limits the bandwith used globally by the subsequent stages in the aspnetcore pipeline.
10 | ///
11 | /// The IApplicationBuilder instance.
12 | ///
13 | /// The maximum number of bytes per second to be transferred. Use 0 or a negative
14 | /// number to specify infinite bandwidth.
15 | ///
16 | /// (Optional) The name of the logger log messages are written to.
17 | /// The IApplicationBuilder instance.
18 | public static IApplicationBuilder MaxBandwidthGlobal(this IApplicationBuilder app, int maxBytesPerSecond,
19 | string loggerName = null)
20 | {
21 | if (app == null)
22 | {
23 | throw new ArgumentNullException(nameof(app));
24 | }
25 |
26 | return MaxBandwidthGlobal(app, () => maxBytesPerSecond, loggerName);
27 | }
28 |
29 | ///
30 | /// Limits the bandwith used globally by the subsequent stages in the aspnetcore pipeline.
31 | ///
32 | /// The IApplicationBuilder instance.
33 | ///
34 | /// A delegate to retrieve the maximum number of bytes per second to be transferred.
35 | /// Allows you to supply different values at runtime. Use 0 or a negative number to specify infinite bandwidth.
36 | ///
37 | /// (Optional) The name of the logger log messages are written to.
38 | /// The app instance.
39 | public static IApplicationBuilder MaxBandwidthGlobal(this IApplicationBuilder app, Func getMaxBytesPerSecond, string loggerName = null)
40 | {
41 | if (app == null)
42 | {
43 | throw new ArgumentNullException(nameof(app));
44 | }
45 | if (getMaxBytesPerSecond == null)
46 | {
47 | throw new ArgumentNullException(nameof(getMaxBytesPerSecond));
48 | }
49 |
50 | app.Use(Limits.MaxBandwidthGlobal(getMaxBytesPerSecond, loggerName));
51 | return app;
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/LimitsMiddleware.AspNetCore/InterlockedBoolean.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright 2013 Hans Wolff
3 | //
4 | // Source: https://gist.github.com/hanswolff/7926751
5 | //
6 | // Licensed under the Apache License, Version 2.0 (the "License");
7 | // you may not use this file except in compliance with the License.
8 | // You may obtain a copy of the License at
9 | //
10 | // http://www.apache.org/licenses/LICENSE-2.0
11 | //
12 | // Unless required by applicable law or agreed to in writing, software
13 | // distributed under the License is distributed on an "AS IS" BASIS,
14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | // See the License for the specific language governing permissions and
16 | // limitations under the License.
17 | //
18 |
19 |
20 | namespace LimitsMiddleware
21 | {
22 | using System.Threading;
23 |
24 | ///
25 | /// Interlocked support for boolean values
26 | ///
27 | internal class InterlockedBoolean
28 | {
29 | private int _value;
30 |
31 | ///
32 | /// Current value
33 | ///
34 | public bool Value => _value == 1;
35 |
36 | ///
37 | /// Initializes a new instance of
38 | ///
39 | /// initial value
40 | public InterlockedBoolean(bool initialValue = false)
41 | {
42 | _value = initialValue ? 1 : 0;
43 | }
44 |
45 | ///
46 | /// Sets a new value
47 | ///
48 | /// new value
49 | /// the original value before any operation was performed
50 | public bool Set(bool newValue)
51 | {
52 | int oldValue = Interlocked.Exchange(ref _value, newValue ? 1 : 0);
53 | return oldValue == 1;
54 | }
55 |
56 | ///
57 | /// Compares the current value and the comparand for equality and, if they are equal,
58 | /// replaces the current value with the new value in an atomic/thread-safe operation.
59 | ///
60 | /// new value
61 | /// value to compare the current value with
62 | /// the original value before any operation was performed
63 | public bool CompareExchange(bool newValue, bool comparand)
64 | {
65 | int oldValue = Interlocked.CompareExchange(ref _value, newValue ? 1 : 0, comparand ? 1 : 0);
66 | return oldValue == 1;
67 | }
68 | }
69 | }
--------------------------------------------------------------------------------
/test/LimitsMiddleware.AspNetCore.Tests/MaxQueryStringTests.cs:
--------------------------------------------------------------------------------
1 | namespace LimitsMiddleware.Tests
2 | {
3 | using System.Net;
4 | using System.Net.Http;
5 | using System.Threading.Tasks;
6 | using Extensions;
7 | using Microsoft.AspNetCore.Builder;
8 | using Microsoft.AspNetCore.Hosting;
9 | using Microsoft.AspNetCore.TestHost;
10 | using Shouldly;
11 | using Xunit;
12 |
13 | public class MaxQueryStringTests
14 | {
15 | [Fact]
16 | public async Task When_max_queryString_length_is_10_then_a_request_with_9_should_be_served()
17 | {
18 | HttpClient client = CreateClient(10);
19 |
20 | HttpResponseMessage response = await client.GetAsync("http://example.com?q=1234567");
21 |
22 | response.StatusCode.ShouldBe(HttpStatusCode.OK);
23 | }
24 |
25 | [Fact]
26 | public async Task When_max_queryString_length_is_10_then_a_request_with_11_should_be_rejected()
27 | {
28 | HttpClient client = CreateClient(10);
29 |
30 | HttpResponseMessage response = await client.GetAsync("http://example.com?q=123456789");
31 |
32 | response.StatusCode.ShouldBe(HttpStatusCode.RequestUriTooLong);
33 | }
34 |
35 | [Fact]
36 | public async Task When_max_queryString_length_is_10_then_a_request_with_escaped_length_greater_than_10_but_unescaped_lower_or_equal_than_10_should_be_served()
37 | {
38 | HttpClient client = CreateClient(10);
39 |
40 | HttpResponseMessage response = await client.GetAsync("http://example.com?q=%48%49%50%51%52%53%54");
41 |
42 | response.StatusCode.ShouldBe(HttpStatusCode.OK);
43 | }
44 |
45 | [Fact]
46 | public async Task When_queryString_exceeds_max_length_then_request_should_be_rejected()
47 | {
48 | HttpClient client = CreateClient(5);
49 |
50 | HttpResponseMessage response = await client.GetAsync("http://example.com?q=123456");
51 |
52 | response.StatusCode.ShouldBe(HttpStatusCode.RequestUriTooLong);
53 | }
54 |
55 | private static HttpClient CreateClient(int length)
56 | {
57 | var builder = new WebHostBuilder().Configure(app => app
58 | .MaxQueryStringLength(length)
59 | .Use((context, next) =>
60 | {
61 | if (context.Response.IsSuccessStatusCode())
62 | {
63 | context.Response.StatusCode = (int)HttpStatusCode.OK;
64 | }
65 | return Task.FromResult(0);
66 | }));
67 |
68 | var server = new TestServer(builder);
69 | return server.CreateClient();
70 | }
71 | }
72 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 | # Limits Middleware for ASP.NET Core [](https://ci.appveyor.com/project/lvermeulen/limitsmiddleware-aspnetcore) [](https://github.com/lvermeulen/LimitsMiddleware.aspnetcore/blob/master/LICENSE) [](https://www.nuget.org/packages/LimitsMiddleware.aspnetcore/) [](https://coveralls.io/github/lvermeulen/LimitsMiddleware.aspnetcore?branch=master) [](https://gitter.im/lvermeulen/LimitsMiddleware.aspnetcore?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)  
3 | Middleware to apply limits to an ASP.NET Core pipeline. This code was ported from [Damian Hickey's Limits Middleware for Owin](https://github.com/damianh/LimitsMiddleware).
4 |
5 | ##Features:
6 |
7 | - Max bandwidth
8 | - Max concurrent requests
9 | - Connection timeout
10 | - Max query string
11 | - Max request content length
12 | - Max url length
13 | - Min response delay
14 |
15 |
16 | ##Usage:
17 |
18 | Configuration values can be supplied as constants or with a delegate for runtime values.
19 |
20 | ```csharp
21 | public class Startup
22 | {
23 | public void Configure(IApplicationBuilder app)
24 | {
25 | // static settings
26 | app
27 | .MaxBandwidth(10000) //bps
28 | .MaxConcurrentRequests(10)
29 | .ConnectionTimeout(TimeSpan.FromSeconds(10))
30 | .MaxQueryStringLength(15) //Unescaped QueryString
31 | .MaxRequestContentLength(15)
32 | .MaxUrlLength(20)
33 | .MinResponseDelay(200ms)
34 | .Use(..);
35 |
36 | // dynamic settings
37 | app
38 | .MaxBandwidth(() => 10000) //bps
39 | .MaxConcurrentRequests(() => 10)
40 | .ConnectionTimeout(() => TimeSpan.FromSeconds(10))
41 | .MaxQueryStringLength(() => 15)
42 | .MaxRequestContentLength(() => 15)
43 | .MaxUrlLength(() => 20)
44 | .Use(..);
45 | }
46 | }
47 | }
48 | ```
49 |
50 | ##Thanks
51 | * [Funnel](https://thenounproject.com/term/funnel/515072/) icon by [Arthur Shlain](https://thenounproject.com/ArtZ91/) from [The Noun Project](https://thenounproject.com)
52 |
--------------------------------------------------------------------------------
/test/LimitsMiddleware.AspNetCore.Tests/MaxBandwidthPerRequestTests.cs:
--------------------------------------------------------------------------------
1 | namespace LimitsMiddleware.Tests
2 | {
3 | using System;
4 | using System.Diagnostics;
5 | using System.IO;
6 | using System.Net;
7 | using System.Net.Http;
8 | using System.Reflection;
9 | using System.Threading.Tasks;
10 | using Extensions;
11 | using Microsoft.AspNetCore.Builder;
12 | using Microsoft.AspNetCore.Hosting;
13 | using Microsoft.AspNetCore.Http;
14 | using Microsoft.AspNetCore.TestHost;
15 | using Microsoft.Extensions.FileProviders;
16 | using Shouldly;
17 | using Xunit;
18 |
19 | public class MaxBandwidthPerRequestTests
20 | {
21 | [Theory]
22 | [InlineData("file_64KB.txt", 8000, 15)]
23 | [InlineData("file_64KB.txt", 16000, 5)]
24 | [InlineData("file_512KB.txt", 100000, 5)]
25 | [InlineData("file_512KB.txt", 200000, 2)]
26 | public async Task When_bandwidth_is_applied_then_time_to_receive_data_should_be_longer(string file, int maxKiloBytesPerSeconds, int approximateSeconds)
27 | {
28 | var stopwatch = new Stopwatch();
29 | using (var httpClient = CreateHttpClient())
30 | {
31 | stopwatch.Start();
32 |
33 | var response = await httpClient.GetAsync(file);
34 | response.StatusCode.ShouldBe(HttpStatusCode.OK);
35 |
36 | stopwatch.Stop();
37 | }
38 | TimeSpan nolimitTimeSpan = stopwatch.Elapsed;
39 |
40 | using (var httpClient = CreateHttpClient(maxKiloBytesPerSeconds))
41 | {
42 | stopwatch.Restart();
43 |
44 | var response = await httpClient.GetAsync(file);
45 | response.StatusCode.ShouldBe(HttpStatusCode.OK);
46 |
47 | stopwatch.Stop();
48 | }
49 | TimeSpan limitedTimeSpan = stopwatch.Elapsed;
50 |
51 | Debug.WriteLine("No limits: {0}", nolimitTimeSpan);
52 | Debug.WriteLine("Limited : {0}", limitedTimeSpan);
53 |
54 | limitedTimeSpan.ShouldBeGreaterThan(nolimitTimeSpan);
55 |
56 | double abs = Math.Abs((limitedTimeSpan.TotalSeconds - nolimitTimeSpan.TotalSeconds) - approximateSeconds);
57 | (abs < 1).ShouldBeTrue($"value {abs} >= 1");
58 | }
59 |
60 | private static HttpClient CreateHttpClient(int maxKiloBytesPerSecond = -1)
61 | {
62 | string currentDirectory = Path.GetDirectoryName(typeof(MaxBandwidthPerRequestTests).GetTypeInfo().Assembly.Location);
63 | var builder = new WebHostBuilder().Configure(app => app
64 | .MaxBandwidthPerRequest(maxKiloBytesPerSecond)
65 | .UseStaticFiles(new StaticFileOptions()
66 | {
67 | FileProvider = new PhysicalFileProvider(Path.Combine(currentDirectory, "Files")),
68 | RequestPath = new PathString("")
69 | }));
70 |
71 | var server = new TestServer(builder);
72 | return server.CreateClient();
73 | }
74 | }
75 | }
--------------------------------------------------------------------------------
/src/LimitsMiddleware.AspNetCore/Extensions/ApplicationBuilderExtensions.MaxUrlLength.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.AspNetCore.Builder;
3 |
4 | namespace LimitsMiddleware.Extensions
5 | {
6 | public static partial class ApplicationBuilderExtensions
7 | {
8 | ///
9 | /// Limits the length of the URL.
10 | ///
11 | /// The IApplicationBuilder instance.
12 | /// Maximum length of the URL.
13 | /// (Optional) The name of the logger log messages are written to.
14 | /// The IApplicationBuilder instance.
15 | public static IApplicationBuilder MaxUrlLength(this IApplicationBuilder app, int maxUrlLength, string loggerName = null)
16 | {
17 | if (app == null)
18 | {
19 | throw new ArgumentNullException(nameof(app));
20 | }
21 |
22 | return MaxUrlLength(app, () => maxUrlLength, loggerName);
23 | }
24 |
25 | ///
26 | /// Limits the length of the URL.
27 | ///
28 | /// The IApplicationBuilder instance.
29 | /// A delegate to get the maximum URL length.
30 | /// (Optional) The name of the logger log messages are written to.
31 | /// The IApplicationBuilder instance.
32 | public static IApplicationBuilder MaxUrlLength(this IApplicationBuilder app, Func getMaxUrlLength, string loggerName = null)
33 | {
34 | if (app == null)
35 | {
36 | throw new ArgumentNullException(nameof(app));
37 | }
38 | if (getMaxUrlLength == null)
39 | {
40 | throw new ArgumentNullException(nameof(getMaxUrlLength));
41 | }
42 |
43 | app.Use(Limits.MaxUrlLength(getMaxUrlLength, loggerName));
44 | return app;
45 | }
46 |
47 | ///
48 | /// Limits the length of the URL.
49 | ///
50 | /// The IApplicationBuilder instance.
51 | /// A delegate to get the maximum URL length.
52 | /// (Optional) The name of the logger log messages are written to.
53 | /// The IApplicationBuilder instance.
54 | public static IApplicationBuilder MaxUrlLength(this IApplicationBuilder app, Func getMaxUrlLength, string loggerName = null)
55 | {
56 | if (app == null)
57 | {
58 | throw new ArgumentNullException(nameof(app));
59 | }
60 | if (getMaxUrlLength == null)
61 | {
62 | throw new ArgumentNullException(nameof(getMaxUrlLength));
63 | }
64 |
65 | app.Use(Limits.MaxUrlLength(getMaxUrlLength, loggerName));
66 | return app;
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/LimitsMiddleware.AspNetCore/RateLimiters/FixedTokenBucket.cs:
--------------------------------------------------------------------------------
1 | using LimitsMiddleware.Logging.LogProviders;
2 |
3 | namespace LimitsMiddleware.RateLimiters
4 | {
5 | using System;
6 | using System.Threading;
7 |
8 | internal class FixedTokenBucket
9 | {
10 | private readonly Func _getBucketTokenCapacty;
11 | private readonly long _refillIntervalTicks;
12 | private readonly TimeSpan _refillInterval;
13 | private readonly GetUtcNow _getUtcNow;
14 | private long _nextRefillTime;
15 | private long _tokens;
16 | private readonly InterlockedBoolean _updatingTokens = new InterlockedBoolean();
17 | private int _concurrentRequestCount;
18 |
19 | public FixedTokenBucket(Func getBucketTokenCapacty, GetUtcNow getUtcNow = null)
20 | {
21 | _getBucketTokenCapacty = getBucketTokenCapacty;
22 | _refillInterval = TimeSpan.FromSeconds(1);
23 | _refillIntervalTicks = TimeSpan.FromSeconds(1).Ticks;
24 | _getUtcNow = getUtcNow ?? SystemClock.GetUtcNow;
25 | }
26 |
27 | public bool ShouldThrottle(int tokenCount)
28 | {
29 | TimeSpan _;
30 | return ShouldThrottle(tokenCount, out _);
31 | }
32 |
33 | public bool ShouldThrottle(int tokenCount, out TimeSpan waitTimeSpan)
34 | {
35 | waitTimeSpan = TimeSpan.Zero;
36 | UpdateTokens();
37 | long tokens = Interlocked.Read(ref _tokens);
38 | if (tokens < tokenCount)
39 | {
40 | long currentTime = _getUtcNow().Ticks;
41 | long waitTicks = _nextRefillTime - currentTime;
42 | if (waitTicks <= 0)
43 | {
44 | return false;
45 | }
46 | waitTimeSpan = TimeSpan.FromTicks(waitTicks * _concurrentRequestCount);
47 | return true;
48 | }
49 | Interlocked.Add(ref _tokens, -tokenCount);
50 | return false;
51 | }
52 |
53 | public long CurrentTokenCount
54 | {
55 | get
56 | {
57 | UpdateTokens();
58 | return Interlocked.Read(ref _tokens);
59 | }
60 | }
61 |
62 | public int Capacity => _getBucketTokenCapacty();
63 |
64 | public double Rate => Capacity/_refillInterval.TotalSeconds;
65 |
66 | public IDisposable RegisterRequest()
67 | {
68 | Interlocked.Increment(ref _concurrentRequestCount);
69 | return new DisposableAction(() =>
70 | {
71 | Interlocked.Decrement(ref _concurrentRequestCount);
72 | });
73 | }
74 |
75 | private void UpdateTokens()
76 | {
77 | if (_updatingTokens.EnsureCalledOnce())
78 | {
79 | return;
80 | }
81 | long currentTime = _getUtcNow().Ticks;
82 |
83 | if (currentTime >= _nextRefillTime)
84 | {
85 | Interlocked.Exchange(ref _tokens, _getBucketTokenCapacty());
86 | _nextRefillTime = currentTime + _refillIntervalTicks;
87 | }
88 |
89 | _updatingTokens.Set(false);
90 | }
91 | }
92 | }
--------------------------------------------------------------------------------
/src/LimitsMiddleware.AspNetCore/Extensions/ApplicationBuilderExtensions.MaxRequestContentLength.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.AspNetCore.Builder;
3 |
4 | namespace LimitsMiddleware.Extensions
5 | {
6 | public static partial class ApplicationBuilderExtensions
7 | {
8 | ///
9 | /// Limits the length of the request content.
10 | ///
11 | /// The IApplicationBuilder instance.
12 | /// Maximum length of the content.
13 | /// (Optional) The name of the logger log messages are written to.
14 | /// The IApplicationBuilder instance.
15 | public static IApplicationBuilder MaxRequestContentLength(this IApplicationBuilder app, int maxContentLength, string loggerName = null)
16 | {
17 | if (app == null)
18 | {
19 | throw new ArgumentNullException(nameof(app));
20 | }
21 |
22 | return MaxRequestContentLength(app, () => maxContentLength, loggerName);
23 | }
24 |
25 | ///
26 | /// Limits the length of the request content.
27 | ///
28 | /// The IApplicationBuilder instance.
29 | /// A delegate to get the maximum content length.
30 | /// (Optional) The name of the logger log messages are written to.
31 | /// The IApplicationBuilder instance.
32 | public static IApplicationBuilder MaxRequestContentLength(this IApplicationBuilder app, Func getMaxContentLength, string loggerName = null)
33 | {
34 | if (app == null)
35 | {
36 | throw new ArgumentNullException(nameof(app));
37 | }
38 | if (getMaxContentLength == null)
39 | {
40 | throw new ArgumentNullException(nameof(getMaxContentLength));
41 | }
42 |
43 | app.Use(Limits.MaxRequestContentLength(getMaxContentLength, loggerName));
44 |
45 | return app;
46 | }
47 |
48 | ///
49 | /// Limits the length of the request content.
50 | ///
51 | /// The IApplicationBuilder instance.
52 | /// A delegate to get the maximum content length.
53 | /// (Optional) The name of the logger log messages are written to.
54 | /// The IApplicationBuilder instance.
55 | public static IApplicationBuilder MaxRequestContentLength(this IApplicationBuilder app, Func getMaxContentLength, string loggerName = null)
56 | {
57 | if (app == null)
58 | {
59 | throw new ArgumentNullException(nameof(app));
60 | }
61 | if (getMaxContentLength == null)
62 | {
63 | throw new ArgumentNullException(nameof(getMaxContentLength));
64 | }
65 |
66 | app.Use(Limits.MaxRequestContentLength(getMaxContentLength, loggerName));
67 |
68 | return app;
69 | }
70 | }
71 | }
--------------------------------------------------------------------------------
/src/LimitsMiddleware.AspNetCore/Extensions/ApplicationBuilderExtensions.MaxQueryStringLength.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.AspNetCore.Builder;
3 |
4 | namespace LimitsMiddleware.Extensions
5 | {
6 | public static partial class ApplicationBuilderExtensions
7 | {
8 | ///
9 | /// Limits the length of the query string.
10 | ///
11 | /// The IApplicationBuilder instance.
12 | /// Maximum length of the query string.
13 | /// (Optional) The name of the logger log messages are written to.
14 | /// The IApplicationBuilder instance.
15 | public static IApplicationBuilder MaxQueryStringLength(this IApplicationBuilder app, int maxQueryStringLength,
16 | string loggerName = null)
17 | {
18 | if (app == null)
19 | {
20 | throw new ArgumentNullException(nameof(app));
21 | }
22 |
23 | return MaxQueryStringLength(app, () => maxQueryStringLength, loggerName);
24 | }
25 |
26 | ///
27 | /// Limits the length of the query string.
28 | ///
29 | /// The IApplicationBuilder instance.
30 | /// A delegate to get the maximum query string length.
31 | /// (Optional) The name of the logger log messages are written to.
32 | /// The IApplicationBuilder instance.
33 | public static IApplicationBuilder MaxQueryStringLength(this IApplicationBuilder app, Func getMaxQueryStringLength,
34 | string loggerName = null)
35 | {
36 | if (app == null)
37 | {
38 | throw new ArgumentNullException(nameof(app));
39 | }
40 | if (getMaxQueryStringLength == null)
41 | {
42 | throw new ArgumentNullException(nameof(getMaxQueryStringLength));
43 | }
44 |
45 | app.Use(Limits.MaxQueryStringLength(getMaxQueryStringLength, loggerName));
46 |
47 | return app;
48 | }
49 |
50 |
51 | ///
52 | /// Limits the length of the query string.
53 | ///
54 | /// The IApplicationBuilder instance.
55 | /// A delegate to get the maximum query string length.
56 | /// (Optional) The name of the logger log messages are written to.
57 | /// The IApplicationBuilder instance.
58 | public static IApplicationBuilder MaxQueryStringLength(this IApplicationBuilder app,
59 | Func getMaxQueryStringLength, string loggerName = null)
60 | {
61 | if (app == null)
62 | {
63 | throw new ArgumentNullException(nameof(app));
64 | }
65 | if (getMaxQueryStringLength == null)
66 | {
67 | throw new ArgumentNullException(nameof(getMaxQueryStringLength));
68 | }
69 |
70 | app.Use(Limits.MaxQueryStringLength(getMaxQueryStringLength, loggerName));
71 |
72 | return app;
73 | }
74 | }
75 | }
--------------------------------------------------------------------------------
/LimitsMiddleware.sln:
--------------------------------------------------------------------------------
1 | Microsoft Visual Studio Solution File, Format Version 12.00
2 | # Visual Studio 14
3 | VisualStudioVersion = 14.0.25420.1
4 | MinimumVisualStudioVersion = 10.0.40219.1
5 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{2E129E87-1111-4E18-A73E-E64A2DA359BE}"
6 | ProjectSection(SolutionItems) = preProject
7 | ..\.gitattributes = ..\.gitattributes
8 | ..\.gitignore = ..\.gitignore
9 | appveyor.yml = appveyor.yml
10 | global.json = global.json
11 | LICENSE = LICENSE
12 | README.md = README.md
13 | EndProjectSection
14 | EndProject
15 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Test", "Test", "{248F98EE-CEF1-4190-8020-DFA2174ED82E}"
16 | EndProject
17 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Demo", "Demo", "{9F696FF8-7F23-4CB3-A875-1256F09EDBD5}"
18 | EndProject
19 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{50AB4FEC-B1AC-45F8-B7EE-E636F3619AAD}"
20 | ProjectSection(SolutionItems) = preProject
21 | build\build.ps1 = build\build.ps1
22 | build\cover.ps1 = build\cover.ps1
23 | build\test.ps1 = build\test.ps1
24 | EndProjectSection
25 | EndProject
26 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "LimitsMiddleware.AspNetCore", "src\LimitsMiddleware.AspNetCore\LimitsMiddleware.AspNetCore.xproj", "{A61B64D6-3D62-472E-B9BA-2435166C6786}"
27 | EndProject
28 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "LimitsMiddleware.AspNetCore.Tests", "test\LimitsMiddleware.AspNetCore.Tests\LimitsMiddleware.AspNetCore.Tests.xproj", "{1E42D5F7-31EB-4C2B-AFD6-841D2024E636}"
29 | EndProject
30 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "LimitsMiddleware.Demo", "demo\LimitsMiddleware.AspNetCore.Demo\LimitsMiddleware.Demo.xproj", "{962B06A8-4D78-4438-878D-34A0F86F2A5C}"
31 | EndProject
32 | Global
33 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
34 | Debug|Any CPU = Debug|Any CPU
35 | Release|Any CPU = Release|Any CPU
36 | EndGlobalSection
37 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
38 | {A61B64D6-3D62-472E-B9BA-2435166C6786}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
39 | {A61B64D6-3D62-472E-B9BA-2435166C6786}.Debug|Any CPU.Build.0 = Debug|Any CPU
40 | {A61B64D6-3D62-472E-B9BA-2435166C6786}.Release|Any CPU.ActiveCfg = Release|Any CPU
41 | {A61B64D6-3D62-472E-B9BA-2435166C6786}.Release|Any CPU.Build.0 = Release|Any CPU
42 | {1E42D5F7-31EB-4C2B-AFD6-841D2024E636}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
43 | {1E42D5F7-31EB-4C2B-AFD6-841D2024E636}.Debug|Any CPU.Build.0 = Debug|Any CPU
44 | {1E42D5F7-31EB-4C2B-AFD6-841D2024E636}.Release|Any CPU.ActiveCfg = Release|Any CPU
45 | {1E42D5F7-31EB-4C2B-AFD6-841D2024E636}.Release|Any CPU.Build.0 = Release|Any CPU
46 | {962B06A8-4D78-4438-878D-34A0F86F2A5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
47 | {962B06A8-4D78-4438-878D-34A0F86F2A5C}.Debug|Any CPU.Build.0 = Debug|Any CPU
48 | {962B06A8-4D78-4438-878D-34A0F86F2A5C}.Release|Any CPU.ActiveCfg = Release|Any CPU
49 | {962B06A8-4D78-4438-878D-34A0F86F2A5C}.Release|Any CPU.Build.0 = Release|Any CPU
50 | EndGlobalSection
51 | GlobalSection(SolutionProperties) = preSolution
52 | HideSolutionNode = FALSE
53 | EndGlobalSection
54 | GlobalSection(NestedProjects) = preSolution
55 | {1E42D5F7-31EB-4C2B-AFD6-841D2024E636} = {248F98EE-CEF1-4190-8020-DFA2174ED82E}
56 | {962B06A8-4D78-4438-878D-34A0F86F2A5C} = {9F696FF8-7F23-4CB3-A875-1256F09EDBD5}
57 | EndGlobalSection
58 | EndGlobal
59 |
--------------------------------------------------------------------------------
/src/LimitsMiddleware.AspNetCore/Limits.MaxBandwidthGlobal.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using LimitsMiddleware.Logging;
4 | using LimitsMiddleware.RateLimiters;
5 | using Microsoft.AspNetCore.Http;
6 |
7 | namespace LimitsMiddleware
8 | {
9 | using MidFunc = Func;
10 |
11 | public static partial class Limits
12 | {
13 | ///
14 | /// Limits the bandwith used by the subsequent stages in the aspnetcore pipeline.
15 | ///
16 | ///
17 | /// The maximum number of bytes per second to be transferred. Use 0 or a negative
18 | /// number to specify infinite bandwidth.
19 | ///
20 | /// (Optional) The name of the logger log messages are written to.
21 | /// A middleware delegate.
22 | public static MidFunc MaxBandwidthGlobal(int maxBytesPerSecond, string loggerName) => MaxBandwidthGlobal(() => maxBytesPerSecond, loggerName);
23 |
24 | ///
25 | /// Limits the bandwith used by the subsequent stages in the aspnetcore pipeline.
26 | ///
27 | ///
28 | /// A delegate to retrieve the maximum number of bytes per second to be transferred.
29 | /// Allows you to supply different values at runtime. Use 0 or a negative number to specify infinite bandwidth.
30 | ///
31 | /// (Optional) The name of the logger log messages are written to.
32 | /// A middleware delegate.
33 | /// getMaxBytesToWrite
34 | public static MidFunc MaxBandwidthGlobal(Func getBytesPerSecond, string loggerName = null)
35 | {
36 | if (getBytesPerSecond == null)
37 | {
38 | throw new ArgumentNullException(nameof(getBytesPerSecond));
39 | }
40 |
41 | loggerName = string.IsNullOrWhiteSpace(loggerName)
42 | ? $"{nameof(LimitsMiddleware)}.{nameof(MaxBandwidthGlobal)}"
43 | : loggerName;
44 | var logger = LogProvider.GetLogger(loggerName);
45 |
46 | var requestTokenBucket = new FixedTokenBucket(getBytesPerSecond);
47 | var responseTokenBucket = new FixedTokenBucket(getBytesPerSecond);
48 | logger.Debug("Configure streams to be globally limited to.");
49 |
50 | return
51 | next =>
52 | async context =>
53 | {
54 | // ReSharper disable once ArrangeBraces_using
55 | using (requestTokenBucket.RegisterRequest())
56 | using (responseTokenBucket.RegisterRequest())
57 | {
58 | var requestBodyStream = context.Request.Body ?? Stream.Null;
59 | var responseBodyStream = context.Response.Body;
60 |
61 | context.Request.Body = new ThrottledStream(requestBodyStream, requestTokenBucket);
62 | context.Response.Body = new ThrottledStream(responseBodyStream, responseTokenBucket);
63 |
64 | //TODO consider SendFile interception
65 | await next(context).ConfigureAwait(false);
66 | }
67 | };
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/LimitsMiddleware.AspNetCore/Extensions/ApplicationBuilderExtensions.MaxBandwidthPerRequest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.AspNetCore.Builder;
3 |
4 | namespace LimitsMiddleware.Extensions
5 | {
6 | public static partial class ApplicationBuilderExtensions
7 | {
8 | ///
9 | /// Limits the bandwith used by the subsequent stages in the aspnetcore pipeline.
10 | ///
11 | /// The IApplicationBuilder instance.
12 | ///
13 | /// The maximum number of bytes per second to be transferred. Use 0 or a negative
14 | /// number to specify infinite bandwidth.
15 | ///
16 | /// The IApplicationBuilder instance.
17 | public static IApplicationBuilder MaxBandwidthPerRequest(this IApplicationBuilder app, int maxBytesPerSecond)
18 | {
19 | if (app == null)
20 | {
21 | throw new ArgumentNullException(nameof(app));
22 | }
23 |
24 | return MaxBandwidthPerRequest(app, () => maxBytesPerSecond);
25 | }
26 |
27 | ///
28 | /// Limits the bandwith used by the subsequent stages in the aspnetcore pipeline.
29 | ///
30 | /// The IApplicationBuilder instance.
31 | ///
32 | /// A delegate to retrieve the maximum number of bytes per second to be transferred.
33 | /// Allows you to supply different values at runtime. Use 0 or a negative number to specify infinite bandwidth.
34 | ///
35 | /// The app instance.
36 | public static IApplicationBuilder MaxBandwidthPerRequest(this IApplicationBuilder app, Func getMaxBytesPerSecond)
37 | {
38 | if (app == null)
39 | {
40 | throw new ArgumentNullException(nameof(app));
41 | }
42 | if (getMaxBytesPerSecond == null)
43 | {
44 | throw new ArgumentNullException(nameof(getMaxBytesPerSecond));
45 | }
46 |
47 | app.Use(Limits.MaxBandwidthPerRequest(getMaxBytesPerSecond));
48 | return app;
49 | }
50 |
51 | ///
52 | /// Limits the bandwith used by the subsequent stages in the aspnetcore pipeline.
53 | ///
54 | ///
55 | /// A delegate to retrieve the maximum number of bytes per second to be transferred.
56 | /// Allows you to supply different values at runtime. Use 0 or a negative number to specify infinite bandwidth.
57 | ///
58 | /// A middleware delegate.
59 | /// The IApplicationBuilder instance.
60 | /// app
61 | /// getMaxBytesPerSecond
62 | public static IApplicationBuilder MaxBandwidthPerRequest(this IApplicationBuilder app, Func getMaxBytesPerSecond)
63 | {
64 | if (app == null)
65 | {
66 | throw new ArgumentNullException(nameof(app));
67 | }
68 | if (getMaxBytesPerSecond == null)
69 | {
70 | throw new ArgumentNullException(nameof(getMaxBytesPerSecond));
71 | }
72 |
73 | app.Use(Limits.MaxBandwidthPerRequest(getMaxBytesPerSecond));
74 | return app;
75 | }
76 | }
77 | }
--------------------------------------------------------------------------------
/src/LimitsMiddleware.AspNetCore/TimeoutStream.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using LimitsMiddleware.Logging;
6 |
7 | namespace LimitsMiddleware
8 | {
9 | internal class TimeoutStream : Stream
10 | {
11 | private readonly Stream _innerStream;
12 | private readonly TimeSpan _timeout;
13 | private readonly ILog _logger;
14 | private readonly Action