├── .gitattributes
├── .gitignore
├── LICENSE
├── LimitsMiddleware.sln
├── README.md
├── appveyor.yml
├── build
├── build.ps1
├── cover.ps1
└── test.ps1
├── demo
└── LimitsMiddleware.AspNetCore.Demo
│ ├── App_Packages
│ └── LibLog.4.2
│ │ └── LibLog.cs
│ ├── LimitsMiddleware.Demo.xproj
│ ├── Program.cs
│ ├── Properties
│ └── launchSettings.json
│ ├── Startup.cs
│ ├── project.json
│ └── web.config
├── global.json
├── src
└── LimitsMiddleware.AspNetCore
│ ├── App_Packages
│ └── LibLog.4.2
│ │ └── LibLog.cs
│ ├── ContentLengthExceededException.cs
│ ├── ContentLengthLimitingStream.cs
│ ├── ContentLengthRequiredException.cs
│ ├── Extensions
│ ├── ApplicationBuilderExtensions.ConnectionTimeout.cs
│ ├── ApplicationBuilderExtensions.MaxBandwidthGlobal.cs
│ ├── ApplicationBuilderExtensions.MaxBandwidthPerRequest.cs
│ ├── ApplicationBuilderExtensions.MaxConcurrentRequests.cs
│ ├── ApplicationBuilderExtensions.MaxQueryStringLength.cs
│ ├── ApplicationBuilderExtensions.MaxRequestContentLength.cs
│ ├── ApplicationBuilderExtensions.MaxUrlLength.cs
│ └── ApplicationBuilderExtensions.MinResponseDelay.cs
│ ├── InterlockedBoolean.cs
│ ├── InterlockedBooleanExtensions.cs
│ ├── Limits.ConnectionTimeout.cs
│ ├── Limits.MaxBandwidthGlobal.cs
│ ├── Limits.MaxBandwidthPerRequest.cs
│ ├── Limits.MaxConcurrentRequests.cs
│ ├── Limits.MaxQueryStringLength.cs
│ ├── Limits.MaxRequestContentLength.cs
│ ├── Limits.MaxUrlLength.cs
│ ├── Limits.MinResponseDelay.cs
│ ├── LimitsMiddleware.AspNetCore.xproj
│ ├── LimitsMiddleware.xproj
│ ├── Properties
│ └── AssemblyInfo.cs
│ ├── RateLimiters
│ ├── FixedTokenBucket.cs
│ ├── GetUtcNow.cs
│ └── SystemClock.cs
│ ├── RequestContext.cs
│ ├── ThrottledStream.cs
│ ├── TimeoutStream.cs
│ └── project.json
└── test
└── LimitsMiddleware.AspNetCore.Tests
├── ConnectionTimeoutTests.cs
├── Files
├── file_512KB.txt
└── file_64KB.txt
├── Helpers
├── EapTask.cs
└── TaskExt.cs
├── HttpResponseExtensions.cs
├── LimitsMiddleware.AspNetCore.Tests.xproj
├── MaxBandwidthGlobalTests.cs
├── MaxBandwidthPerRequestTests.cs
├── MaxConcurrentRequestsTests.cs
├── MaxQueryStringTests.cs
├── MaxRequestContentLengthTests.cs
├── MaxUrlLengthTests.cs
├── MinResponseDelayMiddlewareTests.cs
├── Properties
└── AssemblyInfo.cs
├── RateLimiters
└── FixedTokenBucketTests.cs
├── project.json
└── xunit.runner.json
/.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 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | bld/
21 | [Bb]in/
22 | [Oo]bj/
23 | [Ll]og/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 | # Uncomment if you have tasks that create the project's static files in wwwroot
28 | #wwwroot/
29 |
30 | # MSTest test Results
31 | [Tt]est[Rr]esult*/
32 | [Bb]uild[Ll]og.*
33 |
34 | # NUNIT
35 | *.VisualState.xml
36 | TestResult.xml
37 |
38 | # Build Results of an ATL Project
39 | [Dd]ebugPS/
40 | [Rr]eleasePS/
41 | dlldata.c
42 |
43 | # DNX
44 | project.lock.json
45 | artifacts/
46 |
47 | *_i.c
48 | *_p.c
49 | *_i.h
50 | *.ilk
51 | *.meta
52 | *.obj
53 | *.pch
54 | *.pdb
55 | *.pgc
56 | *.pgd
57 | *.rsp
58 | *.sbr
59 | *.tlb
60 | *.tli
61 | *.tlh
62 | *.tmp
63 | *.tmp_proj
64 | *.log
65 | *.vspscc
66 | *.vssscc
67 | .builds
68 | *.pidb
69 | *.svclog
70 | *.scc
71 |
72 | # Chutzpah Test files
73 | _Chutzpah*
74 |
75 | # Visual C++ cache files
76 | ipch/
77 | *.aps
78 | *.ncb
79 | *.opendb
80 | *.opensdf
81 | *.sdf
82 | *.cachefile
83 | *.VC.db
84 | *.VC.VC.opendb
85 |
86 | # Visual Studio profiler
87 | *.psess
88 | *.vsp
89 | *.vspx
90 | *.sap
91 |
92 | # TFS 2012 Local Workspace
93 | $tf/
94 |
95 | # Guidance Automation Toolkit
96 | *.gpState
97 |
98 | # ReSharper is a .NET coding add-in
99 | _ReSharper*/
100 | *.[Rr]e[Ss]harper
101 | *.DotSettings.user
102 |
103 | # JustCode is a .NET coding add-in
104 | .JustCode
105 |
106 | # TeamCity is a build add-in
107 | _TeamCity*
108 |
109 | # DotCover is a Code Coverage Tool
110 | *.dotCover
111 |
112 | # NCrunch
113 | _NCrunch_*
114 | .*crunch*.local.xml
115 | nCrunchTemp_*
116 |
117 | # MightyMoose
118 | *.mm.*
119 | AutoTest.Net/
120 |
121 | # Web workbench (sass)
122 | .sass-cache/
123 |
124 | # Installshield output folder
125 | [Ee]xpress/
126 |
127 | # DocProject is a documentation generator add-in
128 | DocProject/buildhelp/
129 | DocProject/Help/*.HxT
130 | DocProject/Help/*.HxC
131 | DocProject/Help/*.hhc
132 | DocProject/Help/*.hhk
133 | DocProject/Help/*.hhp
134 | DocProject/Help/Html2
135 | DocProject/Help/html
136 |
137 | # Click-Once directory
138 | publish/
139 |
140 | # Publish Web Output
141 | *.[Pp]ublish.xml
142 | *.azurePubxml
143 | # TODO: Comment the next line if you want to checkin your web deploy settings
144 | # but database connection strings (with potential passwords) will be unencrypted
145 | *.pubxml
146 | *.publishproj
147 |
148 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
149 | # checkin your Azure Web App publish settings, but sensitive information contained
150 | # in these scripts will be unencrypted
151 | PublishScripts/
152 |
153 | # NuGet Packages
154 | *.nupkg
155 | # The packages folder can be ignored because of Package Restore
156 | **/packages/*
157 | # except build/, which is used as an MSBuild target.
158 | !**/packages/build/
159 | # Uncomment if necessary however generally it will be regenerated when needed
160 | #!**/packages/repositories.config
161 | # NuGet v3's project.json files produces more ignoreable files
162 | *.nuget.props
163 | *.nuget.targets
164 |
165 | # Microsoft Azure Build Output
166 | csx/
167 | *.build.csdef
168 |
169 | # Microsoft Azure Emulator
170 | ecf/
171 | rcf/
172 |
173 | # Windows Store app package directories and files
174 | AppPackages/
175 | BundleArtifacts/
176 | Package.StoreAssociation.xml
177 | _pkginfo.txt
178 |
179 | # Visual Studio cache files
180 | # files ending in .cache can be ignored
181 | *.[Cc]ache
182 | # but keep track of directories ending in .cache
183 | !*.[Cc]ache/
184 |
185 | # Others
186 | ClientBin/
187 | ~$*
188 | *~
189 | *.dbmdl
190 | *.dbproj.schemaview
191 | *.pfx
192 | *.publishsettings
193 | node_modules/
194 | orleans.codegen.cs
195 |
196 | # Since there are multiple workflows, uncomment next line to ignore bower_components
197 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
198 | #bower_components/
199 |
200 | # RIA/Silverlight projects
201 | Generated_Code/
202 |
203 | # Backup & report files from converting an old project file
204 | # to a newer Visual Studio version. Backup files are not needed,
205 | # because we have git ;-)
206 | _UpgradeReport_Files/
207 | Backup*/
208 | UpgradeLog*.XML
209 | UpgradeLog*.htm
210 |
211 | # SQL Server files
212 | *.mdf
213 | *.ldf
214 |
215 | # Business Intelligence projects
216 | *.rdl.data
217 | *.bim.layout
218 | *.bim_*.settings
219 |
220 | # Microsoft Fakes
221 | FakesAssemblies/
222 |
223 | # GhostDoc plugin setting file
224 | *.GhostDoc.xml
225 |
226 | # Node.js Tools for Visual Studio
227 | .ntvs_analysis.dat
228 |
229 | # Visual Studio 6 build log
230 | *.plg
231 |
232 | # Visual Studio 6 workspace options file
233 | *.opt
234 |
235 | # Visual Studio LightSwitch build output
236 | **/*.HTMLClient/GeneratedArtifacts
237 | **/*.DesktopClient/GeneratedArtifacts
238 | **/*.DesktopClient/ModelManifest.xml
239 | **/*.Server/GeneratedArtifacts
240 | **/*.Server/ModelManifest.xml
241 | _Pvt_Extensions
242 |
243 | # Paket dependency manager
244 | .paket/paket.exe
245 | paket-files/
246 |
247 | # FAKE - F# Make
248 | .fake/
249 |
250 | # JetBrains Rider
251 | .idea/
252 | *.sln.iml
253 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/build/build.ps1:
--------------------------------------------------------------------------------
1 | param (
2 | [string]$BuildVersionNumber=$(throw "-BuildVersionNumber is required."),
3 | [string]$TagVersionNumber
4 | )
5 |
6 | # set version number in package.json
7 | Get-ChildItem -Path $PSScriptRoot\..\src -Filter project.json -Recurse | ForEach-Object{
8 | $ProjectJsonPath = $_.FullName
9 | if ($TagVersionNumber){
10 | (gc -Path $ProjectJsonPath) `
11 | -replace "(?<=`"version`":\s`")[.\w-]*(?=`",)", "$TagVersionNumber" |
12 | sc -Path $ProjectJsonPath -Encoding UTF8
13 | }
14 | else{
15 | (gc -Path $ProjectJsonPath) `
16 | -replace "(?<=`"version`":\s`")[.\w-]*(?=`",)", "$BuildVersionNumber" |
17 | sc -Path $ProjectJsonPath -Encoding UTF8
18 | }
19 | }
20 |
21 | # run restore on all project.json files in the src folder including 2>1 to redirect stderr to stdout for badly behaved tools
22 | Get-ChildItem -Path $PSScriptRoot\..\src -Filter project.json -Recurse | ForEach-Object { & dotnet restore $_.FullName 2>1 }
23 |
24 | # run pack on all project.json files in the src folder including 2>1 to redirect stderr to stdout for badly behaved tools
25 | Get-ChildItem -Path $PSScriptRoot\..\src -Filter project.json -Recurse | ForEach-Object { & dotnet pack $_.FullName -c Release 2>1 }
26 |
--------------------------------------------------------------------------------
/build/cover.ps1:
--------------------------------------------------------------------------------
1 | param (
2 | [string]$CoverallsRepoToken=$(throw "-CoverallsRepoToken is required."),
3 | [string]$CoverFilter=$(throw "-CoverFilter is required.")
4 | )
5 |
6 | Write-Output "Starting code coverage with filter: $CoverFilter"
7 |
8 | $alwaysFilter = "-[xunit*]* -[Microsoft*]* -[dotnet*]* -[NuGet*]* -[Newtonsoft*]* -[csc]* -[Anonymously*]*"
9 | $filter = "$CoverFilter $alwaysFilter"
10 |
11 | $packagesPath = $env:USERPROFILE + "\.nuget\packages"
12 | $opencoverPath = $packagesPath + "\OpenCover\4.6.519\tools\OpenCover.Console.exe"
13 | $coverallsPath = $packagesPath + "\coveralls.io\1.3.4\tools\coveralls.net.exe"
14 | $tempPath = "c:\codecoverage"
15 | $tempCoveragePath = $tempPath + "\coverage\"
16 | $tempCoverageFileName = $tempCoveragePath + "coverage.xml"
17 |
18 | # create temp path
19 | if (-not (test-path $tempPath) ) {
20 | mkdir $tempPath | Out-Null
21 | }
22 |
23 | # run opencover
24 | Get-ChildItem -Path $PSScriptRoot\..\test -Filter project.json -Recurse | ForEach-Object {
25 | $path = "$tempPath\$($_.Directory.BaseName)"
26 | if (-not (test-path $path) ) {
27 | mkdir $path | Out-Null
28 | }
29 |
30 | $tempBinPath = $path + "\bin\"
31 | $targetArgs = "`"test -o $tempBinPath $($_.FullName)`""
32 |
33 | if (-not (test-path $tempBinPath) ) {
34 | mkdir $tempBinPath | Out-Null
35 | }
36 |
37 | if (-not (test-path $tempCoveragePath) ) {
38 | mkdir $tempCoveragePath | Out-Null
39 | }
40 |
41 | & $opencoverPath `
42 | -register:user `
43 | -target:"dotnet.exe" `
44 | -targetargs:$targetArgs `
45 | -searchdirs:$tempBinPath `
46 | -output:$tempCoverageFileName `
47 | -mergebyhash `
48 | -mergeoutput `
49 | -skipautoprops `
50 | -returntargetcode `
51 | -filter:$filter `
52 | -hideskipped:Filter `
53 | -oldstyle
54 | }
55 |
56 | # upload to coveralls.io
57 | Write-Output "Sending code coverage results to coveralls.io"
58 |
59 | & $coverallsPath `
60 | --opencover $tempCoverageFileName `
61 | --full-sources `
62 | --repo-token $CoverallsRepoToken
63 |
64 | 7z a codecoverage.zip $tempCoverageFileName
65 | Push-AppveyorArtifact codecoverage.zip
66 |
67 |
68 |
--------------------------------------------------------------------------------
/build/test.ps1:
--------------------------------------------------------------------------------
1 | # run restore on all project.json files in the src folder including 2>1 to redirect stderr to stdout for badly behaved tools
2 | Get-ChildItem -Path $PSScriptRoot\..\test -Filter project.json -Recurse | ForEach-Object { & dotnet restore $_.FullName 2>&1 }
3 |
4 | # run tests
5 | Get-ChildItem -Path $PSScriptRoot\..\test -Filter project.json -Recurse | ForEach-Object { & dotnet test -c Release $_.FullName 2>&1 }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/demo/LimitsMiddleware.AspNetCore.Demo/web.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/global.json:
--------------------------------------------------------------------------------
1 | {
2 | "projects": [ "src", "test" ],
3 | "sdk": { "version": "1.0.0-preview2-003131" }
4 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/src/LimitsMiddleware.AspNetCore/ContentLengthLimitingStream.cs:
--------------------------------------------------------------------------------
1 | namespace LimitsMiddleware
2 | {
3 | using System.IO;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 |
7 | // Template from http://forums.asp.net/t/1966232.aspx?Web+API+OWIN+Host+Limit+Request+Size
8 |
9 | internal class ContentLengthLimitingStream : Stream
10 | {
11 | private readonly Stream _innerStream;
12 | private readonly long _maxRequestSizeInBytes;
13 | private long _totalBytesReadCount;
14 |
15 | public ContentLengthLimitingStream(Stream innerStream, long maxReceivedMessageSize)
16 | {
17 | _innerStream = innerStream;
18 | _maxRequestSizeInBytes = maxReceivedMessageSize;
19 | }
20 |
21 | protected Stream InnerStream => _innerStream;
22 |
23 | public override bool CanRead => _innerStream.CanRead;
24 |
25 | public override bool CanSeek => _innerStream.CanSeek;
26 |
27 | public override bool CanWrite => _innerStream.CanWrite;
28 |
29 | public override long Length => _innerStream.Length;
30 |
31 | public override long Position
32 | {
33 | get { return _innerStream.Position; }
34 | set { _innerStream.Position = value; }
35 | }
36 |
37 | public override int ReadTimeout
38 | {
39 | get { return _innerStream.ReadTimeout; }
40 | set { _innerStream.ReadTimeout = value; }
41 | }
42 |
43 | public override bool CanTimeout => _innerStream.CanTimeout;
44 |
45 | public override int WriteTimeout
46 | {
47 | get { return _innerStream.WriteTimeout; }
48 | set { _innerStream.WriteTimeout = value; }
49 | }
50 |
51 | protected override void Dispose(bool disposing)
52 | {
53 | if (disposing)
54 | {
55 | _innerStream.Dispose();
56 | }
57 | base.Dispose(disposing);
58 | }
59 |
60 | public override long Seek(long offset, SeekOrigin origin)
61 | {
62 | return _innerStream.Seek(offset, origin);
63 | }
64 |
65 | public override int Read(byte[] buffer, int offset, int count)
66 | {
67 | int currentNumberOfBytesRead = _innerStream.Read(buffer, offset, count);
68 |
69 | ValidateRequestSize(currentNumberOfBytesRead);
70 |
71 | return currentNumberOfBytesRead;
72 | }
73 |
74 | public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
75 | {
76 | int currentNumberOfBytesRead = await _innerStream.ReadAsync(buffer, offset, count, cancellationToken);
77 |
78 | ValidateRequestSize(currentNumberOfBytesRead);
79 |
80 | return currentNumberOfBytesRead;
81 | }
82 |
83 | public override int ReadByte()
84 | {
85 | int currentNumberOfBytesRead = _innerStream.ReadByte();
86 |
87 | ValidateRequestSize(currentNumberOfBytesRead);
88 |
89 | return currentNumberOfBytesRead;
90 | }
91 |
92 | public override void Flush()
93 | {
94 | _innerStream.Flush();
95 | }
96 |
97 | public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
98 | {
99 | return _innerStream.CopyToAsync(destination, bufferSize, cancellationToken);
100 | }
101 |
102 | public override Task FlushAsync(CancellationToken cancellationToken)
103 | {
104 | return _innerStream.FlushAsync(cancellationToken);
105 | }
106 |
107 | public override void SetLength(long value)
108 | {
109 | _innerStream.SetLength(value);
110 | }
111 |
112 | public override void Write(byte[] buffer, int offset, int count)
113 | {
114 | _innerStream.Write(buffer, offset, count);
115 | }
116 |
117 | public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
118 | {
119 | return _innerStream.WriteAsync(buffer, offset, count, cancellationToken);
120 | }
121 |
122 | public override void WriteByte(byte value)
123 | {
124 | _innerStream.WriteByte(value);
125 | }
126 |
127 | private void ValidateRequestSize(int currentNumberOfBytesRead)
128 | {
129 | _totalBytesReadCount += currentNumberOfBytesRead;
130 |
131 | if (_totalBytesReadCount > 0 && _maxRequestSizeInBytes == 0)
132 | {
133 | throw new ContentLengthRequiredException();
134 | }
135 |
136 | if (_totalBytesReadCount > _maxRequestSizeInBytes)
137 | {
138 | throw new ContentLengthExceededException($"Request size exceeds the allowed maximum size of {_maxRequestSizeInBytes} bytes");
139 | }
140 | }
141 | }
142 | }
--------------------------------------------------------------------------------
/src/LimitsMiddleware.AspNetCore/ContentLengthRequiredException.cs:
--------------------------------------------------------------------------------
1 | namespace LimitsMiddleware
2 | {
3 | using System;
4 |
5 | internal class ContentLengthRequiredException : Exception
6 | {}
7 | }
--------------------------------------------------------------------------------
/src/LimitsMiddleware.AspNetCore/Extensions/ApplicationBuilderExtensions.ConnectionTimeout.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.AspNetCore.Builder;
3 | using Microsoft.AspNetCore.Http;
4 |
5 | namespace LimitsMiddleware.Extensions
6 | {
7 | public static partial class ApplicationBuilderExtensions
8 | {
9 | ///
10 | /// Timeouts the connection if there hasn't been an read activity on the request body stream or any
11 | /// write activity on the response body stream.
12 | ///
13 | /// The IApplicationBuilder instance.
14 | /// The timeout.
15 | /// (Optional) The name of the logger log messages are written to.
16 | /// The IApplicationBuilder instance.
17 | public static IApplicationBuilder ConnectionTimeout(this IApplicationBuilder app, TimeSpan timeout, string loggerName = null)
18 | {
19 | if (app == null)
20 | {
21 | throw new ArgumentNullException(nameof(app));
22 | }
23 |
24 | app.Use(Limits.ConnectionTimeout(timeout, loggerName));
25 |
26 | return app;
27 | }
28 |
29 | ///
30 | /// Timeouts the connection if there hasn't been an read activity on the request body stream or any
31 | /// write activity on the response body stream.
32 | ///
33 | /// The IApplicationBuilder instance.
34 | ///
35 | /// A delegate to retrieve the timeout timespan. Allows you
36 | /// to supply different values at runtime.
37 | ///
38 | /// (Optional) The name of the logger log messages are written to.
39 | /// The IApplicationBuilder instance.
40 | public static IApplicationBuilder ConnectionTimeout(this IApplicationBuilder app, Func getTimeout, string loggerName = null)
41 | {
42 | if (app == null)
43 | {
44 | throw new ArgumentNullException(nameof(app));
45 | }
46 | if (getTimeout == null)
47 | {
48 | throw new ArgumentNullException(nameof(getTimeout));
49 | }
50 |
51 | app.Use(Limits.ConnectionTimeout(getTimeout, loggerName));
52 |
53 | return app;
54 | }
55 |
56 |
57 | ///
58 | /// Timeouts the connection if there hasn't been an read activity on the request body stream or any
59 | /// write activity on the response body stream.
60 | ///
61 | /// The IApplicationBuilder instance.
62 | ///
63 | /// A delegate to retrieve the timeout timespan. Allows you
64 | /// to supply different values at runtime.
65 | ///
66 | /// (Optional) The name of the logger log messages are written to.
67 | /// A middleware delegate.
68 | /// getTimeout
69 | public static IApplicationBuilder ConnectionTimeout(this IApplicationBuilder app, Func getTimeout, string loggerName = null)
70 | {
71 | if (app == null)
72 | {
73 | throw new ArgumentNullException(nameof(app));
74 | }
75 | if (getTimeout == null)
76 | {
77 | throw new ArgumentNullException(nameof(getTimeout));
78 | }
79 |
80 | app.Use(async (context, next) =>
81 | {
82 | Limits.ConnectionTimeout(getTimeout, loggerName);
83 | await next.Invoke();
84 | });
85 | Func connectionTimeout = Limits.ConnectionTimeout(getTimeout, loggerName);
86 | app.Use(connectionTimeout);
87 |
88 | return app;
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/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/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/Extensions/ApplicationBuilderExtensions.MaxConcurrentRequests.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 number of concurrent requests that can be handled used by the subsequent stages in the aspnetcore pipeline.
10 | ///
11 | /// The IApplicationBuilder instance.
12 | ///
13 | /// The maximum number of concurrent requests. Use 0 or a negative
14 | /// number to specify unlimited number of concurrent requests.
15 | ///
16 | /// (Optional) The name of the logger log messages are written to.
17 | /// The IApplicationBuilder instance.
18 | public static IApplicationBuilder MaxConcurrentRequests(this IApplicationBuilder app, int maxConcurrentRequests, string loggerName = null)
19 | {
20 | if (app == null)
21 | {
22 | throw new ArgumentNullException(nameof(app));
23 | }
24 |
25 | return MaxConcurrentRequests(app, () => maxConcurrentRequests, loggerName);
26 | }
27 |
28 | ///
29 | /// Limits the number of concurrent requests that can be handled used by the subsequent stages in the aspnetcore pipeline.
30 | ///
31 | /// The IApplicationBuilder instance.
32 | ///
33 | /// A delegate to retrieve the maximum number of concurrent requests. Allows you
34 | /// to supply different values at runtime. Use 0 or a negative number to specify unlimited number of concurrent
35 | /// requests.
36 | ///
37 | /// (Optional) The name of the logger log messages are written to.
38 | /// The IApplicationBuilder instance.
39 | public static IApplicationBuilder MaxConcurrentRequests(this IApplicationBuilder app, Func getMaxConcurrentRequests, string loggerName = null)
40 | {
41 | if (app == null)
42 | {
43 | throw new ArgumentNullException(nameof(app));
44 | }
45 | if (getMaxConcurrentRequests == null)
46 | {
47 | throw new ArgumentNullException(nameof(getMaxConcurrentRequests));
48 | }
49 |
50 | app.Use(Limits.MaxConcurrentRequests(getMaxConcurrentRequests, loggerName));
51 |
52 | return app;
53 | }
54 |
55 | ///
56 | /// Limits the number of concurrent requests that can be handled used by the subsequent stages in the aspnetcore pipeline.
57 | ///
58 | /// The IApplicationBuilder instance.
59 | ///
60 | /// A delegate to retrieve the maximum number of concurrent requests. Allows you
61 | /// to supply different values at runtime. Use 0 or a negative number to specify unlimited number of concurrent
62 | /// requests.
63 | ///
64 | /// (Optional) The name of the logger log messages are written to.
65 | /// The IApplicationBuilder instance.
66 | public static IApplicationBuilder MaxConcurrentRequests(this IApplicationBuilder app, Func getMaxConcurrentRequests, string loggerName = null)
67 | {
68 | if (app == null)
69 | {
70 | throw new ArgumentNullException(nameof(app));
71 | }
72 | if (getMaxConcurrentRequests == null)
73 | {
74 | throw new ArgumentNullException(nameof(getMaxConcurrentRequests));
75 | }
76 |
77 | app.Use(Limits.MaxConcurrentRequests(getMaxConcurrentRequests, loggerName));
78 |
79 | return app;
80 | }
81 | }
82 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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.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/Extensions/ApplicationBuilderExtensions.MinResponseDelay.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.AspNetCore.Builder;
3 |
4 | namespace LimitsMiddleware.Extensions
5 | {
6 | public static partial class ApplicationBuilderExtensions
7 | {
8 | ///
9 | /// Sets a minimum delay in miliseconds before sending the response.
10 | ///
11 | /// The IApplicationBuilder instance.
12 | ///
13 | /// The minimum delay to wait before sending the response.
14 | ///
15 | /// (Optional) The name of the logger log messages are written to.
16 | /// The IApplicationBuilder instance.
17 | public static IApplicationBuilder MinResponseDelay(this IApplicationBuilder app, int minDelay, string loggerName = null)
18 | {
19 | if (app == null)
20 | {
21 | throw new ArgumentNullException(nameof(app));
22 | }
23 |
24 | return MinResponseDelay(app, () => minDelay, loggerName);
25 | }
26 |
27 | ///
28 | /// Sets a minimum delay before sending the response.
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 | /// (Optional) The name of the logger log messages are written to.
36 | /// The IApplicationBuilder instance.
37 | public static IApplicationBuilder MinResponseDelay(this IApplicationBuilder app, Func getMinDelay, string loggerName = null)
38 | {
39 | if (app == null)
40 | {
41 | throw new ArgumentNullException(nameof(app));
42 | }
43 | if (getMinDelay == null)
44 | {
45 | throw new ArgumentNullException(nameof(getMinDelay));
46 | }
47 |
48 | app.Use(Limits.MinResponseDelay(getMinDelay, loggerName));
49 | return app;
50 | }
51 |
52 | ///
53 | /// Sets a minimum delay before sending the response.
54 | ///
55 | /// The IApplicationBuilder instance.
56 | ///
57 | /// A delegate to retrieve the minimum delay before calling the next stage in the pipeline. Note:
58 | /// the delegate should return quickly.
59 | ///
60 | /// (Optional) The name of the logger log messages are written to.
61 | /// The IApplicationBuilder instance.
62 | public static IApplicationBuilder MinResponseDelay(this IApplicationBuilder app, Func getMinDelay, string loggerName = null)
63 | {
64 | if (app == null)
65 | {
66 | throw new ArgumentNullException(nameof(app));
67 | }
68 | if (getMinDelay == null)
69 | {
70 | throw new ArgumentNullException(nameof(getMinDelay));
71 | }
72 |
73 | app.Use(Limits.MinResponseDelay(getMinDelay, loggerName));
74 | return app;
75 | }
76 |
77 | ///
78 | /// Sets a minimum delay before sending the response.
79 | ///
80 | /// The IApplicationBuilder instance.
81 | ///
82 | /// A delegate to retrieve the minimum delay before calling the next stage in the pipeline. Note:
83 | /// the delegate should return quickly.
84 | ///
85 | /// (Optional) The name of the logger log messages are written to.
86 | /// The IApplicationBuilder instance.
87 | public static IApplicationBuilder MinResponseDelay(this IApplicationBuilder app, Func getMinDelay, string loggerName = null)
88 | {
89 | if (app == null)
90 | {
91 | throw new ArgumentNullException(nameof(app));
92 | }
93 | if (getMinDelay == null)
94 | {
95 | throw new ArgumentNullException(nameof(getMinDelay));
96 | }
97 |
98 | app.Use(Limits.MinResponseDelay(getMinDelay, loggerName));
99 | return app;
100 | }
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/src/LimitsMiddleware.AspNetCore/Limits.ConnectionTimeout.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using LimitsMiddleware.Logging;
4 | using Microsoft.AspNetCore.Http;
5 |
6 | namespace LimitsMiddleware
7 | {
8 | using MidFunc = Func;
9 |
10 | public static partial class Limits
11 | {
12 | ///
13 | /// Timeouts the connection if there hasn't been an read activity on the request body stream or any
14 | /// write activity on the response body stream.
15 | ///
16 | /// (Optional) The name of the logger log messages are written to.
17 | /// The timeout.
18 | /// A middleware delegate.
19 | public static MidFunc ConnectionTimeout(TimeSpan timeout, string loggerName = null) => ConnectionTimeout(() => timeout, loggerName);
20 |
21 | ///
22 | /// Timeouts the connection if there hasn't been an read activity on the request body stream or any
23 | /// write activity on the response body stream.
24 | ///
25 | ///
26 | /// A delegate to retrieve the timeout timespan. Allows you
27 | /// to supply different values at runtime.
28 | ///
29 | /// (Optional) The name of the logger log messages are written to.
30 | /// A middleware delegate.
31 | /// getTimeout
32 | public static MidFunc ConnectionTimeout(Func getTimeout, string loggerName = null)
33 | {
34 | if (getTimeout == null)
35 | {
36 | throw new ArgumentNullException(nameof(getTimeout));
37 | }
38 |
39 | return ConnectionTimeout(_ => getTimeout(), loggerName);
40 | }
41 |
42 | ///
43 | /// Timeouts the connection if there hasn't been an read activity on the request body stream or any
44 | /// write activity on the response body stream.
45 | ///
46 | ///
47 | /// A delegate to retrieve the timeout timespan. Allows you
48 | /// to supply different values at runtime.
49 | ///
50 | /// (Optional) The name of the logger log messages are written to.
51 | /// A middleware delegate.
52 | /// getTimeout
53 | public static MidFunc ConnectionTimeout(Func getTimeout, string loggerName = null)
54 | {
55 | if (getTimeout == null)
56 | {
57 | throw new ArgumentNullException(nameof(getTimeout));
58 | }
59 |
60 | loggerName = string.IsNullOrWhiteSpace(loggerName)
61 | ? $"{nameof(LimitsMiddleware)}.{nameof(ConnectionTimeout)}"
62 | : loggerName;
63 |
64 | var logger = LogProvider.GetLogger(loggerName);
65 |
66 | return
67 | next =>
68 | context =>
69 | {
70 | var limitsRequestContext = new RequestContext(context.Request);
71 |
72 | var requestBodyStream = context.Request.Body ?? Stream.Null;
73 | var responseBodyStream = context.Response.Body;
74 |
75 | var connectionTimeout = getTimeout(limitsRequestContext);
76 | context.Request.Body = new TimeoutStream(requestBodyStream, connectionTimeout, logger);
77 | context.Response.Body = new TimeoutStream(responseBodyStream, connectionTimeout, logger);
78 | return next(context);
79 | };
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/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/Limits.MaxBandwidthPerRequest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using LimitsMiddleware.RateLimiters;
4 | using Microsoft.AspNetCore.Http;
5 |
6 | namespace LimitsMiddleware
7 | {
8 | using MidFunc = Func;
9 |
10 | public static partial class Limits
11 | {
12 | ///
13 | /// Limits the bandwith used by the subsequent stages in the aspnetcore pipeline.
14 | ///
15 | ///
16 | /// The maximum number of bytes per second to be transferred. Use 0 or a negative
17 | /// number to specify infinite bandwidth.
18 | ///
19 | /// A middleware delegate.
20 | public static MidFunc MaxBandwidthPerRequest(int maxBytesPerSecond) => MaxBandwidthPerRequest(() => maxBytesPerSecond);
21 |
22 | ///
23 | /// Limits the bandwith used by the subsequent stages in the aspnetcore pipeline.
24 | ///
25 | ///
26 | /// A delegate to retrieve the maximum number of bytes per second to be transferred.
27 | /// Allows you to supply different values at runtime. Use 0 or a negative number to specify infinite bandwidth.
28 | ///
29 | /// A middleware delegate.
30 | /// getMaxBytesPerSecond
31 | public static MidFunc MaxBandwidthPerRequest(Func getMaxBytesPerSecond)
32 | {
33 | if (getMaxBytesPerSecond == null)
34 | {
35 | throw new ArgumentNullException(nameof(getMaxBytesPerSecond));
36 | }
37 |
38 | return MaxBandwidthPerRequest(_ => getMaxBytesPerSecond());
39 | }
40 |
41 | ///
42 | /// Limits the bandwith used by the subsequent stages in the aspnetcore pipeline.
43 | ///
44 | ///
45 | /// A delegate to retrieve the maximum number of bytes per second to be transferred.
46 | /// Allows you to supply different values at runtime. Use 0 or a negative number to specify infinite bandwidth.
47 | ///
48 | /// A middleware delegate.
49 | /// getMaxBytesPerSecond
50 | public static MidFunc MaxBandwidthPerRequest(Func getMaxBytesPerSecond)
51 | {
52 | if (getMaxBytesPerSecond == null)
53 | {
54 | throw new ArgumentNullException(nameof(getMaxBytesPerSecond));
55 | }
56 |
57 | return
58 | next =>
59 | async context =>
60 | {
61 | var requestBodyStream = context.Request.Body ?? Stream.Null;
62 | var responseBodyStream = context.Response.Body;
63 |
64 | var limitsRequestContext = new RequestContext(context.Request);
65 |
66 | var requestTokenBucket = new FixedTokenBucket(
67 | () => getMaxBytesPerSecond(limitsRequestContext));
68 | var responseTokenBucket = new FixedTokenBucket(
69 | () => getMaxBytesPerSecond(limitsRequestContext));
70 |
71 | // ReSharper disable once ArrangeBraces_using
72 | using (requestTokenBucket.RegisterRequest())
73 | using (responseTokenBucket.RegisterRequest())
74 | {
75 |
76 | context.Request.Body = new ThrottledStream(requestBodyStream, requestTokenBucket);
77 | context.Response.Body = new ThrottledStream(responseBodyStream, responseTokenBucket);
78 |
79 | //TODO consider SendFile interception
80 | await next(context).ConfigureAwait(false);
81 | }
82 | };
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/LimitsMiddleware.AspNetCore/Limits.MaxConcurrentRequests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net;
3 | using System.Threading;
4 | using LimitsMiddleware.Logging;
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 number of concurrent requests that can be handled used by the subsequent stages in the aspnetcore pipeline.
15 | ///
16 | ///
17 | /// The maximum number of concurrent requests. Use 0 or a negative
18 | /// number to specify unlimited number of concurrent requests.
19 | ///
20 | /// (Optional) The name of the logger log messages are written to.
21 | /// A middleware delegate.
22 | public static MidFunc MaxConcurrentRequests(int maxConcurrentRequests, string loggerName = null) =>
23 | MaxConcurrentRequests(() => maxConcurrentRequests, loggerName);
24 |
25 | ///
26 | /// Limits the number of concurrent requests that can be handled used by the subsequent stages in the aspnetcore pipeline.
27 | ///
28 | ///
29 | /// A delegate to retrieve the maximum number of concurrent requests. Allows you
30 | /// to supply different values at runtime. Use 0 or a negative number to specify unlimited number of concurrent
31 | /// requests.
32 | ///
33 | /// (Optional) The name of the logger log messages are written to.
34 | /// A middleware delegate.
35 | /// getMaxConcurrentRequests
36 | public static MidFunc MaxConcurrentRequests(Func getMaxConcurrentRequests, string loggerName = null)
37 | {
38 | if (getMaxConcurrentRequests == null)
39 | {
40 | throw new ArgumentNullException(nameof(getMaxConcurrentRequests));
41 | }
42 |
43 | return MaxConcurrentRequests(_ => getMaxConcurrentRequests(), loggerName);
44 | }
45 |
46 | ///
47 | /// Limits the number of concurrent requests that can be handled used by the subsequent stages in the aspnetcore pipeline.
48 | ///
49 | ///
50 | /// A delegate to retrieve the maximum number of concurrent requests. Allows you
51 | /// to supply different values at runtime. Use 0 or a negative number to specify unlimited number of concurrent
52 | /// requests.
53 | ///
54 | /// (Optional) The name of the logger log messages are written to.
55 | /// A middleware delegate.
56 | /// getMaxConcurrentRequests
57 | public static MidFunc MaxConcurrentRequests(Func getMaxConcurrentRequests, string loggerName = null)
58 | {
59 | if (getMaxConcurrentRequests == null)
60 | {
61 | throw new ArgumentNullException(nameof(getMaxConcurrentRequests));
62 | }
63 |
64 | loggerName = string.IsNullOrWhiteSpace(loggerName)
65 | ? $"{nameof(LimitsMiddleware)}.{nameof(MaxConcurrentRequests)}"
66 | : loggerName;
67 | var logger = LogProvider.GetLogger(loggerName);
68 | int concurrentRequestCounter = 0;
69 |
70 | return
71 | next =>
72 | async context =>
73 | {
74 | var limitsRequestContext = new RequestContext(context.Request);
75 | int maxConcurrentRequests = getMaxConcurrentRequests(limitsRequestContext);
76 | if (maxConcurrentRequests <= 0)
77 | {
78 | maxConcurrentRequests = int.MaxValue;
79 | }
80 | try
81 | {
82 | int concurrentRequests = Interlocked.Increment(ref concurrentRequestCounter);
83 | logger.Debug($"Concurrent request {concurrentRequests}/{maxConcurrentRequests}.");
84 | if (concurrentRequests > maxConcurrentRequests)
85 | {
86 | logger.Info($"Limit ({maxConcurrentRequests}). Request rejected.");
87 | var response = context.Response;
88 | response.StatusCode = (int)HttpStatusCode.ServiceUnavailable;
89 | return;
90 | }
91 | await next(context);
92 | }
93 | finally
94 | {
95 | int concurrentRequests = Interlocked.Decrement(ref concurrentRequestCounter);
96 | logger.Debug($"Concurrent request {concurrentRequests}/{maxConcurrentRequests}.");
97 | }
98 | };
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/LimitsMiddleware.AspNetCore/Limits.MaxQueryStringLength.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net;
3 | using LimitsMiddleware.Logging;
4 | using Microsoft.AspNetCore.Http;
5 |
6 | namespace LimitsMiddleware
7 | {
8 | using MidFunc = Func;
9 |
10 | public static partial class Limits
11 | {
12 | ///
13 | /// Limits the length of the query string.
14 | ///
15 | /// Maximum length of the query string.
16 | /// (Optional) The name of the logger log messages are written to.
17 | /// A middleware delegate.
18 | public static MidFunc MaxQueryStringLength(int maxQueryStringLength, string loggerName = null) =>
19 | MaxQueryStringLength(_ => maxQueryStringLength, loggerName);
20 |
21 | ///
22 | /// Limits the length of the query string.
23 | ///
24 | /// A delegate to get the maximum query string length.
25 | /// (Optional) The name of the logger log messages are written to.
26 | /// A middleware delegate.
27 | public static MidFunc MaxQueryStringLength(Func getMaxQueryStringLength, string loggerName = null) =>
28 | MaxQueryStringLength(_ => getMaxQueryStringLength(), loggerName);
29 |
30 | ///
31 | /// Limits the length of the query string.
32 | ///
33 | /// A delegate to get the maximum query string length.
34 | /// (Optional) The name of the logger log messages are written to.
35 | /// A middleware delegate.
36 | /// getMaxQueryStringLength
37 | public static MidFunc MaxQueryStringLength(Func getMaxQueryStringLength, string loggerName = null)
38 | {
39 | if (getMaxQueryStringLength == null)
40 | {
41 | throw new ArgumentNullException(nameof(getMaxQueryStringLength));
42 | }
43 |
44 | loggerName = string.IsNullOrWhiteSpace(loggerName)
45 | ? $"{nameof(LimitsMiddleware)}.{nameof(MaxQueryStringLength)}"
46 | : loggerName;
47 | var logger = LogProvider.GetLogger(loggerName);
48 |
49 | return
50 | next =>
51 | async context =>
52 | {
53 | var requestContext = new RequestContext(context.Request);
54 |
55 | var queryString = context.Request.QueryString;
56 | if (queryString.HasValue)
57 | {
58 | int maxQueryStringLength = getMaxQueryStringLength(requestContext);
59 | string unescapedQueryString = Uri.UnescapeDataString(queryString.Value);
60 | logger.Debug($"Querystring of request with an unescaped length of {unescapedQueryString.Length}");
61 | if (unescapedQueryString.Length > maxQueryStringLength)
62 | {
63 | logger.Info($"Querystring (Length {unescapedQueryString.Length}) too long (allowed {maxQueryStringLength}). Request rejected.");
64 | context.Response.StatusCode = (int)HttpStatusCode.RequestUriTooLong;
65 | return;
66 | }
67 | }
68 | else
69 | {
70 | logger.Debug("No querystring.");
71 | }
72 | await next(context);
73 | };
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/LimitsMiddleware.AspNetCore/Limits.MaxRequestContentLength.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net;
3 | using LimitsMiddleware.Logging;
4 | using Microsoft.AspNetCore.Http;
5 |
6 | namespace LimitsMiddleware
7 | {
8 | using MidFunc = Func;
9 |
10 | public static partial class Limits
11 | {
12 | ///
13 | /// Limits the length of the request content.
14 | ///
15 | /// Maximum length of the content.
16 | /// (Optional) The name of the logger log messages are written to.
17 | /// A middleware delegate.
18 | public static MidFunc MaxRequestContentLength(int maxContentLength, string loggerName = null)
19 | {
20 | return MaxRequestContentLength(() => maxContentLength, loggerName);
21 | }
22 |
23 | ///
24 | /// Limits the length of the request content.
25 | ///
26 | /// A delegate to get the maximum content length.
27 | /// (Optional) The name of the logger log messages are written to.
28 | /// A middleware delegate.
29 | /// getMaxContentLength
30 | public static MidFunc MaxRequestContentLength(Func getMaxContentLength, string loggerName = null)
31 | {
32 | if (getMaxContentLength == null)
33 | {
34 | throw new ArgumentNullException(nameof(getMaxContentLength));
35 | }
36 |
37 | return MaxRequestContentLength(_ => getMaxContentLength(), loggerName);
38 | }
39 |
40 | ///
41 | /// Limits the length of the request content.
42 | ///
43 | /// A delegate to get the maximum content length.
44 | /// (Optional) The name of the logger log messages are written to.
45 | /// A middleware delegate.
46 | /// getMaxContentLength
47 | public static MidFunc MaxRequestContentLength(Func getMaxContentLength, string loggerName = null)
48 | {
49 | if (getMaxContentLength == null)
50 | {
51 | throw new ArgumentNullException(nameof(getMaxContentLength));
52 | }
53 |
54 | loggerName = string.IsNullOrWhiteSpace(loggerName)
55 | ? $"{nameof(LimitsMiddleware)}.{nameof(MaxRequestContentLength)}"
56 | : loggerName;
57 | var logger = LogProvider.GetLogger(loggerName);
58 |
59 | return
60 | next =>
61 | async context =>
62 | {
63 | var request = context.Request;
64 | string requestMethod = request.Method.Trim().ToUpperInvariant();
65 |
66 | if (requestMethod == "HEAD")
67 | {
68 | logger.Debug("HEAD request forwarded without check.");
69 | await next(context);
70 | }
71 | int maxContentLength = getMaxContentLength(new RequestContext(request));
72 | logger.Debug($"Max valid content length is {maxContentLength}.");
73 | if (!IsChunkedRequest(request))
74 | {
75 | logger.Debug("Not a chunked request. Checking content length header.");
76 | var contentLengthHeaderValue = request.ContentLength;
77 | if (!contentLengthHeaderValue.HasValue)
78 | {
79 | if (requestMethod == "PUT" || requestMethod == "POST")
80 | {
81 | SetResponseStatusCode(context, (int)HttpStatusCode.LengthRequired);
82 | return;
83 | }
84 | request.Body = new ContentLengthLimitingStream(request.Body, 0);
85 | }
86 | else
87 | {
88 | if (contentLengthHeaderValue > maxContentLength)
89 | {
90 | logger.Info($"Content length of {contentLengthHeaderValue} exceeds maximum of {maxContentLength}. Request rejected.");
91 | SetResponseStatusCode(context, (int)HttpStatusCode.RequestEntityTooLarge);
92 | return;
93 | }
94 | logger.Debug("Content length header check passed.");
95 |
96 | request.Body = new ContentLengthLimitingStream(request.Body, maxContentLength);
97 | logger.Debug($"Request body stream configured with length limiting stream of {maxContentLength}.");
98 | }
99 | }
100 | else
101 | {
102 | request.Body = new ContentLengthLimitingStream(request.Body, maxContentLength);
103 | logger.Debug($"Request body stream configured with length limiting stream of {maxContentLength}.");
104 | logger.Debug("Chunked request. Content length header not checked.");
105 | }
106 |
107 | try
108 | {
109 | logger.Debug("Request forwarded.");
110 | await next(context);
111 | logger.Debug("Processing finished.");
112 | }
113 | catch (ContentLengthRequiredException)
114 | {
115 | logger.Info("Content length required. Request canceled and rejected.");
116 | SetResponseStatusCode(context, (int)HttpStatusCode.LengthRequired);
117 | }
118 | catch (ContentLengthExceededException)
119 | {
120 | logger.Info($"Content length of {maxContentLength} exceeded. Request canceled and rejected.");
121 | SetResponseStatusCode(context, (int)HttpStatusCode.RequestEntityTooLarge);
122 | }
123 | };
124 | }
125 |
126 | private static bool IsChunkedRequest(HttpRequest request)
127 | {
128 | string header = request.Headers["Transfer-Encoding"];
129 | return header != null && header.Equals("chunked", StringComparison.OrdinalIgnoreCase);
130 | }
131 |
132 | private static void SetResponseStatusCode(HttpContext context, int statusCode)
133 | {
134 | context.Response.StatusCode = statusCode;
135 | }
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/LimitsMiddleware.AspNetCore/Limits.MinResponseDelay.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using LimitsMiddleware.Logging;
4 | using Microsoft.AspNetCore.Http;
5 |
6 | namespace LimitsMiddleware
7 | {
8 | using MidFunc = Func;
9 |
10 | public static partial class Limits
11 | {
12 | ///
13 | /// Adds a minimum delay before sending the response.
14 | ///
15 | /// The min response delay, in milliseconds.
16 | /// (Optional) The name of the logger log messages are written to.
17 | /// A midfunc.
18 | public static MidFunc MinResponseDelay(int minDelay, string loggerName = null) => MinResponseDelay(_ => minDelay, loggerName);
19 |
20 | ///
21 | /// Adds a minimum delay before sending the response.
22 | ///
23 | /// A delegate to return the min response delay.
24 | /// (Optional) The name of the logger log messages are written to.
25 | /// The aspnetcore builder instance.
26 | /// getMinDelay
27 | public static MidFunc MinResponseDelay(Func getMinDelay, string loggerName = null)
28 | {
29 | if (getMinDelay == null)
30 | {
31 | throw new ArgumentNullException(nameof(getMinDelay));
32 | }
33 |
34 | return MinResponseDelay(_ => getMinDelay(), loggerName);
35 | }
36 |
37 | ///
38 | /// Adds a minimum delay before sending the response.
39 | ///
40 | /// A delegate to return the min response delay.
41 | /// (Optional) The name of the logger log messages are written to.
42 | /// The aspnetcore builder instance.
43 | /// getMinDelay
44 | public static MidFunc MinResponseDelay(Func getMinDelay, string loggerName = null)
45 | {
46 | if (getMinDelay == null)
47 | {
48 | throw new ArgumentNullException(nameof(getMinDelay));
49 | }
50 |
51 | return MinResponseDelay(context => TimeSpan.FromMilliseconds(getMinDelay(context)), loggerName);
52 | }
53 |
54 | ///
55 | /// Adds a minimum delay before sending the response.
56 | ///
57 | /// The min response delay.
58 | /// (Optional) The name of the logger log messages are written to.
59 | /// A midfunc.
60 | public static MidFunc MinResponseDelay(TimeSpan minDelay, string loggerName = null) => MinResponseDelay(_ => minDelay, loggerName);
61 |
62 | ///
63 | /// Adds a minimum delay before sending the response.
64 | ///
65 | /// A delegate to return the min response delay.
66 | /// (Optional) The name of the logger log messages are written to.
67 | /// The aspnetcore builder instance.
68 | /// getMinDelay
69 | public static MidFunc MinResponseDelay(Func getMinDelay, string loggerName = null)
70 | {
71 | if (getMinDelay == null)
72 | {
73 | throw new ArgumentNullException(nameof(getMinDelay));
74 | }
75 |
76 | return MinResponseDelay(_ => getMinDelay(), loggerName);
77 | }
78 |
79 | ///
80 | /// Adds a minimum delay before sending the response.
81 | ///
82 | /// A delegate to return the min response delay.
83 | /// (Optional) The name of the logger log messages are written to.
84 | /// The aspnetcore builder instance.
85 | /// getMinDelay
86 | public static MidFunc MinResponseDelay(Func getMinDelay, string loggerName = null)
87 | {
88 | if (getMinDelay == null)
89 | {
90 | throw new ArgumentNullException(nameof(getMinDelay));
91 | }
92 |
93 | loggerName = string.IsNullOrWhiteSpace(loggerName)
94 | ? $"{nameof(LimitsMiddleware)}.{nameof(MinResponseDelay)}"
95 | : loggerName;
96 |
97 | var logger = LogProvider.GetLogger(loggerName);
98 |
99 | return
100 | next =>
101 | async context =>
102 | {
103 | var limitsRequestContext = new RequestContext(context.Request);
104 | var delay = getMinDelay(limitsRequestContext);
105 |
106 | if (delay <= TimeSpan.Zero)
107 | {
108 | await next(context);
109 | return;
110 | }
111 |
112 | logger.Debug($"Delaying response by {delay}");
113 | await Task.Delay(delay, context.RequestAborted);
114 | await next(context);
115 | };
116 | }
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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/RateLimiters/GetUtcNow.cs:
--------------------------------------------------------------------------------
1 | namespace LimitsMiddleware.RateLimiters
2 | {
3 | using System;
4 |
5 | internal delegate DateTimeOffset GetUtcNow();
6 | }
--------------------------------------------------------------------------------
/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/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 | }
--------------------------------------------------------------------------------
/src/LimitsMiddleware.AspNetCore/ThrottledStream.cs:
--------------------------------------------------------------------------------
1 | namespace LimitsMiddleware
2 | {
3 | using System;
4 | using System.IO;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 | using RateLimiters;
8 |
9 | internal class ThrottledStream : Stream
10 | {
11 | private readonly Stream _innerStream;
12 | private readonly FixedTokenBucket _tokenBucket;
13 |
14 | public ThrottledStream(Stream innerStream, FixedTokenBucket fixedTokenBucket)
15 | {
16 | if (innerStream == null)
17 | {
18 | throw new ArgumentNullException(nameof(innerStream));
19 | }
20 |
21 | _innerStream = innerStream;
22 | _tokenBucket = fixedTokenBucket;
23 | }
24 |
25 | public override bool CanRead => _innerStream.CanRead;
26 |
27 | public override bool CanSeek => _innerStream.CanSeek;
28 |
29 | public override bool CanWrite => _innerStream.CanWrite;
30 |
31 | public override long Length => _innerStream.Length;
32 |
33 | public override long Position
34 | {
35 | get { return _innerStream.Position; }
36 | set { _innerStream.Position = value; }
37 | }
38 |
39 | public override void Flush()
40 | {
41 | _innerStream.Flush();
42 | }
43 |
44 | protected override void Dispose(bool disposing)
45 | {
46 | if (disposing)
47 | {
48 | _innerStream.Dispose();
49 | }
50 | }
51 |
52 | public override int Read(byte[] buffer, int offset, int count) => _innerStream.Read(buffer, offset, count);
53 |
54 | public override long Seek(long offset, SeekOrigin origin) => _innerStream.Seek(offset, origin);
55 |
56 | public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
57 | {
58 | int rateBytesPerSecond = Convert.ToInt32(_tokenBucket.Rate);
59 | if (rateBytesPerSecond <= 0)
60 | {
61 | _innerStream.Write(buffer, offset, count);
62 | return;
63 | }
64 |
65 | int tempCountBytes = count;
66 |
67 | // In the unlikely event that the count is greater than the rate (i.e. buffer
68 | // is 16KB (typically this is the max size) and the rate is < 16Kbs), we'll need
69 | // to split it into multiple.
70 |
71 | TimeSpan wait;
72 | while (tempCountBytes > rateBytesPerSecond)
73 | {
74 | if (_tokenBucket.ShouldThrottle(tempCountBytes, out wait))
75 | {
76 | if (wait > TimeSpan.Zero)
77 | {
78 | await Task.Delay(wait, cancellationToken).ConfigureAwait(false);
79 | }
80 | }
81 | await _innerStream.WriteAsync(buffer, offset, rateBytesPerSecond, cancellationToken)
82 | .ConfigureAwait(false);
83 | offset += rateBytesPerSecond;
84 | tempCountBytes -= rateBytesPerSecond;
85 | }
86 | while (_tokenBucket.ShouldThrottle(tempCountBytes, out wait))
87 | {
88 | if (wait > TimeSpan.Zero)
89 | {
90 | await Task.Delay(wait, cancellationToken).ConfigureAwait(false);
91 | }
92 | }
93 | await _innerStream.WriteAsync(buffer, offset, tempCountBytes, cancellationToken)
94 | .ConfigureAwait(false);
95 | }
96 |
97 | public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => _innerStream.ReadAsync(buffer, offset, count, cancellationToken);
98 |
99 | public override void SetLength(long value)
100 | {
101 | _innerStream.SetLength(value);
102 | }
103 |
104 | public override void Write(byte[] buffer, int offset, int count)
105 | {
106 | WriteAsync(buffer, offset, count).Wait();
107 | }
108 |
109 | public override string ToString() => _innerStream.ToString();
110 | }
111 | }
--------------------------------------------------------------------------------
/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