├── .gitattributes ├── .gitignore ├── LICENSE.md ├── README.md ├── WebApiThrottle.StrongName ├── Properties │ └── AssemblyInfo.cs ├── WebApiThrottle.StrongName.csproj ├── WebApiThrottle.StrongName.nuspec ├── WebApiThrottle.snk └── packages.config ├── WebApiThrottle.Tests ├── IpAddressUtilTests.cs ├── Properties │ └── AssemblyInfo.cs ├── WebApiThrottle.Tests.csproj └── packages.config ├── WebApiThrottle.WebApiDemo ├── App_Start │ └── WebApiConfig.cs ├── Controllers │ └── ValuesController.cs ├── Global.asax ├── Global.asax.cs ├── Helpers │ ├── CustomThrottleLogger.cs │ ├── CustomThrottlingFilter.cs │ └── CustomThrottlingHandler.cs ├── Net │ └── CustomIpAddressParser.cs ├── Properties │ └── AssemblyInfo.cs ├── Web.Debug.config ├── Web.Release.config ├── Web.config ├── WebApiThrottle.WebApiDemo.csproj └── packages.config ├── WebApiThrottle.sln ├── WebApiThrottle ├── Attributes │ ├── DisableThrottingAttribute.cs │ └── EnableThrottlingAttribute.cs ├── Configuration │ ├── ThrottlePolicyConfiguration.cs │ ├── ThrottlePolicyRuleConfigurationCollection.cs │ ├── ThrottlePolicyRuleConfigurationElement.cs │ ├── ThrottlePolicyWhitelistConfigurationCollection.cs │ └── ThrottlePolicyWhitelistConfigurationElement.cs ├── Logging │ ├── IThrottleLogger.cs │ ├── ThrottleLogEntry.cs │ └── TracingThrottleLogger.cs ├── Models │ ├── IPAddressRange.cs │ ├── RateLimitPeriod.cs │ ├── RateLimits.cs │ ├── RequestIdentity.cs │ ├── ThrottleCounter.cs │ ├── ThrottlePolicyRule.cs │ ├── ThrottlePolicySettings.cs │ ├── ThrottlePolicyType.cs │ └── ThrottlePolicyWhitelist.cs ├── Net │ ├── DefaultIpAddressParser.cs │ ├── HttpRequestExtensions.cs │ ├── IIpAddressParser.cs │ └── IpAddressUtil.cs ├── Properties │ └── AssemblyInfo.cs ├── Providers │ ├── IThrottlePolicyProvider.cs │ └── PolicyConfigurationProvider.cs ├── Repositories │ ├── CacheRepository.cs │ ├── ConcurrentDictionaryRepository.cs │ ├── IPolicyRepository.cs │ ├── IThrottleRepository.cs │ ├── MemoryCacheRepository.cs │ ├── PolicyCacheRepository.cs │ └── PolicyMemoryCacheRepository.cs ├── ThrottleManager.cs ├── ThrottlePolicy.cs ├── ThrottlingCore.cs ├── ThrottlingFilter.cs ├── ThrottlingHandler.cs ├── ThrottlingMiddleware.cs ├── WebApiThrottle.csproj ├── WebApiThrottle.nuspec ├── app.config └── packages.config └── WebApiThrottler.SelfHostOwinDemo ├── App.config ├── Program.cs ├── Properties └── AssemblyInfo.cs ├── Startup.cs ├── ValuesController.cs ├── WebApiThrottler.SelfHostOwinDemo.csproj └── packages.config /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | [Oo]bj/ 2 | [Bb]in/ 3 | TestResults/ 4 | .nuget/ 5 | _ReSharper.*/ 6 | packages/ 7 | artifacts/ 8 | PublishProfiles/ 9 | *.user 10 | *.suo 11 | *.cache 12 | *.docstates 13 | _ReSharper.* 14 | nuget.exe 15 | *net45.csproj 16 | *k10.csproj 17 | *.psess 18 | *.vsp 19 | *.pidb 20 | *.userprefs 21 | *DS_Store 22 | *.ncrunchsolution 23 | *.*sdf 24 | *.ipch 25 | .vs/ 26 | .sass-cache/ 27 | tmp/ 28 | Thumbs.db 29 | ehthumbs.db 30 | Desktop.ini 31 | $RECYCLE.BIN/ 32 | project.lock.json 33 | 34 | bower_components/ 35 | node_modules/ 36 | *.TMP 37 | 38 | 39 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright 2013-2016 Stefan Prodan (https://stefanprodan.com/) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | WebApiThrottle 2 | ============== 3 | 4 | [![Build status](https://ci.appveyor.com/api/projects/status/vdvuhk2c0tqds297?svg=true)](https://ci.appveyor.com/project/stefanprodan/webapithrottle) 5 | [![NuGet](https://img.shields.io/nuget/v/WebApiThrottle.svg)](https://www.nuget.org/packages/WebApiThrottle) 6 | 7 | ASP.NET Web API Throttling handler, OWIN middleware and filter are designed to control the rate of requests that clients 8 | can make to a Web API based on IP address, client API key and request route. 9 | WebApiThrottle package is available on NuGet at [nuget.org/packages/WebApiThrottle](https://www.nuget.org/packages/WebApiThrottle/). 10 | 11 | Web API throttling can be configured using the built-in ThrottlePolicy. You can set multiple limits 12 | for different scenarios like allowing an IP or Client to make a maximum number of calls per second, per minute, per hour per day or even per week. 13 | You can define these limits to address all requests made to an API or you can scope the limits to each API route. 14 | 15 | --- 16 | If you are looking for the ASP.NET Core version please head to [AspNetCoreRateLimit](https://github.com/stefanprodan/AspNetCoreRateLimit) project. 17 | 18 | AspNetCoreRateLimit is a full rewrite of WebApiThrottle and offers more flexibility in configuring rate limiting for Web API and MVC apps. 19 | 20 | --- 21 | 22 | ### Global throttling based on IP 23 | 24 | The setup bellow will limit the number of requests originated from the same IP. 25 | If from the same IP, in same second, you'll make a call to api/values and api/values/1 the last call will get blocked. 26 | 27 | ``` cs 28 | public static class WebApiConfig 29 | { 30 | public static void Register(HttpConfiguration config) 31 | { 32 | config.MessageHandlers.Add(new ThrottlingHandler() 33 | { 34 | Policy = new ThrottlePolicy(perSecond: 1, perMinute: 20, perHour: 200, perDay: 1500, perWeek: 3000) 35 | { 36 | IpThrottling = true 37 | }, 38 | Repository = new CacheRepository() 39 | }); 40 | } 41 | } 42 | ``` 43 | 44 | If you are self-hosting WebApi with Owin, then you'll have to switch to MemoryCacheRepository that uses the runtime memory cache instead of CacheRepository that uses ASP.NET cache. 45 | 46 | ``` cs 47 | public class Startup 48 | { 49 | public void Configuration(IAppBuilder appBuilder) 50 | { 51 | // Configure Web API for self-host. 52 | HttpConfiguration config = new HttpConfiguration(); 53 | 54 | //Register throttling handler 55 | config.MessageHandlers.Add(new ThrottlingHandler() 56 | { 57 | Policy = new ThrottlePolicy(perSecond: 1, perMinute: 20, perHour: 200, perDay: 1500, perWeek: 3000) 58 | { 59 | IpThrottling = true 60 | }, 61 | Repository = new MemoryCacheRepository() 62 | }); 63 | 64 | appBuilder.UseWebApi(config); 65 | } 66 | } 67 | ``` 68 | 69 | ### Endpoint throttling based on IP 70 | 71 | If, from the same IP, in the same second, you'll make two calls to api/values, the last call will get blocked. 72 | But if in the same second you call api/values/1 too, the request will go through because it's a different route. 73 | 74 | ``` cs 75 | config.MessageHandlers.Add(new ThrottlingHandler() 76 | { 77 | Policy = new ThrottlePolicy(perSecond: 1, perMinute: 30) 78 | { 79 | IpThrottling = true, 80 | EndpointThrottling = true 81 | }, 82 | Repository = new CacheRepository() 83 | }); 84 | ``` 85 | 86 | ### Endpoint throttling based on IP and Client Key 87 | 88 | If a client (identified by an unique API key) from the same IP, in the same second, makes two calls to api/values, then the last call will get blocked. 89 | If you want to apply limits to clients regardless of their IPs then you should set IpThrottling to false. 90 | 91 | ``` cs 92 | config.MessageHandlers.Add(new ThrottlingHandler() 93 | { 94 | Policy = new ThrottlePolicy(perSecond: 1, perMinute: 30) 95 | { 96 | IpThrottling = true, 97 | ClientThrottling = true, 98 | EndpointThrottling = true 99 | }, 100 | Repository = new CacheRepository() 101 | }); 102 | ``` 103 | 104 | ### IP and/or Client Key White-listing 105 | 106 | If requests are initiated from a white-listed IP or Client, then the throttling policy will not be applied and the requests will not get stored. The IP white-list supports IP v4 and v6 ranges like "192.168.0.0/24", "fe80::/10" and "192.168.0.0-192.168.0.255" for more information check [jsakamoto/ipaddressrange](https://github.com/jsakamoto/ipaddressrange). 107 | 108 | ``` cs 109 | config.MessageHandlers.Add(new ThrottlingHandler() 110 | { 111 | Policy = new ThrottlePolicy(perSecond: 2, perMinute: 60) 112 | { 113 | IpThrottling = true, 114 | IpWhitelist = new List { "::1", "192.168.0.0/24" }, 115 | 116 | ClientThrottling = true, 117 | ClientWhitelist = new List { "admin-key" } 118 | }, 119 | Repository = new CacheRepository() 120 | }); 121 | ``` 122 | 123 | ### IP and/or Client Key custom rate limits 124 | 125 | You can define custom limits for known IPs or Client Keys, these limits will override the default ones. Be aware that a custom limit will only work if you have defined a global counterpart. 126 | 127 | ``` cs 128 | config.MessageHandlers.Add(new ThrottlingHandler() 129 | { 130 | Policy = new ThrottlePolicy(perSecond: 1, perMinute: 20, perHour: 200, perDay: 1500) 131 | { 132 | IpThrottling = true, 133 | IpRules = new Dictionary 134 | { 135 | { "192.168.1.1", new RateLimits { PerSecond = 2 } }, 136 | { "192.168.2.0/24", new RateLimits { PerMinute = 30, PerHour = 30*60, PerDay = 30*60*24 } } 137 | }, 138 | 139 | ClientThrottling = true, 140 | ClientRules = new Dictionary 141 | { 142 | { "api-client-key-1", new RateLimits { PerMinute = 40, PerHour = 400 } }, 143 | { "api-client-key-9", new RateLimits { PerDay = 2000 } } 144 | } 145 | }, 146 | Repository = new CacheRepository() 147 | }); 148 | ``` 149 | ### Endpoint custom rate limits 150 | 151 | You can also define custom limits for certain routes, these limits will override the default ones. 152 | You can define endpoint rules by providing relative routes like api/entry/1 or just a URL segment like /entry/. 153 | The endpoint throttling engine will search for the expression you've provided in the absolute URI, 154 | if the expression is contained in the request route then the rule will be applied. 155 | If two or more rules match the same URI then the lower limit will be applied. 156 | 157 | ``` cs 158 | config.MessageHandlers.Add(new ThrottlingHandler() 159 | { 160 | Policy = new ThrottlePolicy(perSecond: 1, perMinute: 20, perHour: 200) 161 | { 162 | IpThrottling = true, 163 | ClientThrottling = true, 164 | EndpointThrottling = true, 165 | EndpointRules = new Dictionary 166 | { 167 | { "api/search", new RateLimits { PerSecond = 10, PerMinute = 100, PerHour = 1000 } } 168 | } 169 | }, 170 | Repository = new CacheRepository() 171 | }); 172 | ``` 173 | 174 | ### Stack rejected requests 175 | 176 | By default, rejected calls are not added to the throttle counter. If a client makes 3 requests per second 177 | and you've set a limit of one call per second, the minute, hour and day counters will only record the first call, the one that wasn't blocked. 178 | If you want rejected requests to count towards the other limits, you'll have to set StackBlockedRequests to true. 179 | 180 | ``` cs 181 | config.MessageHandlers.Add(new ThrottlingHandler() 182 | { 183 | Policy = new ThrottlePolicy(perSecond: 1, perMinute: 30) 184 | { 185 | IpThrottling = true, 186 | ClientThrottling = true, 187 | EndpointThrottling = true, 188 | StackBlockedRequests = true 189 | }, 190 | Repository = new CacheRepository() 191 | }); 192 | ``` 193 | 194 | ### Define rate limits in web.config or app.config 195 | 196 | WebApiThrottle comes with a custom configuration section that lets you define the throttle policy as xml. 197 | 198 | ``` cs 199 | config.MessageHandlers.Add(new ThrottlingHandler() 200 | { 201 | Policy = ThrottlePolicy.FromStore(new PolicyConfigurationProvider()), 202 | Repository = new CacheRepository() 203 | }); 204 | ``` 205 | 206 | Config example (policyType values are 1 - IP, 2 - ClientKey, 3 - Endpoint): 207 | ``` xml 208 | 209 | 210 | 211 |
213 | 214 | 215 | 223 | 224 | 225 | 228 | 230 | 231 | 233 | 234 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | ``` 248 | 249 | ### Retrieving API Client Key 250 | 251 | By default, the ThrottlingHandler retrieves the client API key from the "Authorization-Token" request header value. 252 | If your API key is stored differently, you can override the ThrottlingHandler.SetIdentity function and specify your own retrieval method. 253 | 254 | ``` cs 255 | public class CustomThrottlingHandler : ThrottlingHandler 256 | { 257 | protected override RequestIdentity SetIdentity(HttpRequestMessage request) 258 | { 259 | return new RequestIdentity() 260 | { 261 | ClientKey = request.Headers.Contains("Authorization-Key") ? request.Headers.GetValues("Authorization-Key").First() : "anon", 262 | ClientIp = base.GetClientIp(request).ToString(), 263 | Endpoint = request.RequestUri.AbsolutePath.ToLowerInvariant() 264 | }; 265 | } 266 | } 267 | ``` 268 | 269 | ### Storing throttle metrics 270 | 271 | WebApiThrottle stores all request data in-memory using ASP.NET Cache when hosted in IIS or Runtime MemoryCache when self-hosted with Owin. If you want to change the storage to Velocity, Redis or a NoSQL database, all you have to do is create your own repository by implementing the IThrottleRepository interface. 272 | 273 | ``` cs 274 | public interface IThrottleRepository 275 | { 276 | bool Any(string id); 277 | 278 | ThrottleCounter? FirstOrDefault(string id); 279 | 280 | void Save(string id, ThrottleCounter throttleCounter, TimeSpan expirationTime); 281 | 282 | void Remove(string id); 283 | 284 | void Clear(); 285 | } 286 | ``` 287 | 288 | Since version 1.2 there is an interface for storing and retrieving the policy object as well. The IPolicyRepository is used to update the policy object at runtime. 289 | 290 | ``` cs 291 | public interface IPolicyRepository 292 | { 293 | ThrottlePolicy FirstOrDefault(string id); 294 | 295 | void Remove(string id); 296 | 297 | void Save(string id, ThrottlePolicy policy); 298 | } 299 | ``` 300 | 301 | ### Update rate limits at runtime 302 | 303 | In order to update the policy object at runtime you'll need to use the new ThrottlingHandler constructor along with ThrottleManager.UpdatePolicy function introduced in WebApiThrottle v1.2. 304 | 305 | Register the ThrottlingHandler providing PolicyCacheRepository in the constructor, if you are self-hosting the service with Owin then use PolicyMemoryCacheRepository: 306 | 307 | ``` cs 308 | public static void Register(HttpConfiguration config) 309 | { 310 | //trace provider 311 | var traceWriter = new SystemDiagnosticsTraceWriter() 312 | { 313 | IsVerbose = true 314 | }; 315 | config.Services.Replace(typeof(ITraceWriter), traceWriter); 316 | config.EnableSystemDiagnosticsTracing(); 317 | 318 | //Web API throttling handler 319 | config.MessageHandlers.Add(new ThrottlingHandler( 320 | policy: new ThrottlePolicy(perMinute: 20, perHour: 30, perDay: 35, perWeek: 3000) 321 | { 322 | //scope to IPs 323 | IpThrottling = true, 324 | 325 | //scope to clients 326 | ClientThrottling = true, 327 | ClientRules = new Dictionary 328 | { 329 | { "api-client-key-1", new RateLimits { PerMinute = 60, PerHour = 600 } }, 330 | { "api-client-key-2", new RateLimits { PerDay = 5000 } } 331 | }, 332 | 333 | //scope to endpoints 334 | EndpointThrottling = true 335 | }, 336 | 337 | //replace with PolicyMemoryCacheRepository for Owin self-host 338 | policyRepository: new PolicyCacheRepository(), 339 | 340 | //replace with MemoryCacheRepository for Owin self-host 341 | repository: new CacheRepository(), 342 | 343 | logger: new TracingThrottleLogger(traceWriter))); 344 | } 345 | 346 | ``` 347 | 348 | When you want to update the policy object call the static method ThrottleManager.UpdatePolicy anywhere in you code. 349 | 350 | ``` cs 351 | public void UpdateRateLimits() 352 | { 353 | //init policy repo 354 | var policyRepository = new PolicyCacheRepository(); 355 | 356 | //get policy object from cache 357 | var policy = policyRepository.FirstOrDefault(ThrottleManager.GetPolicyKey()); 358 | 359 | //update client rate limits 360 | policy.ClientRules["api-client-key-1"] = 361 | new RateLimits { PerMinute = 80, PerHour = 800 }; 362 | 363 | //add new client rate limits 364 | policy.ClientRules.Add("api-client-key-3", 365 | new RateLimits { PerMinute = 60, PerHour = 600 }); 366 | 367 | //apply policy updates 368 | ThrottleManager.UpdatePolicy(policy, policyRepository); 369 | 370 | } 371 | ``` 372 | 373 | ### Logging throttled requests 374 | 375 | If you want to log throttled requests you'll have to implement IThrottleLogger interface and provide it to the ThrottlingHandler. 376 | 377 | ``` cs 378 | public interface IThrottleLogger 379 | { 380 | void Log(ThrottleLogEntry entry); 381 | } 382 | ``` 383 | 384 | Logging implementation example with ITraceWriter 385 | ``` cs 386 | public class TracingThrottleLogger : IThrottleLogger 387 | { 388 | private readonly ITraceWriter traceWriter; 389 | 390 | public TracingThrottleLogger(ITraceWriter traceWriter) 391 | { 392 | this.traceWriter = traceWriter; 393 | } 394 | 395 | public void Log(ThrottleLogEntry entry) 396 | { 397 | if (null != traceWriter) 398 | { 399 | traceWriter.Info(entry.Request, "WebApiThrottle", 400 | "{0} Request {1} from {2} has been throttled (blocked), quota {3}/{4} exceeded by {5}", 401 | entry.LogDate, entry.RequestId, entry.ClientIp, entry.RateLimit, entry.RateLimitPeriod, entry.TotalRequests); 402 | } 403 | } 404 | } 405 | ``` 406 | 407 | Logging usage example with SystemDiagnosticsTraceWriter and ThrottlingHandler 408 | ``` cs 409 | var traceWriter = new SystemDiagnosticsTraceWriter() 410 | { 411 | IsVerbose = true 412 | }; 413 | config.Services.Replace(typeof(ITraceWriter), traceWriter); 414 | config.EnableSystemDiagnosticsTracing(); 415 | 416 | config.MessageHandlers.Add(new ThrottlingHandler() 417 | { 418 | Policy = new ThrottlePolicy(perSecond: 1, perMinute: 30) 419 | { 420 | IpThrottling = true, 421 | ClientThrottling = true, 422 | EndpointThrottling = true 423 | }, 424 | Repository = new CacheRepository(), 425 | Logger = new TracingThrottleLogger(traceWriter) 426 | }); 427 | ``` 428 | 429 | ### Attribute-based rate limiting with ThrottlingFilter and EnableThrottlingAttribute 430 | 431 | As an alternative to the ThrottlingHandler, ThrottlingFilter does the same thing but allows custom rate limits to be specified by decorating Web API controllers and actions with EnableThrottlingAttribute. Be aware that when a request is processed, the ThrottlingHandler executes before the http controller dispatcher in the [Web API request pipeline](https://www.asp.net/media/4071077/aspnet-web-api-poster.pdf), therefore it is preferable that you always use the handler instead of the filter when you don't need the features that the ThrottlingFilter provides. 432 | 433 | Setup the filter as you would the ThrottlingHandler: 434 | 435 | ``` cs 436 | config.Filters.Add(new ThrottlingFilter() 437 | { 438 | Policy = new ThrottlePolicy(perSecond: 1, perMinute: 20, 439 | perHour: 200, perDay: 2000, perWeek: 10000) 440 | { 441 | //scope to IPs 442 | IpThrottling = true, 443 | IpRules = new Dictionary 444 | { 445 | { "::1/10", new RateLimits { PerSecond = 2 } }, 446 | { "192.168.2.1", new RateLimits { PerMinute = 30, PerHour = 30*60, PerDay = 30*60*24 } } 447 | }, 448 | //white list the "::1" IP to disable throttling on localhost 449 | IpWhitelist = new List { "127.0.0.1", "192.168.0.0/24" }, 450 | 451 | //scope to clients (if IP throttling is applied then the scope becomes a combination of IP and client key) 452 | ClientThrottling = true, 453 | ClientRules = new Dictionary 454 | { 455 | { "api-client-key-demo", new RateLimits { PerDay = 5000 } } 456 | }, 457 | //white list API keys that don’t require throttling 458 | ClientWhitelist = new List { "admin-key" }, 459 | 460 | //Endpoint rate limits will be loaded from EnableThrottling attribute 461 | EndpointThrottling = true 462 | } 463 | }); 464 | ``` 465 | 466 | Use the attributes to toggle throttling and set rate limits: 467 | 468 | ``` cs 469 | [EnableThrottling(PerSecond = 2)] 470 | public class ValuesController : ApiController 471 | { 472 | [EnableThrottling(PerSecond = 1, PerMinute = 30, PerHour = 100)] 473 | public IEnumerable Get() 474 | { 475 | return new string[] { "value1", "value2" }; 476 | } 477 | 478 | [DisableThrotting] 479 | public string Get(int id) 480 | { 481 | return "value"; 482 | } 483 | } 484 | ``` 485 | 486 | ### Rate limiting with ThrottlingMiddleware 487 | 488 | ThrottlingMiddleware is an OWIN middleware component that works the same as the ThrottlingHandler. With the ThrottlingMiddleware you can target endpoints outside of the WebAPI area, like OAuth middleware or SignalR endpoints. 489 | 490 | Self-hosted configuration example: 491 | 492 | ``` cs 493 | public class Startup 494 | { 495 | public void Configuration(IAppBuilder appBuilder) 496 | { 497 | ... 498 | 499 | //throtting middleware with policy loaded from app.config 500 | appBuilder.Use(typeof(ThrottlingMiddleware), 501 | ThrottlePolicy.FromStore(new PolicyConfigurationProvider()), 502 | new PolicyMemoryCacheRepository(), 503 | new MemoryCacheRepository(), 504 | null, 505 | null); 506 | 507 | ... 508 | } 509 | } 510 | ``` 511 | 512 | IIS hosted configuration example: 513 | 514 | ``` cs 515 | public class Startup 516 | { 517 | public void Configuration(IAppBuilder appBuilder) 518 | { 519 | ... 520 | 521 | //throtting middleware with policy loaded from web.config 522 | appBuilder.Use(typeof(ThrottlingMiddleware), 523 | ThrottlePolicy.FromStore(new PolicyConfigurationProvider()), 524 | new PolicyCacheRepository(), 525 | new CacheRepository(), 526 | null, 527 | null); 528 | 529 | ... 530 | } 531 | } 532 | ``` 533 | 534 | ### Custom ip address parsing 535 | 536 | If you need to extract client ip's from e.g. additional headers then you can plug in custom ipAddressParsers. 537 | There is an example implementation in the WebApiThrottle.Demo project - WebApiThrottle.Demo.Net.CustomIpAddressParser 538 | 539 | ``` cs 540 | config.MessageHandlers.Add(new ThrottlingHandler( 541 | policy: new ThrottlePolicy(perMinute: 20, perHour: 30, perDay: 35, perWeek: 3000) 542 | { 543 | IpThrottling = true, 544 | ///... 545 | }, 546 | policyRepository: new PolicyCacheRepository(), 547 | repository: new CacheRepository(), 548 | logger: new TracingThrottleLogger(traceWriter), 549 | ipAddressParser: new CustomIpAddressParser())); 550 | ``` 551 | 552 | ### Custom Quota Exceeded Response 553 | 554 | If you want to customize the quota exceeded response you can set the properties QuotaExceededResponseCode and QuotaExceededMessage. 555 | 556 | ``` cs 557 | config.MessageHandlers.Add(new ThrottlingHandler( 558 | policy: new ThrottlePolicy(perMinute: 20, perHour: 30, perDay: 35, perWeek: 3000) 559 | { 560 | IpThrottling = true, 561 | ///... 562 | }, 563 | repository: new CacheRepository(), 564 | QuotaExceededResponseCode = HttpStatusCode.ServiceUnavailable, 565 | QuotaExceededMessage = "Too many calls! We can only allow {0} per {1}")); 566 | ``` -------------------------------------------------------------------------------- /WebApiThrottle.StrongName/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: AssemblyTitle("WebApiThrottle.StrongName")] 9 | [assembly: AssemblyDescription("Throttling handler and filter for ASP.NET Web API, strong-named version")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("stefanprodan.com")] 12 | [assembly: AssemblyProduct("WebApiThrottle.StrongName")] 13 | [assembly: AssemblyCopyright("Copyright © Stefan Prodan 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("fbf3012b-08ef-408c-9e7d-175abf286cb4")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /WebApiThrottle.StrongName/WebApiThrottle.StrongName.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {FBF3012B-08EF-408C-9E7D-175ABF286CB4} 8 | Library 9 | Properties 10 | WebApiThrottle.StrongName 11 | WebApiThrottle.StrongName 12 | v4.5 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | true 34 | 35 | 36 | WebApiThrottle.snk 37 | 38 | 39 | 40 | False 41 | ..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll 42 | 43 | 44 | False 45 | ..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll 46 | 47 | 48 | False 49 | ..\packages\Owin.1.0\lib\net40\Owin.dll 50 | 51 | 52 | 53 | 54 | 55 | 56 | False 57 | ..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll 58 | 59 | 60 | 61 | 62 | 63 | 64 | False 65 | ..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 88 | -------------------------------------------------------------------------------- /WebApiThrottle.StrongName/WebApiThrottle.StrongName.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $version$ 5 | Stefan Prodan 6 | Stefan Prodan 7 | WebApiThrottle.StrongName 8 | ASP.NET Web API Rate Limiter Strong-Named 9 | false 10 | WebApiThrottle message handler and action filter are designed for controlling the rate of requests that clients can make to an API based on IP address, client API key and request route. WebApiThrottle works with ASP.NET Web API hosted is IIS, Owin with IIS and Owin self-hosted. 11 | If you are looking for the ASP.NET Core version please use AspNetCoreRateLimit package 12 | Stefan Prodan 2013-2016 13 | ASP.NET Web API, Owin, throttling, rate limits 14 | https://github.com/stefanprodan/WebApiThrottle 15 | en-US 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /WebApiThrottle.StrongName/WebApiThrottle.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanprodan/WebApiThrottle/a95aa352e4101f7968af812a3fb70aeaa57f38cd/WebApiThrottle.StrongName/WebApiThrottle.snk -------------------------------------------------------------------------------- /WebApiThrottle.StrongName/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /WebApiThrottle.Tests/IpAddressUtilTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using WebApiThrottle.Net; 7 | using Xunit; 8 | 9 | namespace WebApiThrottle.Tests 10 | { 11 | public class IpAddressUtilTests 12 | { 13 | [Fact] 14 | public void IsPrivateIpAddress_PrivateAddress_ReturnsTrue() 15 | { 16 | bool result = IpAddressUtil.IsPrivateIpAddress("10.0.0.1"); 17 | 18 | Assert.Equal(true, result); 19 | } 20 | 21 | [Fact] 22 | public void IsPrivateIpAddress_PublicAddress_ReturnsFalse() 23 | { 24 | bool result = IpAddressUtil.IsPrivateIpAddress("8.8.8.8"); 25 | 26 | Assert.Equal(false, result); 27 | } 28 | 29 | [Fact] 30 | public void IsPrivateIpAddress_PublicAddressIpv6_ReturnsFalse() 31 | { 32 | bool result = IpAddressUtil.IsPrivateIpAddress("2001:4860:4860::8888"); 33 | 34 | Assert.Equal(false, result); 35 | } 36 | 37 | [Fact] 38 | public void IsPrivateIpAddress_PrivateAddressIpv6_ReturnsFalse() 39 | { 40 | bool result = IpAddressUtil.IsPrivateIpAddress("fd74:20cf:81a2::"); 41 | 42 | Assert.Equal(true, result); 43 | } 44 | 45 | [Fact] 46 | public void IsPrivateIpAddress_PrivateAddressWithPort_ReturnsTrue() 47 | { 48 | bool result = IpAddressUtil.IsPrivateIpAddress("10.0.0.1:5555"); 49 | 50 | Assert.Equal(true, result); 51 | } 52 | 53 | [Fact] 54 | public void IsPrivateIpAddress_PrivateAddressIpv6WithPort_ReturnsTrue() 55 | { 56 | bool result = IpAddressUtil.IsPrivateIpAddress("[fd74:20cf:81a2::]:5555"); 57 | 58 | Assert.Equal(true, result); 59 | } 60 | 61 | [Fact] 62 | public void IsPrivateIpAddress_PublicIpAddressWithInitialSpace_ReturnsFalse() 63 | { 64 | bool result = IpAddressUtil.IsPrivateIpAddress(" 8.8.8.8"); 65 | 66 | Assert.Equal(false, result); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /WebApiThrottle.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("WebApiThrottle.Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("WebApiThrottle.Tests")] 13 | [assembly: AssemblyCopyright("Copyright © 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("a2db243c-dfd4-4e22-9753-ed95cf6fc21e")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /WebApiThrottle.Tests/WebApiThrottle.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {A2DB243C-DFD4-4E22-9753-ED95CF6FC21E} 8 | Library 9 | Properties 10 | WebApiThrottle.Tests 11 | WebApiThrottle.Tests 12 | v4.5.2 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | ..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll 43 | True 44 | 45 | 46 | ..\packages\xunit.assert.2.1.0\lib\dotnet\xunit.assert.dll 47 | True 48 | 49 | 50 | ..\packages\xunit.extensibility.core.2.1.0\lib\dotnet\xunit.core.dll 51 | True 52 | 53 | 54 | ..\packages\xunit.extensibility.execution.2.1.0\lib\net45\xunit.execution.desktop.dll 55 | True 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | {FBF3012B-08EF-408C-9E7D-175ABF286CB4} 68 | WebApiThrottle.StrongName 69 | 70 | 71 | 72 | 79 | -------------------------------------------------------------------------------- /WebApiThrottle.Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /WebApiThrottle.WebApiDemo/App_Start/WebApiConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web.Http; 5 | using System.Web.Http.Tracing; 6 | using WebApiThrottle.WebApiDemo.Net; 7 | 8 | namespace WebApiThrottle.WebApiDemo 9 | { 10 | public static class WebApiConfig 11 | { 12 | public static void Register(HttpConfiguration config) 13 | { 14 | // Web API routes 15 | config.MapHttpAttributeRoutes(); 16 | 17 | config.Routes.MapHttpRoute( 18 | name: "DefaultApi", 19 | routeTemplate: "api/{controller}/{id}", 20 | defaults: new { id = RouteParameter.Optional } 21 | ); 22 | 23 | //trace provider 24 | var traceWriter = new SystemDiagnosticsTraceWriter() 25 | { 26 | IsVerbose = true 27 | }; 28 | config.Services.Replace(typeof(ITraceWriter), traceWriter); 29 | config.EnableSystemDiagnosticsTracing(); 30 | 31 | //Web API throttling handler 32 | config.MessageHandlers.Add(new ThrottlingHandler( 33 | policy: new ThrottlePolicy(perMinute: 20, perHour: 30, perDay: 35, perWeek: 3000) 34 | { 35 | //scope to IPs 36 | IpThrottling = true, 37 | IpRules = new Dictionary 38 | { 39 | { "::1/10", new RateLimits { PerSecond = 2 } }, 40 | { "192.168.2.1", new RateLimits { PerMinute = 30, PerHour = 30*60, PerDay = 30*60*24 } } 41 | }, 42 | //white list the "::1" IP to disable throttling on localhost for Win8 43 | IpWhitelist = new List { "127.0.0.1", "192.168.0.0/24" }, 44 | 45 | //scope to clients (if IP throttling is applied then the scope becomes a combination of IP and client key) 46 | ClientThrottling = true, 47 | ClientRules = new Dictionary 48 | { 49 | { "api-client-key-1", new RateLimits { PerMinute = 60, PerHour = 600 } }, 50 | { "api-client-key-9", new RateLimits { PerDay = 5000 } } 51 | }, 52 | //white list API keys that don’t require throttling 53 | ClientWhitelist = new List { "admin-key" }, 54 | 55 | //scope to endpoints 56 | EndpointThrottling = true, 57 | EndpointRules = new Dictionary 58 | { 59 | { "api/search", new RateLimits { PerSecond = 10, PerMinute = 100, PerHour = 1000 } } 60 | } 61 | }, 62 | policyRepository: new PolicyCacheRepository(), 63 | repository: new CacheRepository(), 64 | logger: new TracingThrottleLogger(traceWriter), 65 | ipAddressParser: new CustomIpAddressParser())); 66 | 67 | //Web API throttling handler load policy from web.config 68 | //config.MessageHandlers.Add(new ThrottlingHandler( 69 | // policy: ThrottlePolicy.FromStore(new PolicyConfigurationProvider()), 70 | // policyRepository: new PolicyCacheRepository(), 71 | // repository: new CacheRepository(), 72 | // logger: new TracingThrottleLogger(traceWriter))); 73 | 74 | //Web API throttling filter 75 | //config.Filters.Add(new ThrottlingFilter( 76 | // policy: new ThrottlePolicy(perMinute: 20, perHour: 30, perDay: 35, perWeek: 3000) 77 | // { 78 | // //scope to IPs 79 | // IpThrottling = true, 80 | // IpRules = new Dictionary 81 | // { 82 | // { "::1/10", new RateLimits { PerSecond = 2 } }, 83 | // { "192.168.2.1", new RateLimits { PerMinute = 30, PerHour = 30*60, PerDay = 30*60*24 } } 84 | // }, 85 | // //white list the "::1" IP to disable throttling on localhost for Win8 86 | // IpWhitelist = new List { "127.0.0.1", "192.168.0.0/24" }, 87 | 88 | // //scope to clients (if IP throttling is applied then the scope becomes a combination of IP and client key) 89 | // ClientThrottling = true, 90 | // ClientRules = new Dictionary 91 | // { 92 | // { "api-client-key-1", new RateLimits { PerMinute = 60, PerHour = 600 } }, 93 | // { "api-client-key-9", new RateLimits { PerDay = 5000 } } 94 | // }, 95 | // //white list API keys that don’t require throttling 96 | // ClientWhitelist = new List { "admin-key" }, 97 | 98 | // //Endpoint rate limits will be loaded from EnableThrottling attribute 99 | // EndpointThrottling = true 100 | // }, 101 | // policyRepository: new PolicyCacheRepository(), 102 | // repository: new CacheRepository(), 103 | // logger: new TracingThrottleLogger(traceWriter))); 104 | 105 | //Web API throttling filter load policy from web.config 106 | //config.Filters.Add(new ThrottlingFilter( 107 | // policy: ThrottlePolicy.FromStore(new PolicyConfigurationProvider()), 108 | // policyRepository: new PolicyCacheRepository(), 109 | // repository: new CacheRepository(), 110 | // logger: new TracingThrottleLogger(traceWriter))); 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /WebApiThrottle.WebApiDemo/Controllers/ValuesController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Net.Http; 6 | using System.Web.Http; 7 | 8 | namespace WebApiThrottle.WebApiDemo.Controllers 9 | { 10 | public class ValuesController : ApiController 11 | { 12 | [EnableThrottling(PerSecond = 2)] 13 | public IEnumerable Get() 14 | { 15 | return new string[] { "value1", "value2" }; 16 | } 17 | 18 | [DisableThrotting] 19 | public string Get(int id) 20 | { 21 | return "value"; 22 | } 23 | 24 | /// 25 | /// Policy runtime update example 26 | /// 27 | [NonAction] 28 | public void UpdateRateLimits() 29 | { 30 | //init policy repo 31 | var policyRepository = new PolicyCacheRepository(); 32 | 33 | //get policy object from cache 34 | var policy = policyRepository.FirstOrDefault(ThrottleManager.GetPolicyKey()); 35 | 36 | //update client rate limits 37 | policy.ClientRules["api-client-key-1"] = 38 | new RateLimits { PerMinute = 50, PerHour = 500 }; 39 | 40 | //add new client rate limits 41 | policy.ClientRules.Add("api-client-key-3", 42 | new RateLimits { PerMinute = 60, PerHour = 600 }); 43 | 44 | //apply policy updates 45 | ThrottleManager.UpdatePolicy(policy, policyRepository); 46 | 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /WebApiThrottle.WebApiDemo/Global.asax: -------------------------------------------------------------------------------- 1 | <%@ Application Codebehind="Global.asax.cs" Inherits="WebApiThrottle.WebApiDemo.WebApiApplication" Language="C#" %> 2 | -------------------------------------------------------------------------------- /WebApiThrottle.WebApiDemo/Global.asax.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web; 5 | using System.Web.Http; 6 | using System.Web.Routing; 7 | 8 | namespace WebApiThrottle.WebApiDemo 9 | { 10 | public class WebApiApplication : System.Web.HttpApplication 11 | { 12 | protected void Application_Start() 13 | { 14 | GlobalConfiguration.Configure(WebApiConfig.Register); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /WebApiThrottle.WebApiDemo/Helpers/CustomThrottleLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Web; 6 | 7 | namespace WebApiThrottle.WebApiDemo.Helpers 8 | { 9 | public class CustomThrottleLogger : IThrottleLogger 10 | { 11 | public void Log(ThrottleLogEntry entry) 12 | { 13 | Debug.WriteLine("{0} Request {1} has been blocked, quota {2}/{3} exceeded by {4}", 14 | entry.LogDate, entry.RequestId, entry.RateLimit, entry.RateLimitPeriod, entry.TotalRequests); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /WebApiThrottle.WebApiDemo/Helpers/CustomThrottlingFilter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Net.Http; 6 | 7 | namespace WebApiThrottle.WebApiDemo.Helpers 8 | { 9 | public class CustomThrottlingFilter : ThrottlingFilter 10 | { 11 | public CustomThrottlingFilter(ThrottlePolicy policy, IPolicyRepository policyRepository, IThrottleRepository repository, IThrottleLogger logger) 12 | : base(policy, policyRepository, repository, logger) 13 | { 14 | this.QuotaExceededMessage = "API calls quota exceeded! maximum admitted {0} per {1}."; 15 | } 16 | 17 | protected override RequestIdentity SetIdentity(HttpRequestMessage request) 18 | { 19 | return new RequestIdentity() 20 | { 21 | ClientKey = request.Headers.Contains("Authorization-Key") ? request.Headers.GetValues("Authorization-Key").First() : "anon", 22 | ClientIp = base.GetClientIp(request).ToString(), 23 | Endpoint = request.RequestUri.AbsolutePath.ToLowerInvariant() 24 | }; 25 | } 26 | 27 | protected override HttpResponseMessage QuotaExceededResponse(HttpRequestMessage request, object content, HttpStatusCode responseCode, string retryAfter) 28 | { 29 | var response = request.CreateResponse(responseCode, request); 30 | response.Headers.Add("Retry-After", new string[] { retryAfter }); 31 | return response; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /WebApiThrottle.WebApiDemo/Helpers/CustomThrottlingHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web; 5 | 6 | namespace WebApiThrottle.WebApiDemo.Helpers 7 | { 8 | public class CustomThrottlingHandler : ThrottlingHandler 9 | { 10 | protected override RequestIdentity SetIdentity(System.Net.Http.HttpRequestMessage request) 11 | { 12 | return new RequestIdentity() 13 | { 14 | ClientKey = request.Headers.Contains("Authorization-Key") ? request.Headers.GetValues("Authorization-Key").First() : "anon", 15 | ClientIp = base.GetClientIp(request).ToString(), 16 | Endpoint = request.RequestUri.AbsolutePath.ToLowerInvariant() 17 | }; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /WebApiThrottle.WebApiDemo/Net/CustomIpAddressParser.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Net; 4 | using System.Net.Http; 5 | using WebApiThrottle.Net; 6 | 7 | namespace WebApiThrottle.WebApiDemo.Net 8 | { 9 | public class CustomIpAddressParser : DefaultIpAddressParser 10 | { 11 | public override IPAddress GetClientIp(HttpRequestMessage request) 12 | { 13 | const string customHeaderName = "true-client-ip"; 14 | 15 | if (request.Headers.Contains(customHeaderName)) 16 | { 17 | IEnumerable headerValues; 18 | 19 | if (request.Headers.TryGetValues(customHeaderName, out headerValues)) 20 | { 21 | if (headerValues.Any()) 22 | { 23 | return ParseIp(headerValues.FirstOrDefault().Trim()); 24 | } 25 | } 26 | } 27 | 28 | return base.GetClientIp(request); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /WebApiThrottle.WebApiDemo/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: AssemblyTitle("WebApiThrottle.WebApiDemo")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("WebApiThrottle.WebApiDemo")] 13 | [assembly: AssemblyCopyright("Copyright © 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("62251ac4-f48a-442b-b1b3-0164d97661ee")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Revision and Build Numbers 33 | // by using the '*' as shown below: 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /WebApiThrottle.WebApiDemo/Web.Debug.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 17 | 18 | 29 | 30 | -------------------------------------------------------------------------------- /WebApiThrottle.WebApiDemo/Web.Release.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 17 | 18 | 19 | 30 | 31 | -------------------------------------------------------------------------------- /WebApiThrottle.WebApiDemo/Web.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /WebApiThrottle.WebApiDemo/WebApiThrottle.WebApiDemo.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | Debug 8 | AnyCPU 9 | 10 | 11 | 2.0 12 | {62251AC4-F48A-442B-B1B3-0164D97661EE} 13 | {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} 14 | Library 15 | Properties 16 | WebApiThrottle.WebApiDemo 17 | WebApiThrottle.WebApiDemo 18 | v4.6 19 | true 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | true 30 | full 31 | false 32 | bin\ 33 | DEBUG;TRACE 34 | prompt 35 | 4 36 | 37 | 38 | pdbonly 39 | true 40 | bin\ 41 | TRACE 42 | prompt 43 | 4 44 | 45 | 46 | 47 | ..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.1.0.2\lib\net45\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll 48 | True 49 | 50 | 51 | 52 | ..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll 53 | True 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | ..\packages\Microsoft.AspNet.WebApi.Tracing.5.2.3\lib\net45\System.Web.Http.Tracing.dll 67 | True 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | ..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll 80 | 81 | 82 | ..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll 83 | 84 | 85 | ..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.3\lib\net45\System.Web.Http.WebHost.dll 86 | 87 | 88 | 89 | 90 | 91 | Designer 92 | 93 | 94 | 95 | 96 | 97 | 98 | Global.asax 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | Web.config 110 | 111 | 112 | Web.config 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | {f049811f-bc05-4cee-b329-ce3bf2e2e4be} 121 | WebApiThrottle 122 | 123 | 124 | 125 | 10.0 126 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | True 136 | True 137 | 1853 138 | / 139 | http://localhost:1853/ 140 | False 141 | False 142 | 143 | 144 | False 145 | 146 | 147 | 148 | 149 | 150 | 151 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 152 | 153 | 154 | 155 | 156 | 163 | -------------------------------------------------------------------------------- /WebApiThrottle.WebApiDemo/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /WebApiThrottle.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApiThrottle", "WebApiThrottle\WebApiThrottle.csproj", "{F049811F-BC05-4CEE-B329-CE3BF2E2E4BE}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApiThrottler.SelfHostOwinDemo", "WebApiThrottler.SelfHostOwinDemo\WebApiThrottler.SelfHostOwinDemo.csproj", "{22B91BA0-EFC9-4AF8-A24B-CF1CB476A50D}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Readme", "Readme", "{D4FF492F-0AB9-4F78-9F9E-99ADE0D8C3AC}" 11 | ProjectSection(SolutionItems) = preProject 12 | LICENSE.md = LICENSE.md 13 | README.md = README.md 14 | EndProjectSection 15 | EndProject 16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApiThrottle.WebApiDemo", "WebApiThrottle.WebApiDemo\WebApiThrottle.WebApiDemo.csproj", "{62251AC4-F48A-442B-B1B3-0164D97661EE}" 17 | EndProject 18 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApiThrottle.StrongName", "WebApiThrottle.StrongName\WebApiThrottle.StrongName.csproj", "{FBF3012B-08EF-408C-9E7D-175ABF286CB4}" 19 | EndProject 20 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApiThrottle.Tests", "WebApiThrottle.Tests\WebApiThrottle.Tests.csproj", "{A2DB243C-DFD4-4E22-9753-ED95CF6FC21E}" 21 | EndProject 22 | Global 23 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 24 | Debug|Any CPU = Debug|Any CPU 25 | Release|Any CPU = Release|Any CPU 26 | EndGlobalSection 27 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 28 | {F049811F-BC05-4CEE-B329-CE3BF2E2E4BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {F049811F-BC05-4CEE-B329-CE3BF2E2E4BE}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {F049811F-BC05-4CEE-B329-CE3BF2E2E4BE}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {F049811F-BC05-4CEE-B329-CE3BF2E2E4BE}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {22B91BA0-EFC9-4AF8-A24B-CF1CB476A50D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {22B91BA0-EFC9-4AF8-A24B-CF1CB476A50D}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {22B91BA0-EFC9-4AF8-A24B-CF1CB476A50D}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {22B91BA0-EFC9-4AF8-A24B-CF1CB476A50D}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {62251AC4-F48A-442B-B1B3-0164D97661EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {62251AC4-F48A-442B-B1B3-0164D97661EE}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {62251AC4-F48A-442B-B1B3-0164D97661EE}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {62251AC4-F48A-442B-B1B3-0164D97661EE}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {FBF3012B-08EF-408C-9E7D-175ABF286CB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {FBF3012B-08EF-408C-9E7D-175ABF286CB4}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {FBF3012B-08EF-408C-9E7D-175ABF286CB4}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {FBF3012B-08EF-408C-9E7D-175ABF286CB4}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {A2DB243C-DFD4-4E22-9753-ED95CF6FC21E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {A2DB243C-DFD4-4E22-9753-ED95CF6FC21E}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {A2DB243C-DFD4-4E22-9753-ED95CF6FC21E}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {A2DB243C-DFD4-4E22-9753-ED95CF6FC21E}.Release|Any CPU.Build.0 = Release|Any CPU 48 | EndGlobalSection 49 | GlobalSection(SolutionProperties) = preSolution 50 | HideSolutionNode = FALSE 51 | EndGlobalSection 52 | EndGlobal 53 | -------------------------------------------------------------------------------- /WebApiThrottle/Attributes/DisableThrottingAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Web.Http.Filters; 7 | 8 | namespace WebApiThrottle 9 | { 10 | public class DisableThrottingAttribute : ActionFilterAttribute, IActionFilter 11 | { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /WebApiThrottle/Attributes/EnableThrottlingAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Web.Http.Filters; 7 | 8 | namespace WebApiThrottle 9 | { 10 | public class EnableThrottlingAttribute : ActionFilterAttribute, IActionFilter 11 | { 12 | public long PerSecond { get; set; } 13 | 14 | public long PerMinute { get; set; } 15 | 16 | public long PerHour { get; set; } 17 | 18 | public long PerDay { get; set; } 19 | 20 | public long PerWeek { get; set; } 21 | 22 | public virtual long GetLimit(RateLimitPeriod period) 23 | { 24 | switch (period) 25 | { 26 | case RateLimitPeriod.Second: 27 | return PerSecond; 28 | case RateLimitPeriod.Minute: 29 | return PerMinute; 30 | case RateLimitPeriod.Hour: 31 | return PerHour; 32 | case RateLimitPeriod.Day: 33 | return PerDay; 34 | case RateLimitPeriod.Week: 35 | return PerWeek; 36 | default: 37 | return PerSecond; 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /WebApiThrottle/Configuration/ThrottlePolicyConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace WebApiThrottle 9 | { 10 | public class ThrottlePolicyConfiguration : ConfigurationSection 11 | { 12 | [ConfigurationProperty("limitPerSecond", DefaultValue = "0", IsRequired = false)] 13 | [LongValidator(ExcludeRange = false, MinValue = 0)] 14 | public long LimitPerSecond 15 | { 16 | get 17 | { 18 | return (long)this["limitPerSecond"]; 19 | } 20 | } 21 | 22 | [ConfigurationProperty("limitPerMinute", DefaultValue = "0", IsRequired = false)] 23 | [LongValidator(ExcludeRange = false, MinValue = 0)] 24 | public long LimitPerMinute 25 | { 26 | get 27 | { 28 | return (long)this["limitPerMinute"]; 29 | } 30 | } 31 | 32 | [ConfigurationProperty("limitPerHour", DefaultValue = "0", IsRequired = false)] 33 | [LongValidator(ExcludeRange = false, MinValue = 0)] 34 | public long LimitPerHour 35 | { 36 | get 37 | { 38 | return (long)this["limitPerHour"]; 39 | } 40 | } 41 | 42 | [ConfigurationProperty("limitPerDay", DefaultValue = "0", IsRequired = false)] 43 | [LongValidator(ExcludeRange = false, MinValue = 0)] 44 | public long LimitPerDay 45 | { 46 | get 47 | { 48 | return (long)this["limitPerDay"]; 49 | } 50 | } 51 | 52 | [ConfigurationProperty("limitPerWeek", DefaultValue = "0", IsRequired = false)] 53 | [LongValidator(ExcludeRange = false, MinValue = 0)] 54 | public long LimitPerWeek 55 | { 56 | get 57 | { 58 | return (long)this["limitPerWeek"]; 59 | } 60 | } 61 | 62 | [ConfigurationProperty("ipThrottling", DefaultValue = "false", IsRequired = false)] 63 | public bool IpThrottling 64 | { 65 | get 66 | { 67 | return (bool)this["ipThrottling"]; 68 | } 69 | } 70 | 71 | [ConfigurationProperty("clientThrottling", DefaultValue = "false", IsRequired = false)] 72 | public bool ClientThrottling 73 | { 74 | get 75 | { 76 | return (bool)this["clientThrottling"]; 77 | } 78 | } 79 | 80 | [ConfigurationProperty("endpointThrottling", DefaultValue = "false", IsRequired = false)] 81 | public bool EndpointThrottling 82 | { 83 | get 84 | { 85 | return (bool)this["endpointThrottling"]; 86 | } 87 | } 88 | 89 | [ConfigurationProperty("stackBlockedRequests", DefaultValue = "false", IsRequired = false)] 90 | public bool StackBlockedRequests 91 | { 92 | get 93 | { 94 | return (bool)this["stackBlockedRequests"]; 95 | } 96 | } 97 | 98 | [ConfigurationProperty("rules")] 99 | public ThrottlePolicyRuleConfigurationCollection Rules 100 | { 101 | get 102 | { 103 | return this["rules"] as ThrottlePolicyRuleConfigurationCollection; 104 | } 105 | } 106 | 107 | [ConfigurationProperty("whitelists")] 108 | public ThrottlePolicyWhitelistConfigurationCollection Whitelists 109 | { 110 | get 111 | { 112 | return this["whitelists"] as ThrottlePolicyWhitelistConfigurationCollection; 113 | } 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /WebApiThrottle/Configuration/ThrottlePolicyRuleConfigurationCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace WebApiThrottle 9 | { 10 | public class ThrottlePolicyRuleConfigurationCollection : ConfigurationElementCollection 11 | { 12 | 13 | protected override ConfigurationElement CreateNewElement() 14 | { 15 | return new ThrottlePolicyRuleConfigurationElement(); 16 | } 17 | 18 | protected override object GetElementKey(ConfigurationElement element) 19 | { 20 | return ((ThrottlePolicyRuleConfigurationElement)element).Entry; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /WebApiThrottle/Configuration/ThrottlePolicyRuleConfigurationElement.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace WebApiThrottle 9 | { 10 | public class ThrottlePolicyRuleConfigurationElement : ConfigurationElement 11 | { 12 | [ConfigurationProperty("limitPerSecond", DefaultValue = "0", IsRequired = false)] 13 | [LongValidator(ExcludeRange = false, MinValue = 0)] 14 | public long LimitPerSecond 15 | { 16 | get 17 | { 18 | return (long)this["limitPerSecond"]; 19 | } 20 | } 21 | 22 | [ConfigurationProperty("limitPerMinute", DefaultValue = "0", IsRequired = false)] 23 | [LongValidator(ExcludeRange = false, MinValue = 0)] 24 | public long LimitPerMinute 25 | { 26 | get 27 | { 28 | return (long)this["limitPerMinute"]; 29 | } 30 | } 31 | 32 | [ConfigurationProperty("limitPerHour", DefaultValue = "0", IsRequired = false)] 33 | [LongValidator(ExcludeRange = false, MinValue = 0)] 34 | public long LimitPerHour 35 | { 36 | get 37 | { 38 | return (long)this["limitPerHour"]; 39 | } 40 | } 41 | 42 | [ConfigurationProperty("limitPerDay", DefaultValue = "0", IsRequired = false)] 43 | [LongValidator(ExcludeRange = false, MinValue = 0)] 44 | public long LimitPerDay 45 | { 46 | get 47 | { 48 | return (long)this["limitPerDay"]; 49 | } 50 | } 51 | 52 | [ConfigurationProperty("limitPerWeek", DefaultValue = "0", IsRequired = false)] 53 | [LongValidator(ExcludeRange = false, MinValue = 0)] 54 | public long LimitPerWeek 55 | { 56 | get 57 | { 58 | return (long)this["limitPerWeek"]; 59 | } 60 | } 61 | 62 | [ConfigurationProperty("entry", IsRequired = true)] 63 | public string Entry 64 | { 65 | get 66 | { 67 | return this["entry"] as string; 68 | } 69 | } 70 | 71 | [ConfigurationProperty("policyType", IsRequired = true)] 72 | public int PolicyType 73 | { 74 | get 75 | { 76 | return (int)this["policyType"]; 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /WebApiThrottle/Configuration/ThrottlePolicyWhitelistConfigurationCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace WebApiThrottle 9 | { 10 | public class ThrottlePolicyWhitelistConfigurationCollection : ConfigurationElementCollection 11 | { 12 | protected override ConfigurationElement CreateNewElement() 13 | { 14 | return new ThrottlePolicyWhitelistConfigurationElement(); 15 | } 16 | 17 | protected override object GetElementKey(ConfigurationElement element) 18 | { 19 | return ((ThrottlePolicyWhitelistConfigurationElement)element).Entry; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /WebApiThrottle/Configuration/ThrottlePolicyWhitelistConfigurationElement.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace WebApiThrottle 9 | { 10 | public class ThrottlePolicyWhitelistConfigurationElement : ConfigurationElement 11 | { 12 | [ConfigurationProperty("entry", IsRequired = true)] 13 | public string Entry 14 | { 15 | get 16 | { 17 | return this["entry"] as string; 18 | } 19 | } 20 | 21 | [ConfigurationProperty("policyType", IsRequired = true)] 22 | public int PolicyType 23 | { 24 | get 25 | { 26 | return (int)this["policyType"]; 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /WebApiThrottle/Logging/IThrottleLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace WebApiThrottle 8 | { 9 | /// 10 | /// Log requests that exceed the limit 11 | /// 12 | public interface IThrottleLogger 13 | { 14 | void Log(ThrottleLogEntry entry); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /WebApiThrottle/Logging/ThrottleLogEntry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net.Http; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace WebApiThrottle 9 | { 10 | [Serializable] 11 | public class ThrottleLogEntry 12 | { 13 | public string RequestId { get; set; } 14 | 15 | public string ClientIp { get; set; } 16 | 17 | public string ClientKey { get; set; } 18 | 19 | public string Endpoint { get; set; } 20 | 21 | public long TotalRequests { get; set; } 22 | 23 | public DateTime StartPeriod { get; set; } 24 | 25 | public long RateLimit { get; set; } 26 | 27 | public string RateLimitPeriod { get; set; } 28 | 29 | public DateTime LogDate { get; set; } 30 | 31 | public HttpRequestMessage Request { get; set; } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /WebApiThrottle/Logging/TracingThrottleLogger.cs: -------------------------------------------------------------------------------- 1 | using System.Web.Http.Tracing; 2 | 3 | namespace WebApiThrottle 4 | { 5 | public class TracingThrottleLogger : IThrottleLogger 6 | { 7 | private readonly ITraceWriter traceWriter; 8 | 9 | public TracingThrottleLogger(ITraceWriter traceWriter) 10 | { 11 | this.traceWriter = traceWriter; 12 | } 13 | 14 | public void Log(ThrottleLogEntry entry) 15 | { 16 | if (null != traceWriter) 17 | { 18 | traceWriter.Info( 19 | entry.Request, 20 | "WebApiThrottle", 21 | "{0} Request {1} from {2} has been throttled (blocked), quota {3}/{4} exceeded by {5}", 22 | entry.LogDate, 23 | entry.RequestId, 24 | entry.ClientIp, 25 | entry.RateLimit, 26 | entry.RateLimitPeriod, 27 | entry.TotalRequests); 28 | } 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /WebApiThrottle/Models/IPAddressRange.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Runtime.Serialization; 6 | using System.Text; 7 | using System.Text.RegularExpressions; 8 | using System.Threading.Tasks; 9 | 10 | namespace WebApiThrottle 11 | { 12 | /// 13 | /// IP v4 and v6 range helper by jsakamoto 14 | /// Fork from https://github.com/jsakamoto/ipaddressrange 15 | /// 16 | /// 17 | /// "192.168.0.0/24" 18 | /// "fe80::/10" 19 | /// "192.168.0.0/255.255.255.0" 20 | /// "192.168.0.0-192.168.0.255" 21 | /// 22 | [Serializable] 23 | public class IPAddressRange : ISerializable 24 | { 25 | public IPAddress Begin { get; set; } 26 | 27 | public IPAddress End { get; set; } 28 | 29 | public IPAddressRange() 30 | { 31 | this.Begin = new IPAddress(0L); 32 | this.End = new IPAddress(0L); 33 | } 34 | 35 | public IPAddressRange(string ipRangeString) 36 | { 37 | // remove all spaces. 38 | ipRangeString = ipRangeString.Replace(" ", ""); 39 | 40 | // Pattern 1. CIDR range: "192.168.0.0/24", "fe80::/10" 41 | var m1 = Regex.Match(ipRangeString, @"^(?[\da-f\.:]+)/(?\d+)$", RegexOptions.IgnoreCase); 42 | if (m1.Success) 43 | { 44 | var baseAdrBytes = IPAddress.Parse(m1.Groups["adr"].Value).GetAddressBytes(); 45 | var maskBytes = Bits.GetBitMask(baseAdrBytes.Length, int.Parse(m1.Groups["maskLen"].Value)); 46 | baseAdrBytes = Bits.And(baseAdrBytes, maskBytes); 47 | this.Begin = new IPAddress(baseAdrBytes); 48 | this.End = new IPAddress(Bits.Or(baseAdrBytes, Bits.Not(maskBytes))); 49 | return; 50 | } 51 | 52 | // Pattern 2. Uni address: "127.0.0.1", ":;1" 53 | var m2 = Regex.Match(ipRangeString, @"^(?[\da-f\.:]+)$", RegexOptions.IgnoreCase); 54 | if (m2.Success) 55 | { 56 | this.Begin = this.End = IPAddress.Parse(ipRangeString); 57 | return; 58 | } 59 | 60 | // Pattern 3. Begin end range: "169.258.0.0-169.258.0.255" 61 | var m3 = Regex.Match(ipRangeString, @"^(?[\da-f\.:]+)-(?[\da-f\.:]+)$", RegexOptions.IgnoreCase); 62 | if (m3.Success) 63 | { 64 | this.Begin = IPAddress.Parse(m3.Groups["begin"].Value); 65 | this.End = IPAddress.Parse(m3.Groups["end"].Value); 66 | return; 67 | } 68 | 69 | // Pattern 4. Bit mask range: "192.168.0.0/255.255.255.0" 70 | var m4 = Regex.Match(ipRangeString, @"^(?[\da-f\.:]+)/(?[\da-f\.:]+)$", RegexOptions.IgnoreCase); 71 | if (m4.Success) 72 | { 73 | var baseAdrBytes = IPAddress.Parse(m4.Groups["adr"].Value).GetAddressBytes(); 74 | var maskBytes = IPAddress.Parse(m4.Groups["bitmask"].Value).GetAddressBytes(); 75 | baseAdrBytes = Bits.And(baseAdrBytes, maskBytes); 76 | this.Begin = new IPAddress(baseAdrBytes); 77 | this.End = new IPAddress(Bits.Or(baseAdrBytes, Bits.Not(maskBytes))); 78 | return; 79 | } 80 | 81 | throw new FormatException("Unknown IP range string."); 82 | } 83 | 84 | protected IPAddressRange(SerializationInfo info, StreamingContext context) 85 | { 86 | var names = new List(); 87 | foreach (var item in info) names.Add(item.Name); 88 | 89 | Func deserialize = (name) => names.Contains(name) ? 90 | IPAddress.Parse(info.GetValue(name, typeof(object)).ToString()) : 91 | new IPAddress(0L); 92 | 93 | this.Begin = deserialize("Begin"); 94 | this.End = deserialize("End"); 95 | } 96 | 97 | public bool Contains(IPAddress ipaddress) 98 | { 99 | if (ipaddress.AddressFamily != this.Begin.AddressFamily) return false; 100 | var adrBytes = ipaddress.GetAddressBytes(); 101 | return Bits.GE(this.Begin.GetAddressBytes(), adrBytes) && Bits.LE(this.End.GetAddressBytes(), adrBytes); 102 | } 103 | 104 | public void GetObjectData(SerializationInfo info, StreamingContext context) 105 | { 106 | info.AddValue("Begin", this.Begin != null ? this.Begin.ToString() : ""); 107 | info.AddValue("End", this.End != null ? this.End.ToString() : ""); 108 | } 109 | } 110 | 111 | internal static class Bits 112 | { 113 | internal static byte[] Not(byte[] bytes) 114 | { 115 | return bytes.Select(b => (byte)~b).ToArray(); 116 | } 117 | 118 | internal static byte[] And(byte[] A, byte[] B) 119 | { 120 | return A.Zip(B, (a, b) => (byte)(a & b)).ToArray(); 121 | } 122 | 123 | internal static byte[] Or(byte[] A, byte[] B) 124 | { 125 | return A.Zip(B, (a, b) => (byte)(a | b)).ToArray(); 126 | } 127 | 128 | internal static bool GE(byte[] A, byte[] B) 129 | { 130 | return A.Zip(B, (a, b) => a == b ? 0 : a < b ? 1 : -1) 131 | .SkipWhile(c => c == 0) 132 | .FirstOrDefault() >= 0; 133 | } 134 | 135 | internal static bool LE(byte[] A, byte[] B) 136 | { 137 | return A.Zip(B, (a, b) => a == b ? 0 : a < b ? 1 : -1) 138 | .SkipWhile(c => c == 0) 139 | .FirstOrDefault() <= 0; 140 | } 141 | 142 | internal static byte[] GetBitMask(int sizeOfBuff, int bitLen) 143 | { 144 | var maskBytes = new byte[sizeOfBuff]; 145 | var bytesLen = bitLen / 8; 146 | var bitsLen = bitLen % 8; 147 | for (int i = 0; i < bytesLen; i++) 148 | { 149 | maskBytes[i] = 0xff; 150 | } 151 | if (bitsLen > 0) maskBytes[bytesLen] = (byte)~Enumerable.Range(1, 8 - bitsLen).Select(n => 1 << n - 1).Aggregate((a, b) => a | b); 152 | return maskBytes; 153 | } 154 | 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /WebApiThrottle/Models/RateLimitPeriod.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace WebApiThrottle 8 | { 9 | public enum RateLimitPeriod 10 | { 11 | Second = 1, 12 | Minute, 13 | Hour, 14 | Day, 15 | Week 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /WebApiThrottle/Models/RateLimits.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace WebApiThrottle 8 | { 9 | [Serializable] 10 | public class RateLimits 11 | { 12 | public long PerSecond { get; set; } 13 | 14 | public long PerMinute { get; set; } 15 | 16 | public long PerHour { get; set; } 17 | 18 | public long PerDay { get; set; } 19 | 20 | public long PerWeek { get; set; } 21 | 22 | public long GetLimit(RateLimitPeriod period) 23 | { 24 | switch (period) 25 | { 26 | case RateLimitPeriod.Second: 27 | return PerSecond; 28 | case RateLimitPeriod.Minute: 29 | return PerMinute; 30 | case RateLimitPeriod.Hour: 31 | return PerHour; 32 | case RateLimitPeriod.Day: 33 | return PerDay; 34 | case RateLimitPeriod.Week: 35 | return PerWeek; 36 | default: 37 | return PerSecond; 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /WebApiThrottle/Models/RequestIdentity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace WebApiThrottle 8 | { 9 | /// 10 | /// Stores the client IP, key and endpoint 11 | /// 12 | [Serializable] 13 | public class RequestIdentity 14 | { 15 | public string ClientIp { get; set; } 16 | 17 | public string ClientKey { get; set; } 18 | 19 | public string Endpoint { get; set; } 20 | 21 | public bool ForceWhiteList { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /WebApiThrottle/Models/ThrottleCounter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace WebApiThrottle 8 | { 9 | /// 10 | /// Stores the initial access time and the numbers of calls made from that point 11 | /// 12 | [Serializable] 13 | public struct ThrottleCounter 14 | { 15 | public DateTime Timestamp { get; set; } 16 | 17 | public long TotalRequests { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /WebApiThrottle/Models/ThrottlePolicyRule.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace WebApiThrottle 8 | { 9 | [Serializable] 10 | public class ThrottlePolicyRule 11 | { 12 | public long LimitPerSecond { get; set; } 13 | 14 | public long LimitPerMinute { get; set; } 15 | 16 | public long LimitPerHour { get; set; } 17 | 18 | public long LimitPerDay { get; set; } 19 | 20 | public long LimitPerWeek { get; set; } 21 | 22 | public string Entry { get; set; } 23 | 24 | public ThrottlePolicyType PolicyType { get; set; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /WebApiThrottle/Models/ThrottlePolicySettings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace WebApiThrottle 8 | { 9 | [Serializable] 10 | public class ThrottlePolicySettings 11 | { 12 | public long LimitPerSecond { get; set; } 13 | 14 | public long LimitPerMinute { get; set; } 15 | 16 | public long LimitPerHour { get; set; } 17 | 18 | public long LimitPerDay { get; set; } 19 | 20 | public long LimitPerWeek { get; set; } 21 | 22 | public bool IpThrottling { get; set; } 23 | 24 | public bool ClientThrottling { get; set; } 25 | 26 | public bool EndpointThrottling { get; set; } 27 | 28 | public bool StackBlockedRequests { get; set; } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /WebApiThrottle/Models/ThrottlePolicyType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace WebApiThrottle 8 | { 9 | public enum ThrottlePolicyType : int 10 | { 11 | IpThrottling = 1, 12 | ClientThrottling, 13 | EndpointThrottling 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /WebApiThrottle/Models/ThrottlePolicyWhitelist.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace WebApiThrottle 8 | { 9 | [Serializable] 10 | public class ThrottlePolicyWhitelist 11 | { 12 | public string Entry { get; set; } 13 | 14 | public ThrottlePolicyType PolicyType { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /WebApiThrottle/Net/DefaultIpAddressParser.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Net; 3 | using System.Net.Http; 4 | using System.ServiceModel.Channels; 5 | using System.Web; 6 | 7 | namespace WebApiThrottle.Net 8 | { 9 | public class DefaultIpAddressParser : IIpAddressParser 10 | { 11 | public bool ContainsIp(List ipRules, string clientIp) 12 | { 13 | return IpAddressUtil.ContainsIp(ipRules, clientIp); 14 | } 15 | 16 | public bool ContainsIp(List ipRules, string clientIp, out string rule) 17 | { 18 | return IpAddressUtil.ContainsIp(ipRules, clientIp, out rule); 19 | } 20 | 21 | public virtual IPAddress GetClientIp(HttpRequestMessage request) 22 | { 23 | return ParseIp(request.GetClientIpAddress()); 24 | } 25 | 26 | public IPAddress ParseIp(string ipAddress) 27 | { 28 | return IpAddressUtil.ParseIp(ipAddress); 29 | } 30 | 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /WebApiThrottle/Net/HttpRequestExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Net.Http; 6 | using System.ServiceModel.Channels; 7 | using System.Web; 8 | 9 | namespace WebApiThrottle.Net 10 | { 11 | public static class HttpRequestExtensions 12 | { 13 | /// 14 | /// 15 | /// 16 | /// 17 | /// 18 | public static string GetClientIpAddress(this HttpRequestMessage request) 19 | { 20 | // Always return all zeroes for any failure (my calling code expects it) 21 | string ipAddress = "0.0.0.0"; 22 | 23 | if (request.Properties.ContainsKey("MS_HttpContext")) 24 | { 25 | ipAddress = ((HttpContextBase)request.Properties["MS_HttpContext"]).Request.UserHostAddress; 26 | } 27 | else if (request.Properties.ContainsKey(RemoteEndpointMessageProperty.Name)) 28 | { 29 | ipAddress = ((RemoteEndpointMessageProperty)request.Properties[RemoteEndpointMessageProperty.Name]).Address; 30 | } 31 | 32 | if (request.Properties.ContainsKey("MS_OwinContext")) 33 | { 34 | ipAddress = ((Microsoft.Owin.OwinContext) request.Properties["MS_OwinContext"]).Request.RemoteIpAddress; 35 | } 36 | 37 | // get the X-Forward-For headers (should only really be one) 38 | IEnumerable xForwardForList; 39 | if (!request.Headers.TryGetValues("X-Forwarded-For", out xForwardForList)) 40 | { 41 | return ipAddress; 42 | } 43 | 44 | var xForwardedFor = xForwardForList.FirstOrDefault(); 45 | 46 | // check that we have a value 47 | if (string.IsNullOrEmpty(xForwardedFor)) 48 | { 49 | return ipAddress; 50 | } 51 | 52 | // Get a list of public ip addresses in the X_FORWARDED_FOR variable 53 | var publicForwardingIps = xForwardedFor.Split(',').Where(ip => !IpAddressUtil.IsPrivateIpAddress(ip)).ToList(); 54 | 55 | // If we found any, return the last one, otherwise return the user host address 56 | return publicForwardingIps.Any() ? publicForwardingIps.Last() : ipAddress; 57 | 58 | } 59 | 60 | } 61 | } -------------------------------------------------------------------------------- /WebApiThrottle/Net/IIpAddressParser.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Net; 3 | using System.Net.Http; 4 | 5 | namespace WebApiThrottle.Net 6 | { 7 | public interface IIpAddressParser 8 | { 9 | bool ContainsIp(List ipRules, string clientIp); 10 | 11 | bool ContainsIp(List ipRules, string clientIp, out string rule); 12 | 13 | IPAddress GetClientIp(HttpRequestMessage request); 14 | 15 | IPAddress ParseIp(string ipAddress); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /WebApiThrottle/Net/IpAddressUtil.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Net; 6 | 7 | namespace WebApiThrottle.Net 8 | { 9 | public class IpAddressUtil 10 | { 11 | public static bool ContainsIp(List ipRules, string clientIp) 12 | { 13 | var ip = ParseIp(clientIp); 14 | if (ipRules != null && ipRules.Any()) 15 | { 16 | foreach (var rule in ipRules) 17 | { 18 | var range = new IPAddressRange(rule); 19 | if (range.Contains(ip)) 20 | { 21 | return true; 22 | } 23 | } 24 | } 25 | 26 | return false; 27 | } 28 | 29 | public static bool ContainsIp(List ipRules, string clientIp, out string rule) 30 | { 31 | rule = null; 32 | var ip = ParseIp(clientIp); 33 | if (ipRules != null && ipRules.Any()) 34 | { 35 | foreach (var r in ipRules) 36 | { 37 | var range = new IPAddressRange(r); 38 | if (range.Contains(ip)) 39 | { 40 | rule = r; 41 | return true; 42 | } 43 | } 44 | } 45 | 46 | return false; 47 | } 48 | 49 | public static IPAddress ParseIp(string ipAddress) 50 | { 51 | ipAddress = ipAddress.Trim(); 52 | int portDelimiterPos = ipAddress.LastIndexOf(":", StringComparison.InvariantCultureIgnoreCase); 53 | bool ipv6WithPortStart = ipAddress.StartsWith("["); 54 | int ipv6End = ipAddress.IndexOf("]"); 55 | if (portDelimiterPos != -1 56 | && portDelimiterPos == ipAddress.IndexOf(":", StringComparison.InvariantCultureIgnoreCase) 57 | || ipv6WithPortStart && ipv6End != -1 && ipv6End < portDelimiterPos) 58 | { 59 | ipAddress = ipAddress.Substring(0, portDelimiterPos); 60 | } 61 | 62 | return IPAddress.Parse(ipAddress); 63 | } 64 | 65 | public static bool IsPrivateIpAddress(string ipAddress) 66 | { 67 | // http://en.wikipedia.org/wiki/Private_network 68 | // Private IP Addresses are: 69 | // 24-bit block: 10.0.0.0 through 10.255.255.255 70 | // 20-bit block: 172.16.0.0 through 172.31.255.255 71 | // 16-bit block: 192.168.0.0 through 192.168.255.255 72 | // Link-local addresses: 169.254.0.0 through 169.254.255.255 (http://en.wikipedia.org/wiki/Link-local_address) 73 | 74 | var ip = ParseIp(ipAddress); 75 | var octets = ip.GetAddressBytes(); 76 | 77 | bool isIpv6 = octets.Length == 16; 78 | 79 | if (isIpv6) 80 | { 81 | bool isUniqueLocalAddress = octets[0] == 253; 82 | return isUniqueLocalAddress; 83 | } 84 | else 85 | { 86 | var is24BitBlock = octets[0] == 10; 87 | if (is24BitBlock) return true; // Return to prevent further processing 88 | 89 | var is20BitBlock = octets[0] == 172 && octets[1] >= 16 && octets[1] <= 31; 90 | if (is20BitBlock) return true; // Return to prevent further processing 91 | 92 | var is16BitBlock = octets[0] == 192 && octets[1] == 168; 93 | if (is16BitBlock) return true; // Return to prevent further processing 94 | 95 | var isLinkLocalAddress = octets[0] == 169 && octets[1] == 254; 96 | return isLinkLocalAddress; 97 | } 98 | } 99 | } 100 | } -------------------------------------------------------------------------------- /WebApiThrottle/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: AssemblyTitle("WebApiThrottle")] 9 | [assembly: AssemblyDescription("Throttling handler and filter for ASP.NET Web API v2")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("stefanprodan.com")] 12 | [assembly: AssemblyProduct("WebApiThrottle")] 13 | [assembly: AssemblyCopyright("Copyright Stefan Prodan © 2013-2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("4223068d-0305-4eaa-92ca-3507a46a52f7")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.4.0.0")] 36 | [assembly: AssemblyFileVersion("1.4.0.0")] 37 | -------------------------------------------------------------------------------- /WebApiThrottle/Providers/IThrottlePolicyProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace WebApiThrottle 8 | { 9 | /// 10 | /// Implement this interface if you want to load the policy rules from a persistent store 11 | /// 12 | public interface IThrottlePolicyProvider 13 | { 14 | ThrottlePolicySettings ReadSettings(); 15 | 16 | IEnumerable AllRules(); 17 | 18 | IEnumerable AllWhitelists(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /WebApiThrottle/Providers/PolicyConfigurationProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace WebApiThrottle 9 | { 10 | public class PolicyConfigurationProvider : IThrottlePolicyProvider 11 | { 12 | private readonly ThrottlePolicyConfiguration policyConfig; 13 | 14 | public PolicyConfigurationProvider() 15 | { 16 | this.policyConfig = ConfigurationManager.GetSection("throttlePolicy") as ThrottlePolicyConfiguration; 17 | } 18 | 19 | public ThrottlePolicySettings ReadSettings() 20 | { 21 | var settings = new ThrottlePolicySettings() 22 | { 23 | IpThrottling = policyConfig.IpThrottling, 24 | ClientThrottling = policyConfig.ClientThrottling, 25 | EndpointThrottling = policyConfig.EndpointThrottling, 26 | StackBlockedRequests = policyConfig.StackBlockedRequests, 27 | LimitPerSecond = policyConfig.LimitPerSecond, 28 | LimitPerMinute = policyConfig.LimitPerMinute, 29 | LimitPerHour = policyConfig.LimitPerHour, 30 | LimitPerDay = policyConfig.LimitPerDay, 31 | LimitPerWeek = policyConfig.LimitPerWeek 32 | }; 33 | 34 | return settings; 35 | } 36 | 37 | public IEnumerable AllRules() 38 | { 39 | var rules = new List(); 40 | if (policyConfig.Rules != null) 41 | { 42 | foreach (ThrottlePolicyRuleConfigurationElement rule in policyConfig.Rules) 43 | { 44 | rules.Add(new ThrottlePolicyRule 45 | { 46 | Entry = rule.Entry, 47 | PolicyType = (ThrottlePolicyType)rule.PolicyType, 48 | LimitPerSecond = rule.LimitPerSecond, 49 | LimitPerMinute = rule.LimitPerMinute, 50 | LimitPerHour = rule.LimitPerHour, 51 | LimitPerDay = rule.LimitPerDay, 52 | LimitPerWeek = rule.LimitPerWeek 53 | }); 54 | } 55 | } 56 | return rules; 57 | } 58 | 59 | public IEnumerable AllWhitelists() 60 | { 61 | var whitelists = new List(); 62 | if (policyConfig.Whitelists != null) 63 | { 64 | foreach (ThrottlePolicyWhitelistConfigurationElement whitelist in policyConfig.Whitelists) 65 | { 66 | whitelists.Add(new ThrottlePolicyWhitelist 67 | { 68 | Entry = whitelist.Entry, 69 | PolicyType = (ThrottlePolicyType)whitelist.PolicyType, 70 | }); 71 | } 72 | } 73 | 74 | return whitelists; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /WebApiThrottle/Repositories/CacheRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Web; 7 | using System.Web.Caching; 8 | 9 | namespace WebApiThrottle 10 | { 11 | /// 12 | /// Stores throttle metrics in asp.net cache 13 | /// 14 | public class CacheRepository : IThrottleRepository 15 | { 16 | /// 17 | /// Insert or update 18 | /// 19 | /// 20 | /// The id. 21 | /// 22 | /// 23 | /// The throttle Counter. 24 | /// 25 | /// 26 | /// The expiration Time. 27 | /// 28 | public void Save(string id, ThrottleCounter throttleCounter, TimeSpan expirationTime) 29 | { 30 | if (HttpContext.Current.Cache[id] != null) 31 | { 32 | HttpContext.Current.Cache[id] = throttleCounter; 33 | } 34 | else 35 | { 36 | HttpContext.Current.Cache.Add( 37 | id, 38 | throttleCounter, 39 | null, 40 | Cache.NoAbsoluteExpiration, 41 | expirationTime, 42 | CacheItemPriority.Low, 43 | null); 44 | } 45 | } 46 | 47 | public bool Any(string id) 48 | { 49 | return HttpContext.Current.Cache[id] != null; 50 | } 51 | 52 | public ThrottleCounter? FirstOrDefault(string id) 53 | { 54 | return (ThrottleCounter?)HttpContext.Current.Cache[id]; 55 | } 56 | 57 | public void Remove(string id) 58 | { 59 | HttpContext.Current.Cache.Remove(id); 60 | } 61 | 62 | public void Clear() 63 | { 64 | var cacheEnumerator = HttpContext.Current.Cache.GetEnumerator(); 65 | while (cacheEnumerator.MoveNext()) 66 | { 67 | if (cacheEnumerator.Value is ThrottleCounter) 68 | { 69 | HttpContext.Current.Cache.Remove(cacheEnumerator.Key.ToString()); 70 | } 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /WebApiThrottle/Repositories/ConcurrentDictionaryRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace WebApiThrottle 9 | { 10 | /// 11 | /// Stores throttle metrics in a thread safe dictionary, has no clean-up mechanism, expired counters are deleted on renewal 12 | /// 13 | public class ConcurrentDictionaryRepository : IThrottleRepository 14 | { 15 | private static ConcurrentDictionary cache = new ConcurrentDictionary(); 16 | 17 | public bool Any(string id) 18 | { 19 | return cache.ContainsKey(id); 20 | } 21 | 22 | /// 23 | /// Insert or update 24 | /// 25 | /// 26 | /// The id. 27 | /// 28 | /// 29 | /// The . 30 | /// 31 | public ThrottleCounter? FirstOrDefault(string id) 32 | { 33 | var entry = new ThrottleCounterWrapper(); 34 | 35 | if (cache.TryGetValue(id, out entry)) 36 | { 37 | // remove expired entry 38 | if (entry.Timestamp + entry.ExpirationTime < DateTime.UtcNow) 39 | { 40 | cache.TryRemove(id, out entry); 41 | return null; 42 | } 43 | } 44 | 45 | return new ThrottleCounter 46 | { 47 | Timestamp = entry.Timestamp, 48 | TotalRequests = entry.TotalRequests 49 | }; 50 | } 51 | 52 | public void Save(string id, ThrottleCounter throttleCounter, TimeSpan expirationTime) 53 | { 54 | var entry = new ThrottleCounterWrapper 55 | { 56 | ExpirationTime = expirationTime, 57 | Timestamp = throttleCounter.Timestamp, 58 | TotalRequests = throttleCounter.TotalRequests 59 | }; 60 | 61 | cache.AddOrUpdate(id, entry, (k, e) => entry); 62 | } 63 | 64 | public void Remove(string id) 65 | { 66 | var entry = new ThrottleCounterWrapper(); 67 | cache.TryRemove(id, out entry); 68 | } 69 | 70 | public void Clear() 71 | { 72 | cache.Clear(); 73 | } 74 | 75 | [Serializable] 76 | internal struct ThrottleCounterWrapper 77 | { 78 | public DateTime Timestamp { get; set; } 79 | 80 | public long TotalRequests { get; set; } 81 | 82 | public TimeSpan ExpirationTime { get; set; } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /WebApiThrottle/Repositories/IPolicyRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | namespace WebApiThrottle 3 | { 4 | public interface IPolicyRepository 5 | { 6 | ThrottlePolicy FirstOrDefault(string id); 7 | 8 | void Remove(string id); 9 | 10 | void Save(string id, ThrottlePolicy policy); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /WebApiThrottle/Repositories/IThrottleRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace WebApiThrottle 8 | { 9 | /// 10 | /// Implement this interface if you want to create a persistent store for the throttle metrics 11 | /// 12 | public interface IThrottleRepository 13 | { 14 | bool Any(string id); 15 | 16 | ThrottleCounter? FirstOrDefault(string id); 17 | 18 | void Save(string id, ThrottleCounter throttleCounter, TimeSpan expirationTime); 19 | 20 | void Remove(string id); 21 | 22 | void Clear(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /WebApiThrottle/Repositories/MemoryCacheRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.Caching; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace WebApiThrottle 9 | { 10 | /// 11 | /// Stors throttle metrics in runtime cache, intented for owin self host. 12 | /// 13 | public class MemoryCacheRepository : IThrottleRepository 14 | { 15 | ObjectCache memCache = MemoryCache.Default; 16 | 17 | /// 18 | /// Insert or update 19 | /// 20 | public void Save(string id, ThrottleCounter throttleCounter, TimeSpan expirationTime) 21 | { 22 | if (memCache[id] != null) 23 | { 24 | memCache[id] = throttleCounter; 25 | } 26 | else 27 | { 28 | memCache.Add( 29 | id, 30 | throttleCounter, new CacheItemPolicy() 31 | { 32 | SlidingExpiration = expirationTime 33 | }); 34 | } 35 | } 36 | 37 | public bool Any(string id) 38 | { 39 | return memCache[id] != null; 40 | } 41 | 42 | public ThrottleCounter? FirstOrDefault(string id) 43 | { 44 | return (ThrottleCounter?)memCache[id]; 45 | } 46 | 47 | public void Remove(string id) 48 | { 49 | memCache.Remove(id); 50 | } 51 | 52 | public void Clear() 53 | { 54 | var cacheKeys = memCache.Where(kvp => kvp.Value is ThrottleCounter).Select(kvp => kvp.Key).ToList(); 55 | foreach (string cacheKey in cacheKeys) 56 | { 57 | memCache.Remove(cacheKey); 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /WebApiThrottle/Repositories/PolicyCacheRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Web; 7 | using System.Web.Caching; 8 | 9 | namespace WebApiThrottle 10 | { 11 | /// 12 | /// Stores policy in asp.net cache 13 | /// 14 | public class PolicyCacheRepository : IPolicyRepository 15 | { 16 | public void Save(string id, ThrottlePolicy policy) 17 | { 18 | if (HttpContext.Current.Cache[id] != null) 19 | { 20 | HttpContext.Current.Cache[id] = policy; 21 | } 22 | else 23 | { 24 | HttpContext.Current.Cache.Add( 25 | id, 26 | policy, 27 | null, 28 | Cache.NoAbsoluteExpiration, 29 | Cache.NoSlidingExpiration, 30 | CacheItemPriority.High, 31 | null); 32 | } 33 | } 34 | 35 | public ThrottlePolicy FirstOrDefault(string id) 36 | { 37 | var policy = (ThrottlePolicy)HttpContext.Current.Cache[id]; 38 | return policy; 39 | } 40 | 41 | public void Remove(string id) 42 | { 43 | HttpContext.Current.Cache.Remove(id); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /WebApiThrottle/Repositories/PolicyMemoryCacheRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.Caching; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace WebApiThrottle 9 | { 10 | /// 11 | /// Stores policy in runtime cache, intended for OWIN self host. 12 | /// 13 | public class PolicyMemoryCacheRepository : IPolicyRepository 14 | { 15 | private ObjectCache memCache = MemoryCache.Default; 16 | 17 | public void Save(string id, ThrottlePolicy policy) 18 | { 19 | if (memCache[id] != null) 20 | { 21 | memCache[id] = policy; 22 | } 23 | else 24 | { 25 | memCache.Add( 26 | id, 27 | policy, 28 | new CacheItemPolicy()); 29 | } 30 | } 31 | 32 | public ThrottlePolicy FirstOrDefault(string id) 33 | { 34 | var policy = (ThrottlePolicy)memCache[id]; 35 | return policy; 36 | } 37 | 38 | public void Remove(string id) 39 | { 40 | memCache.Remove(id); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /WebApiThrottle/ThrottleManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace WebApiThrottle 8 | { 9 | /// 10 | /// Allows changing the cache keys prefix and suffix, exposes ways to refresh the policy object at runtime. 11 | /// 12 | public static class ThrottleManager 13 | { 14 | private static string applicationName = string.Empty; 15 | 16 | private static string throttleKey = "throttle"; 17 | 18 | private static string policyKey = "throttle_policy"; 19 | 20 | /// 21 | /// Gets or sets the global prefix 22 | /// 23 | public static string ApplicationName 24 | { 25 | get 26 | { 27 | return applicationName; 28 | } 29 | 30 | set 31 | { 32 | applicationName = value; 33 | } 34 | } 35 | 36 | /// 37 | /// Gets or sets the key prefix for rate limits 38 | /// 39 | public static string ThrottleKey 40 | { 41 | get 42 | { 43 | return throttleKey; 44 | } 45 | 46 | set 47 | { 48 | throttleKey = value; 49 | } 50 | } 51 | 52 | /// 53 | /// Gets or sets the policy key suffix 54 | /// 55 | public static string PolicyKey 56 | { 57 | get 58 | { 59 | return policyKey; 60 | } 61 | 62 | set 63 | { 64 | policyKey = value; 65 | } 66 | } 67 | 68 | /// 69 | /// Returns key prefix for rate limits 70 | /// 71 | /// 72 | /// The throttle key. 73 | /// 74 | public static string GetThrottleKey() 75 | { 76 | return ApplicationName + ThrottleKey; 77 | } 78 | 79 | /// 80 | /// Returns the policy key (global prefix + policy key suffix) 81 | /// 82 | /// 83 | /// The policy key. 84 | /// 85 | public static string GetPolicyKey() 86 | { 87 | return ApplicationName + PolicyKey; 88 | } 89 | 90 | /// 91 | /// Updates the policy object cached value 92 | /// 93 | /// 94 | /// The policy. 95 | /// 96 | /// 97 | /// The policy repository. 98 | /// 99 | public static void UpdatePolicy(ThrottlePolicy policy, IPolicyRepository cacheRepository) 100 | { 101 | cacheRepository.Save(GetPolicyKey(), policy); 102 | } 103 | 104 | /// 105 | /// Reads the policy object from store and updates the cache 106 | /// 107 | /// 108 | /// The store provider. 109 | /// 110 | /// 111 | /// The cache repository. 112 | /// 113 | public static void UpdatePolicy(IThrottlePolicyProvider storeProvider, IPolicyRepository cacheRepository) 114 | { 115 | var policy = ThrottlePolicy.FromStore(storeProvider); 116 | cacheRepository.Save(GetPolicyKey(), policy); 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /WebApiThrottle/ThrottlePolicy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace WebApiThrottle 8 | { 9 | /// 10 | /// Rate limits policy 11 | /// 12 | [Serializable] 13 | public class ThrottlePolicy 14 | { 15 | public ThrottlePolicy() 16 | { 17 | IpWhitelist = new List(); 18 | IpRules = new Dictionary(); 19 | ClientWhitelist = new List(); 20 | ClientRules = new Dictionary(); 21 | EndpointWhitelist = new List(); 22 | EndpointRules = new Dictionary(); 23 | Rates = new Dictionary(); 24 | } 25 | 26 | /// 27 | /// Configure default request limits per second, minute, hour or day 28 | /// 29 | public ThrottlePolicy(long? perSecond = null, long? perMinute = null, long? perHour = null, long? perDay = null, long? perWeek = null) 30 | : this() 31 | { 32 | Rates = new Dictionary(); 33 | if (perSecond.HasValue) 34 | { 35 | Rates.Add(RateLimitPeriod.Second, perSecond.Value); 36 | } 37 | 38 | if (perMinute.HasValue) 39 | { 40 | Rates.Add(RateLimitPeriod.Minute, perMinute.Value); 41 | } 42 | 43 | 44 | if (perHour.HasValue) 45 | { 46 | Rates.Add(RateLimitPeriod.Hour, perHour.Value); 47 | } 48 | 49 | 50 | if (perDay.HasValue) 51 | { 52 | Rates.Add(RateLimitPeriod.Day, perDay.Value); 53 | } 54 | if (perWeek.HasValue) 55 | { 56 | Rates.Add(RateLimitPeriod.Week, perWeek.Value); 57 | } 58 | } 59 | 60 | /// 61 | /// Gets or sets a value indicating whether IP throttling is enabled. 62 | /// 63 | public bool IpThrottling { get; set; } 64 | 65 | public List IpWhitelist { get; set; } 66 | 67 | public IDictionary IpRules { get; set; } 68 | 69 | /// 70 | /// Gets or sets a value indicating whether client key throttling is enabled. 71 | /// 72 | public bool ClientThrottling { get; set; } 73 | 74 | public List ClientWhitelist { get; set; } 75 | 76 | public IDictionary ClientRules { get; set; } 77 | 78 | /// 79 | /// Gets or sets a value indicating whether route throttling is enabled 80 | /// 81 | public bool EndpointThrottling { get; set; } 82 | 83 | public List EndpointWhitelist { get; set; } 84 | 85 | public IDictionary EndpointRules { get; set; } 86 | 87 | /// 88 | /// Gets or sets a value indicating whether all requests, including the rejected ones, should be stacked in this order: day, hour, min, sec 89 | /// 90 | public bool StackBlockedRequests { get; set; } 91 | 92 | public Dictionary Rates { get; set; } 93 | 94 | public static ThrottlePolicy FromStore(IThrottlePolicyProvider provider) 95 | { 96 | var settings = provider.ReadSettings(); 97 | var whitelists = provider.AllWhitelists(); 98 | var rules = provider.AllRules(); 99 | 100 | var policy = new ThrottlePolicy( 101 | perSecond: settings.LimitPerSecond, 102 | perMinute: settings.LimitPerMinute, 103 | perHour: settings.LimitPerHour, 104 | perDay: settings.LimitPerDay, 105 | perWeek: settings.LimitPerWeek); 106 | 107 | policy.IpThrottling = settings.IpThrottling; 108 | policy.ClientThrottling = settings.ClientThrottling; 109 | policy.EndpointThrottling = settings.EndpointThrottling; 110 | policy.StackBlockedRequests = settings.StackBlockedRequests; 111 | 112 | policy.IpRules = new Dictionary(); 113 | policy.ClientRules = new Dictionary(); 114 | policy.EndpointRules = new Dictionary(); 115 | policy.EndpointWhitelist = new List(); 116 | policy.IpWhitelist = new List(); 117 | policy.ClientWhitelist = new List(); 118 | 119 | foreach (var item in rules) 120 | { 121 | var rateLimit = new RateLimits 122 | { 123 | PerSecond = item.LimitPerSecond, 124 | PerMinute = item.LimitPerMinute, 125 | PerHour = item.LimitPerHour, 126 | PerDay = item.LimitPerDay, 127 | PerWeek = item.LimitPerWeek 128 | }; 129 | 130 | switch (item.PolicyType) 131 | { 132 | case ThrottlePolicyType.IpThrottling: 133 | policy.IpRules.Add(item.Entry, rateLimit); 134 | break; 135 | case ThrottlePolicyType.ClientThrottling: 136 | policy.ClientRules.Add(item.Entry, rateLimit); 137 | break; 138 | case ThrottlePolicyType.EndpointThrottling: 139 | policy.EndpointRules.Add(item.Entry, rateLimit); 140 | break; 141 | } 142 | } 143 | 144 | if (whitelists != null) 145 | { 146 | policy.IpWhitelist.AddRange(whitelists.Where(x => x.PolicyType == ThrottlePolicyType.IpThrottling).Select(x => x.Entry)); 147 | policy.ClientWhitelist.AddRange(whitelists.Where(x => x.PolicyType == ThrottlePolicyType.ClientThrottling).Select(x => x.Entry)); 148 | policy.EndpointWhitelist.AddRange(whitelists.Where(x => x.PolicyType == ThrottlePolicyType.EndpointThrottling).Select(x => x.Entry)); 149 | } 150 | return policy; 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /WebApiThrottle/ThrottlingCore.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Net.Http; 6 | using System.Text; 7 | using WebApiThrottle.Net; 8 | 9 | namespace WebApiThrottle 10 | { 11 | /// 12 | /// Common code shared between ThrottlingHandler and ThrottlingFilter 13 | /// 14 | internal class ThrottlingCore 15 | { 16 | public ThrottlingCore() 17 | { 18 | IpAddressParser = new DefaultIpAddressParser(); 19 | } 20 | 21 | private static readonly object ProcessLocker = new object(); 22 | 23 | internal ThrottlePolicy Policy { get; set; } 24 | 25 | internal IThrottleRepository Repository { get; set; } 26 | 27 | internal IIpAddressParser IpAddressParser { get; set; } 28 | 29 | internal bool ContainsIp(List ipRules, string clientIp) 30 | { 31 | return IpAddressParser.ContainsIp(ipRules, clientIp); 32 | } 33 | 34 | internal bool ContainsIp(List ipRules, string clientIp, out string rule) 35 | { 36 | return IpAddressParser.ContainsIp(ipRules, clientIp, out rule); 37 | } 38 | 39 | internal IPAddress GetClientIp(HttpRequestMessage request) 40 | { 41 | return IpAddressParser.GetClientIp(request); 42 | } 43 | 44 | internal ThrottleLogEntry ComputeLogEntry(string requestId, RequestIdentity identity, ThrottleCounter throttleCounter, string rateLimitPeriod, long rateLimit, HttpRequestMessage request) 45 | { 46 | return new ThrottleLogEntry 47 | { 48 | ClientIp = identity.ClientIp, 49 | ClientKey = identity.ClientKey, 50 | Endpoint = identity.Endpoint, 51 | LogDate = DateTime.UtcNow, 52 | RateLimit = rateLimit, 53 | RateLimitPeriod = rateLimitPeriod, 54 | RequestId = requestId, 55 | StartPeriod = throttleCounter.Timestamp, 56 | TotalRequests = throttleCounter.TotalRequests, 57 | Request = request 58 | }; 59 | } 60 | 61 | internal string RetryAfterFrom(DateTime timestamp, RateLimitPeriod period) 62 | { 63 | var secondsPast = Convert.ToInt32((DateTime.UtcNow - timestamp).TotalSeconds); 64 | var retryAfter = 1; 65 | switch (period) 66 | { 67 | case RateLimitPeriod.Minute: 68 | retryAfter = 60; 69 | break; 70 | case RateLimitPeriod.Hour: 71 | retryAfter = 60 * 60; 72 | break; 73 | case RateLimitPeriod.Day: 74 | retryAfter = 60 * 60 * 24; 75 | break; 76 | case RateLimitPeriod.Week: 77 | retryAfter = 60 * 60 * 24 * 7; 78 | break; 79 | } 80 | retryAfter = retryAfter > 1 ? retryAfter - secondsPast : 1; 81 | return retryAfter.ToString(System.Globalization.CultureInfo.InvariantCulture); 82 | } 83 | 84 | internal bool IsWhitelisted(RequestIdentity requestIdentity) 85 | { 86 | if (requestIdentity.ForceWhiteList) 87 | { 88 | return true; 89 | } 90 | 91 | if (Policy.IpThrottling) 92 | { 93 | if (Policy.IpWhitelist != null && ContainsIp(Policy.IpWhitelist, requestIdentity.ClientIp)) 94 | { 95 | return true; 96 | } 97 | } 98 | 99 | if (Policy.ClientThrottling) 100 | { 101 | if (Policy.ClientWhitelist != null && Policy.ClientWhitelist.Contains(requestIdentity.ClientKey)) 102 | { 103 | return true; 104 | } 105 | } 106 | 107 | if (Policy.EndpointThrottling) 108 | { 109 | if (Policy.EndpointWhitelist != null 110 | && Policy.EndpointWhitelist.Any(x => requestIdentity.Endpoint.IndexOf(x, 0, StringComparison.InvariantCultureIgnoreCase) != -1)) 111 | { 112 | return true; 113 | } 114 | } 115 | 116 | return false; 117 | } 118 | 119 | internal string ComputeThrottleKey(RequestIdentity requestIdentity, RateLimitPeriod period) 120 | { 121 | var keyValues = new List() 122 | { 123 | ThrottleManager.GetThrottleKey() 124 | }; 125 | 126 | if (Policy.IpThrottling) 127 | { 128 | keyValues.Add(requestIdentity.ClientIp); 129 | } 130 | 131 | if (Policy.ClientThrottling) 132 | { 133 | keyValues.Add(requestIdentity.ClientKey); 134 | } 135 | 136 | if (Policy.EndpointThrottling) 137 | { 138 | keyValues.Add(requestIdentity.Endpoint); 139 | } 140 | 141 | keyValues.Add(period.ToString()); 142 | 143 | var id = string.Join("_", keyValues); 144 | var idBytes = Encoding.UTF8.GetBytes(id); 145 | 146 | byte[] hashBytes; 147 | 148 | using (var algorithm = System.Security.Cryptography.SHA1.Create()) 149 | { 150 | hashBytes = algorithm.ComputeHash(idBytes); 151 | } 152 | 153 | var hex = BitConverter.ToString(hashBytes).Replace("-", string.Empty); 154 | return hex; 155 | } 156 | 157 | internal List> RatesWithDefaults(List> defRates) 158 | { 159 | if (!defRates.Any(x => x.Key == RateLimitPeriod.Second)) 160 | { 161 | defRates.Insert(0, new KeyValuePair(RateLimitPeriod.Second, 0)); 162 | } 163 | 164 | if (!defRates.Any(x => x.Key == RateLimitPeriod.Minute)) 165 | { 166 | defRates.Insert(1, new KeyValuePair(RateLimitPeriod.Minute, 0)); 167 | } 168 | 169 | if (!defRates.Any(x => x.Key == RateLimitPeriod.Hour)) 170 | { 171 | defRates.Insert(2, new KeyValuePair(RateLimitPeriod.Hour, 0)); 172 | } 173 | 174 | if (!defRates.Any(x => x.Key == RateLimitPeriod.Day)) 175 | { 176 | defRates.Insert(3, new KeyValuePair(RateLimitPeriod.Day, 0)); 177 | } 178 | 179 | if (!defRates.Any(x => x.Key == RateLimitPeriod.Week)) 180 | { 181 | defRates.Insert(4, new KeyValuePair(RateLimitPeriod.Week, 0)); 182 | } 183 | 184 | return defRates; 185 | } 186 | 187 | internal ThrottleCounter ProcessRequest(TimeSpan timeSpan, string id) 188 | { 189 | var throttleCounter = new ThrottleCounter() 190 | { 191 | Timestamp = DateTime.UtcNow, 192 | TotalRequests = 1 193 | }; 194 | 195 | // serial reads and writes 196 | lock (ProcessLocker) 197 | { 198 | var entry = Repository.FirstOrDefault(id); 199 | if (entry.HasValue) 200 | { 201 | // entry has not expired 202 | if (entry.Value.Timestamp + timeSpan >= DateTime.UtcNow) 203 | { 204 | // increment request count 205 | var totalRequests = entry.Value.TotalRequests + 1; 206 | 207 | // deep copy 208 | throttleCounter = new ThrottleCounter 209 | { 210 | Timestamp = entry.Value.Timestamp, 211 | TotalRequests = totalRequests 212 | }; 213 | } 214 | } 215 | 216 | // stores: id (string) - timestamp (datetime) - total (long) 217 | Repository.Save(id, throttleCounter, timeSpan); 218 | } 219 | 220 | return throttleCounter; 221 | } 222 | 223 | internal TimeSpan GetTimeSpanFromPeriod(RateLimitPeriod rateLimitPeriod) 224 | { 225 | var timeSpan = TimeSpan.FromSeconds(1); 226 | 227 | switch (rateLimitPeriod) 228 | { 229 | case RateLimitPeriod.Second: 230 | timeSpan = TimeSpan.FromSeconds(1); 231 | break; 232 | case RateLimitPeriod.Minute: 233 | timeSpan = TimeSpan.FromMinutes(1); 234 | break; 235 | case RateLimitPeriod.Hour: 236 | timeSpan = TimeSpan.FromHours(1); 237 | break; 238 | case RateLimitPeriod.Day: 239 | timeSpan = TimeSpan.FromDays(1); 240 | break; 241 | case RateLimitPeriod.Week: 242 | timeSpan = TimeSpan.FromDays(7); 243 | break; 244 | } 245 | 246 | return timeSpan; 247 | } 248 | 249 | internal void ApplyRules(RequestIdentity identity, TimeSpan timeSpan, RateLimitPeriod rateLimitPeriod, ref long rateLimit) 250 | { 251 | // apply endpoint rate limits 252 | if (Policy.EndpointRules != null) 253 | { 254 | var rules = Policy.EndpointRules.Where(x => identity.Endpoint.IndexOf(x.Key, 0, StringComparison.InvariantCultureIgnoreCase) != -1).ToList(); 255 | if (rules.Any()) 256 | { 257 | // get the lower limit from all applying rules 258 | var customRate = (from r in rules let rateValue = r.Value.GetLimit(rateLimitPeriod) select rateValue).Min(); 259 | 260 | if (customRate > 0) 261 | { 262 | rateLimit = customRate; 263 | } 264 | } 265 | } 266 | 267 | // apply custom rate limit for clients that will override endpoint limits 268 | if (Policy.ClientRules != null && Policy.ClientRules.Keys.Contains(identity.ClientKey)) 269 | { 270 | var limit = Policy.ClientRules[identity.ClientKey].GetLimit(rateLimitPeriod); 271 | if (limit > 0) 272 | { 273 | rateLimit = limit; 274 | } 275 | } 276 | 277 | // enforce ip rate limit as is most specific 278 | string ipRule = null; 279 | if (Policy.IpRules != null && ContainsIp(Policy.IpRules.Keys.ToList(), identity.ClientIp, out ipRule)) 280 | { 281 | var limit = Policy.IpRules[ipRule].GetLimit(rateLimitPeriod); 282 | if (limit > 0) 283 | { 284 | rateLimit = limit; 285 | } 286 | } 287 | } 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /WebApiThrottle/ThrottlingFilter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Net.Http; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using System.Web.Http.Controllers; 9 | using System.Web.Http.Filters; 10 | using WebApiThrottle.Net; 11 | 12 | namespace WebApiThrottle 13 | { 14 | /// 15 | /// Throttle action filter 16 | /// 17 | public class ThrottlingFilter : ActionFilterAttribute, IActionFilter 18 | { 19 | private readonly ThrottlingCore core; 20 | 21 | private IPolicyRepository policyRepository; 22 | 23 | private ThrottlePolicy policy; 24 | 25 | /// 26 | /// Initializes a new instance of the class. 27 | /// By default, the property 28 | /// is set to 429 (Too Many Requests). 29 | /// 30 | public ThrottlingFilter() 31 | { 32 | QuotaExceededResponseCode = (HttpStatusCode)429; 33 | Repository = new CacheRepository(); 34 | core = new ThrottlingCore(); 35 | } 36 | 37 | /// 38 | /// Initializes a new instance of the class. 39 | /// Persists the policy object in cache using implementation. 40 | /// The policy object can be updated by at runtime. 41 | /// 42 | /// 43 | /// The policy. 44 | /// 45 | /// 46 | /// The policy repository. 47 | /// 48 | /// 49 | /// The repository. 50 | /// 51 | /// 52 | /// The logger. 53 | /// 54 | /// 55 | /// The ip address provider 56 | /// 57 | public ThrottlingFilter(ThrottlePolicy policy, 58 | IPolicyRepository policyRepository, 59 | IThrottleRepository repository, 60 | IThrottleLogger logger, 61 | IIpAddressParser ipAddressParser = null) 62 | { 63 | core = new ThrottlingCore(); 64 | core.Repository = repository; 65 | Repository = repository; 66 | Logger = logger; 67 | if (ipAddressParser != null) 68 | { 69 | core.IpAddressParser = ipAddressParser; 70 | } 71 | 72 | QuotaExceededResponseCode = (HttpStatusCode)429; 73 | 74 | this.policy = policy; 75 | this.policyRepository = policyRepository; 76 | 77 | if (policyRepository != null) 78 | { 79 | policyRepository.Save(ThrottleManager.GetPolicyKey(), policy); 80 | } 81 | } 82 | 83 | /// 84 | /// Gets or sets a repository used to access throttling rate limits policy. 85 | /// 86 | public IPolicyRepository PolicyRepository 87 | { 88 | get { return policyRepository; } 89 | set { policyRepository = value; } 90 | } 91 | 92 | /// 93 | /// Gets or sets the throttling rate limits policy 94 | /// 95 | public ThrottlePolicy Policy 96 | { 97 | get { return policy; } 98 | set { policy = value; } 99 | } 100 | 101 | /// 102 | /// Gets or sets the throttle metrics storage 103 | /// 104 | public IThrottleRepository Repository { get; set; } 105 | 106 | /// 107 | /// Gets or sets an instance of that will log blocked requests 108 | /// 109 | public IThrottleLogger Logger { get; set; } 110 | 111 | /// 112 | /// Gets or sets a value that will be used as a formatter for the QuotaExceeded response message. 113 | /// If none specified the default will be: 114 | /// API calls quota exceeded! maximum admitted {0} per {1} 115 | /// 116 | public string QuotaExceededMessage { get; set; } 117 | 118 | /// 119 | /// Gets or sets a value that will be used as a formatter for the QuotaExceeded response message. 120 | /// If none specified the default will be: 121 | /// API calls quota exceeded! maximum admitted {0} per {1} 122 | /// 123 | public Func QuotaExceededContent { get; set; } 124 | 125 | /// 126 | /// Gets or sets the value to return as the HTTP status 127 | /// code when a request is rejected because of the 128 | /// throttling policy. The default value is 429 (Too Many Requests). 129 | /// 130 | public HttpStatusCode QuotaExceededResponseCode { get; set; } 131 | 132 | public override void OnActionExecuting(HttpActionContext actionContext) 133 | { 134 | EnableThrottlingAttribute attrPolicy = null; 135 | var applyThrottling = ApplyThrottling(actionContext, out attrPolicy); 136 | 137 | // get policy from repo 138 | if(policyRepository != null) 139 | { 140 | policy = policyRepository.FirstOrDefault(ThrottleManager.GetPolicyKey()); 141 | } 142 | 143 | if (Policy != null && applyThrottling) 144 | { 145 | core.Repository = Repository; 146 | core.Policy = Policy; 147 | 148 | var identity = SetIdentity(actionContext.Request); 149 | 150 | if (!core.IsWhitelisted(identity)) 151 | { 152 | TimeSpan timeSpan = TimeSpan.FromSeconds(1); 153 | 154 | // get default rates 155 | var defRates = core.RatesWithDefaults(Policy.Rates.ToList()); 156 | if (Policy.StackBlockedRequests) 157 | { 158 | // all requests including the rejected ones will stack in this order: week, day, hour, min, sec 159 | // if a client hits the hour limit then the minutes and seconds counters will expire and will eventually get erased from cache 160 | defRates.Reverse(); 161 | } 162 | 163 | // apply policy 164 | foreach (var rate in defRates) 165 | { 166 | var rateLimitPeriod = rate.Key; 167 | var rateLimit = rate.Value; 168 | 169 | timeSpan = core.GetTimeSpanFromPeriod(rateLimitPeriod); 170 | 171 | // apply EnableThrottlingAttribute policy 172 | var attrLimit = attrPolicy.GetLimit(rateLimitPeriod); 173 | if (attrLimit > 0) 174 | { 175 | rateLimit = attrLimit; 176 | } 177 | 178 | // apply global rules 179 | core.ApplyRules(identity, timeSpan, rateLimitPeriod, ref rateLimit); 180 | 181 | if (rateLimit > 0) 182 | { 183 | // increment counter 184 | var requestId = ComputeThrottleKey(identity, rateLimitPeriod); 185 | var throttleCounter = core.ProcessRequest(timeSpan, requestId); 186 | 187 | // check if key expired 188 | if (throttleCounter.Timestamp + timeSpan < DateTime.UtcNow) 189 | { 190 | continue; 191 | } 192 | 193 | // check if limit is reached 194 | if (throttleCounter.TotalRequests > rateLimit) 195 | { 196 | // log blocked request 197 | if (Logger != null) 198 | { 199 | Logger.Log(core.ComputeLogEntry(requestId, identity, throttleCounter, rateLimitPeriod.ToString(), rateLimit, actionContext.Request)); 200 | } 201 | 202 | var message = !string.IsNullOrEmpty(this.QuotaExceededMessage) 203 | ? this.QuotaExceededMessage 204 | : "API calls quota exceeded! maximum admitted {0} per {1}."; 205 | 206 | var content = this.QuotaExceededContent != null 207 | ? this.QuotaExceededContent(rateLimit, rateLimitPeriod) 208 | : string.Format(message, rateLimit, rateLimitPeriod); 209 | 210 | // add status code and retry after x seconds to response 211 | actionContext.Response = QuotaExceededResponse( 212 | actionContext.Request, 213 | content, 214 | QuotaExceededResponseCode, 215 | core.RetryAfterFrom(throttleCounter.Timestamp, rateLimitPeriod)); 216 | } 217 | } 218 | } 219 | } 220 | } 221 | 222 | base.OnActionExecuting(actionContext); 223 | } 224 | 225 | protected virtual RequestIdentity SetIdentity(HttpRequestMessage request) 226 | { 227 | var entry = new RequestIdentity(); 228 | entry.ClientIp = core.GetClientIp(request).ToString(); 229 | entry.Endpoint = request.RequestUri.AbsolutePath.ToLowerInvariant(); 230 | entry.ClientKey = request.Headers.Contains("Authorization-Token") ? request.Headers.GetValues("Authorization-Token").First() : "anon"; 231 | 232 | return entry; 233 | } 234 | 235 | protected virtual string ComputeThrottleKey(RequestIdentity requestIdentity, RateLimitPeriod period) 236 | { 237 | return core.ComputeThrottleKey(requestIdentity, period); 238 | } 239 | 240 | protected IPAddress GetClientIp(HttpRequestMessage request) 241 | { 242 | return core.GetClientIp(request); 243 | } 244 | 245 | protected virtual HttpResponseMessage QuotaExceededResponse(HttpRequestMessage request, object content, HttpStatusCode responseCode, string retryAfter) 246 | { 247 | var response = request.CreateResponse(responseCode, content); 248 | response.Headers.Add("Retry-After", new string[] { retryAfter }); 249 | return response; 250 | } 251 | 252 | private bool ApplyThrottling(HttpActionContext filterContext, out EnableThrottlingAttribute attr) 253 | { 254 | var applyThrottling = false; 255 | attr = null; 256 | 257 | if (filterContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes(true).Any()) 258 | { 259 | attr = filterContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes(true).First(); 260 | applyThrottling = true; 261 | } 262 | 263 | if (filterContext.ActionDescriptor.GetCustomAttributes(true).Any()) 264 | { 265 | attr = filterContext.ActionDescriptor.GetCustomAttributes(true).First(); 266 | applyThrottling = true; 267 | } 268 | 269 | // explicit disabled 270 | if (filterContext.ActionDescriptor.GetCustomAttributes(true).Any()) 271 | { 272 | applyThrottling = false; 273 | } 274 | 275 | return applyThrottling; 276 | } 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /WebApiThrottle/ThrottlingHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Net.Http; 6 | using System.ServiceModel.Channels; 7 | using System.Text; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | using System.Web; 11 | using WebApiThrottle.Net; 12 | 13 | namespace WebApiThrottle 14 | { 15 | /// 16 | /// Throttle message handler 17 | /// 18 | public class ThrottlingHandler : DelegatingHandler 19 | { 20 | private ThrottlingCore core; 21 | private IPolicyRepository policyRepository; 22 | private ThrottlePolicy policy; 23 | 24 | /// 25 | /// Initializes a new instance of the class. 26 | /// By default, the property 27 | /// is set to 429 (Too Many Requests). 28 | /// 29 | public ThrottlingHandler() 30 | { 31 | QuotaExceededResponseCode = (HttpStatusCode)429; 32 | Repository = new CacheRepository(); 33 | core = new ThrottlingCore(); 34 | } 35 | 36 | /// 37 | /// Initializes a new instance of the class. 38 | /// Persists the policy object in cache using implementation. 39 | /// The policy object can be updated by at runtime. 40 | /// 41 | /// 42 | /// The policy. 43 | /// 44 | /// 45 | /// The policy repository. 46 | /// 47 | /// 48 | /// The repository. 49 | /// 50 | /// 51 | /// The logger. 52 | /// 53 | /// 54 | /// The IpAddressParser 55 | /// 56 | public ThrottlingHandler(ThrottlePolicy policy, 57 | IPolicyRepository policyRepository, 58 | IThrottleRepository repository, 59 | IThrottleLogger logger, 60 | IIpAddressParser ipAddressParser = null) 61 | { 62 | core = new ThrottlingCore(); 63 | core.Repository = repository; 64 | Repository = repository; 65 | Logger = logger; 66 | 67 | if (ipAddressParser != null) 68 | { 69 | core.IpAddressParser = ipAddressParser; 70 | } 71 | 72 | QuotaExceededResponseCode = (HttpStatusCode)429; 73 | 74 | this.policy = policy; 75 | this.policyRepository = policyRepository; 76 | 77 | if (policyRepository != null) 78 | { 79 | policyRepository.Save(ThrottleManager.GetPolicyKey(), policy); 80 | } 81 | } 82 | 83 | /// 84 | /// Gets or sets the throttling rate limits policy repository 85 | /// 86 | public IPolicyRepository PolicyRepository 87 | { 88 | get { return policyRepository; } 89 | set { policyRepository = value; } 90 | } 91 | 92 | /// 93 | /// Gets or sets the throttling rate limits policy 94 | /// 95 | public ThrottlePolicy Policy 96 | { 97 | get { return policy; } 98 | set { policy = value; } 99 | } 100 | 101 | /// 102 | /// Gets or sets the throttle metrics storage 103 | /// 104 | public IThrottleRepository Repository { get; set; } 105 | 106 | /// 107 | /// Gets or sets an instance of that logs traffic and blocked requests 108 | /// 109 | public IThrottleLogger Logger { get; set; } 110 | 111 | /// 112 | /// Gets or sets a value that will be used as a formatter for the QuotaExceeded response message. 113 | /// If none specified the default will be: 114 | /// API calls quota exceeded! maximum admitted {0} per {1} 115 | /// 116 | public string QuotaExceededMessage { get; set; } 117 | 118 | /// 119 | /// Gets or sets a value that will be used as a formatter for the QuotaExceeded response message. 120 | /// If none specified the default will be: 121 | /// API calls quota exceeded! maximum admitted {0} per {1} 122 | /// 123 | public Func QuotaExceededContent { get; set; } 124 | 125 | /// 126 | /// Gets or sets the value to return as the HTTP status 127 | /// code when a request is rejected because of the 128 | /// throttling policy. The default value is 429 (Too Many Requests). 129 | /// 130 | public HttpStatusCode QuotaExceededResponseCode { get; set; } 131 | 132 | protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) 133 | { 134 | // get policy from repo 135 | if (policyRepository != null) 136 | { 137 | policy = policyRepository.FirstOrDefault(ThrottleManager.GetPolicyKey()); 138 | } 139 | 140 | if (policy == null || (!policy.IpThrottling && !policy.ClientThrottling && !policy.EndpointThrottling)) 141 | { 142 | return base.SendAsync(request, cancellationToken); 143 | } 144 | 145 | core.Repository = Repository; 146 | core.Policy = policy; 147 | 148 | var identity = SetIdentity(request); 149 | 150 | if (core.IsWhitelisted(identity)) 151 | { 152 | return base.SendAsync(request, cancellationToken); 153 | } 154 | 155 | TimeSpan timeSpan = TimeSpan.FromSeconds(1); 156 | 157 | // get default rates 158 | var defRates = core.RatesWithDefaults(Policy.Rates.ToList()); 159 | if (Policy.StackBlockedRequests) 160 | { 161 | // all requests including the rejected ones will stack in this order: week, day, hour, min, sec 162 | // if a client hits the hour limit then the minutes and seconds counters will expire and will eventually get erased from cache 163 | defRates.Reverse(); 164 | } 165 | 166 | // apply policy 167 | foreach (var rate in defRates) 168 | { 169 | var rateLimitPeriod = rate.Key; 170 | var rateLimit = rate.Value; 171 | 172 | timeSpan = core.GetTimeSpanFromPeriod(rateLimitPeriod); 173 | 174 | // apply global rules 175 | core.ApplyRules(identity, timeSpan, rateLimitPeriod, ref rateLimit); 176 | 177 | if (rateLimit > 0) 178 | { 179 | // increment counter 180 | var requestId = ComputeThrottleKey(identity, rateLimitPeriod); 181 | var throttleCounter = core.ProcessRequest(timeSpan, requestId); 182 | 183 | // check if key expired 184 | if (throttleCounter.Timestamp + timeSpan < DateTime.UtcNow) 185 | { 186 | continue; 187 | } 188 | 189 | // check if limit is reached 190 | if (throttleCounter.TotalRequests > rateLimit) 191 | { 192 | // log blocked request 193 | if (Logger != null) 194 | { 195 | Logger.Log(core.ComputeLogEntry(requestId, identity, throttleCounter, rateLimitPeriod.ToString(), rateLimit, request)); 196 | } 197 | 198 | var message = !string.IsNullOrEmpty(this.QuotaExceededMessage) 199 | ? this.QuotaExceededMessage 200 | : "API calls quota exceeded! maximum admitted {0} per {1}."; 201 | 202 | var content = this.QuotaExceededContent != null 203 | ? this.QuotaExceededContent(rateLimit, rateLimitPeriod) 204 | : string.Format(message, rateLimit, rateLimitPeriod); 205 | 206 | // break execution 207 | return QuotaExceededResponse( 208 | request, 209 | content, 210 | QuotaExceededResponseCode, 211 | core.RetryAfterFrom(throttleCounter.Timestamp, rateLimitPeriod)); 212 | } 213 | } 214 | } 215 | 216 | // no throttling required 217 | return base.SendAsync(request, cancellationToken); 218 | } 219 | 220 | protected IPAddress GetClientIp(HttpRequestMessage request) 221 | { 222 | return core.GetClientIp(request); 223 | } 224 | 225 | protected virtual RequestIdentity SetIdentity(HttpRequestMessage request) 226 | { 227 | var entry = new RequestIdentity(); 228 | entry.ClientIp = core.GetClientIp(request).ToString(); 229 | entry.Endpoint = request.RequestUri.AbsolutePath.ToLowerInvariant(); 230 | entry.ClientKey = request.Headers.Contains("Authorization-Token") 231 | ? request.Headers.GetValues("Authorization-Token").First() 232 | : "anon"; 233 | 234 | return entry; 235 | } 236 | 237 | protected virtual string ComputeThrottleKey(RequestIdentity requestIdentity, RateLimitPeriod period) 238 | { 239 | return core.ComputeThrottleKey(requestIdentity, period); 240 | } 241 | 242 | protected virtual Task QuotaExceededResponse(HttpRequestMessage request, object content, HttpStatusCode responseCode, string retryAfter) 243 | { 244 | var response = request.CreateResponse(responseCode, content); 245 | response.Headers.Add("Retry-After", new string[] { retryAfter }); 246 | return Task.FromResult(response); 247 | } 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /WebApiThrottle/ThrottlingMiddleware.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Owin; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Net.Http; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using WebApiThrottle.Net; 10 | 11 | namespace WebApiThrottle 12 | { 13 | public class ThrottlingMiddleware : OwinMiddleware 14 | { 15 | private ThrottlingCore core; 16 | private IPolicyRepository policyRepository; 17 | private ThrottlePolicy policy; 18 | 19 | /// 20 | /// Initializes a new instance of the class. 21 | /// By default, the property 22 | /// is set to 429 (Too Many Requests). 23 | /// 24 | public ThrottlingMiddleware(OwinMiddleware next) 25 | : base(next) 26 | { 27 | QuotaExceededResponseCode = (HttpStatusCode)429; 28 | Repository = new CacheRepository(); 29 | core = new ThrottlingCore(); 30 | } 31 | 32 | /// 33 | /// Initializes a new instance of the class. 34 | /// Persists the policy object in cache using implementation. 35 | /// The policy object can be updated by at runtime. 36 | /// 37 | /// 38 | /// The policy. 39 | /// 40 | /// 41 | /// The policy repository. 42 | /// 43 | /// 44 | /// The repository. 45 | /// 46 | /// 47 | /// The logger. 48 | /// 49 | /// 50 | /// The IpAddressParser 51 | /// 52 | public ThrottlingMiddleware(OwinMiddleware next, 53 | ThrottlePolicy policy, 54 | IPolicyRepository policyRepository, 55 | IThrottleRepository repository, 56 | IThrottleLogger logger, 57 | IIpAddressParser ipAddressParser) 58 | : base(next) 59 | { 60 | core = new ThrottlingCore(); 61 | core.Repository = repository; 62 | Repository = repository; 63 | Logger = logger; 64 | 65 | if (ipAddressParser != null) 66 | { 67 | core.IpAddressParser = ipAddressParser; 68 | } 69 | 70 | QuotaExceededResponseCode = (HttpStatusCode)429; 71 | 72 | this.policy = policy; 73 | this.policyRepository = policyRepository; 74 | 75 | if (policyRepository != null) 76 | { 77 | policyRepository.Save(ThrottleManager.GetPolicyKey(), policy); 78 | } 79 | } 80 | 81 | /// 82 | /// Gets or sets the throttling rate limits policy repository 83 | /// 84 | public IPolicyRepository PolicyRepository 85 | { 86 | get { return policyRepository; } 87 | set { policyRepository = value; } 88 | } 89 | 90 | /// 91 | /// Gets or sets the throttling rate limits policy 92 | /// 93 | public ThrottlePolicy Policy 94 | { 95 | get { return policy; } 96 | set { policy = value; } 97 | } 98 | 99 | /// 100 | /// Gets or sets the throttle metrics storage 101 | /// 102 | public IThrottleRepository Repository { get; set; } 103 | 104 | /// 105 | /// Gets or sets an instance of that logs traffic and blocked requests 106 | /// 107 | public IThrottleLogger Logger { get; set; } 108 | 109 | /// 110 | /// Gets or sets a value that will be used as a formatter for the QuotaExceeded response message. 111 | /// If none specified the default will be: 112 | /// API calls quota exceeded! maximum admitted {0} per {1} 113 | /// 114 | public string QuotaExceededMessage { get; set; } 115 | 116 | /// 117 | /// Gets or sets the value to return as the HTTP status 118 | /// code when a request is rejected because of the 119 | /// throttling policy. The default value is 429 (Too Many Requests). 120 | /// 121 | public HttpStatusCode QuotaExceededResponseCode { get; set; } 122 | 123 | public override async Task Invoke(IOwinContext context) 124 | { 125 | var response = context.Response; 126 | var request = context.Request; 127 | 128 | // get policy from repo 129 | if (policyRepository != null) 130 | { 131 | policy = policyRepository.FirstOrDefault(ThrottleManager.GetPolicyKey()); 132 | } 133 | 134 | if (policy == null || (!policy.IpThrottling && !policy.ClientThrottling && !policy.EndpointThrottling)) 135 | { 136 | await Next.Invoke(context); 137 | return; 138 | } 139 | 140 | core.Repository = Repository; 141 | core.Policy = policy; 142 | 143 | var identity = SetIdentity(request); 144 | 145 | if (core.IsWhitelisted(identity)) 146 | { 147 | await Next.Invoke(context); 148 | return; 149 | } 150 | 151 | TimeSpan timeSpan = TimeSpan.FromSeconds(1); 152 | 153 | // get default rates 154 | var defRates = core.RatesWithDefaults(Policy.Rates.ToList()); 155 | if (Policy.StackBlockedRequests) 156 | { 157 | // all requests including the rejected ones will stack in this order: week, day, hour, min, sec 158 | // if a client hits the hour limit then the minutes and seconds counters will expire and will eventually get erased from cache 159 | defRates.Reverse(); 160 | } 161 | 162 | // apply policy 163 | foreach (var rate in defRates) 164 | { 165 | var rateLimitPeriod = rate.Key; 166 | var rateLimit = rate.Value; 167 | 168 | timeSpan = core.GetTimeSpanFromPeriod(rateLimitPeriod); 169 | 170 | // apply global rules 171 | core.ApplyRules(identity, timeSpan, rateLimitPeriod, ref rateLimit); 172 | 173 | if (rateLimit > 0) 174 | { 175 | // increment counter 176 | var requestId = ComputeThrottleKey(identity, rateLimitPeriod); 177 | var throttleCounter = core.ProcessRequest(timeSpan, requestId); 178 | 179 | // check if key expired 180 | if (throttleCounter.Timestamp + timeSpan < DateTime.UtcNow) 181 | { 182 | continue; 183 | } 184 | 185 | // check if limit is reached 186 | if (throttleCounter.TotalRequests > rateLimit) 187 | { 188 | // log blocked request 189 | if (Logger != null) 190 | { 191 | Logger.Log(core.ComputeLogEntry(requestId, identity, throttleCounter, rateLimitPeriod.ToString(), rateLimit, null)); 192 | } 193 | 194 | var message = !string.IsNullOrEmpty(this.QuotaExceededMessage) 195 | ? this.QuotaExceededMessage 196 | : "API calls quota exceeded! maximum admitted {0} per {1}."; 197 | 198 | // break execution 199 | response.OnSendingHeaders(state => 200 | { 201 | var resp = (OwinResponse)state; 202 | resp.Headers.Add("Retry-After", new string[] { core.RetryAfterFrom(throttleCounter.Timestamp, rateLimitPeriod) }); 203 | resp.StatusCode = (int)QuotaExceededResponseCode; 204 | resp.ReasonPhrase = string.Format(message, rateLimit, rateLimitPeriod); 205 | }, response); 206 | 207 | return; 208 | } 209 | } 210 | } 211 | 212 | // no throttling required 213 | await Next.Invoke(context); 214 | } 215 | 216 | protected virtual RequestIdentity SetIdentity(IOwinRequest request) 217 | { 218 | var entry = new RequestIdentity(); 219 | entry.ClientIp = request.RemoteIpAddress; 220 | entry.Endpoint = request.Uri.AbsolutePath.ToLowerInvariant(); 221 | entry.ClientKey = request.Headers.Keys.Contains("Authorization-Token") 222 | ? request.Headers.GetValues("Authorization-Token").First() 223 | : "anon"; 224 | 225 | return entry; 226 | } 227 | 228 | protected virtual string ComputeThrottleKey(RequestIdentity requestIdentity, RateLimitPeriod period) 229 | { 230 | return core.ComputeThrottleKey(requestIdentity, period); 231 | } 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /WebApiThrottle/WebApiThrottle.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {F049811F-BC05-4CEE-B329-CE3BF2E2E4BE} 8 | Library 9 | Properties 10 | WebApiThrottle 11 | WebApiThrottle 12 | v4.5 13 | 512 14 | ..\ 15 | true 16 | 17 | 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | bin\Release\WebApiThrottle.XML 34 | 35 | 36 | 37 | False 38 | ..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll 39 | 40 | 41 | False 42 | ..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll 43 | 44 | 45 | False 46 | ..\packages\Owin.1.0\lib\net40\Owin.dll 47 | 48 | 49 | 50 | 51 | 52 | 53 | False 54 | ..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll 55 | 56 | 57 | 58 | 59 | 60 | 61 | False 62 | ..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /WebApiThrottle/WebApiThrottle.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $version$ 5 | Stefan Prodan 6 | Stefan Prodan 7 | WebApiThrottle 8 | ASP.NET Web API Rate Limiter 9 | false 10 | WebApiThrottle message handler and action filter are designed for controlling the rate of requests that clients can make to an API based on IP address, client API key and request route. WebApiThrottle works with ASP.NET Web API hosted is IIS, Owin with IIS and Owin self-hosted. 11 | If you are looking for the ASP.NET Core version please use AspNetCoreRateLimit package 12 | Stefan Prodan 2013-2016 13 | ASP.NET Web API, Owin, throttling, rate limits 14 | https://github.com/stefanprodan/WebApiThrottle 15 | en-US 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /WebApiThrottle/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /WebApiThrottle/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /WebApiThrottler.SelfHostOwinDemo/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /WebApiThrottler.SelfHostOwinDemo/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Owin.Hosting; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Net.Http; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace WebApiThrottler.SelfHostOwinDemo 10 | { 11 | class Program 12 | { 13 | static void Main(string[] args) 14 | { 15 | string baseAddress = "http://localhost:9000/"; 16 | 17 | // Start OWIN host 18 | using (WebApp.Start(url: baseAddress)) 19 | { 20 | // Create HttpCient and make a request to api/values 21 | HttpClient client = new HttpClient(); 22 | 23 | var response = client.GetAsync(baseAddress + "api/values").Result; 24 | 25 | Console.WriteLine(response); 26 | Console.WriteLine(response.Content.ReadAsStringAsync().Result); 27 | 28 | 29 | Console.ReadLine(); 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /WebApiThrottler.SelfHostOwinDemo/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: AssemblyTitle("WebApiThrottler.SelfHostOwinDemo")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("stefanprodan.eu")] 12 | [assembly: AssemblyProduct("WebApiThrottler.SelfHostOwinDemo")] 13 | [assembly: AssemblyCopyright("Copyright © Stefan Prodan 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("a17cd2be-93f9-428c-a489-8e4a5f92fa44")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /WebApiThrottler.SelfHostOwinDemo/Startup.cs: -------------------------------------------------------------------------------- 1 | using Owin; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Web.Http; 8 | using WebApiThrottle; 9 | 10 | namespace WebApiThrottler.SelfHostOwinDemo 11 | { 12 | public class Startup 13 | { 14 | // This code configures Web API. The Startup class is specified as a type 15 | // parameter in the WebApp.Start method. 16 | public void Configuration(IAppBuilder appBuilder) 17 | { 18 | // Configure Web API for self-host. 19 | HttpConfiguration config = new HttpConfiguration(); 20 | config.Routes.MapHttpRoute( 21 | name: "DefaultApi", 22 | routeTemplate: "api/{controller}/{id}", 23 | defaults: new { id = RouteParameter.Optional } 24 | ); 25 | 26 | //middleware with policy loaded from app.config 27 | appBuilder.Use(typeof(ThrottlingMiddleware), 28 | ThrottlePolicy.FromStore(new PolicyConfigurationProvider()), 29 | new PolicyMemoryCacheRepository(), 30 | new MemoryCacheRepository(), 31 | null, 32 | null); 33 | 34 | //Web API throttling load policy from app.config 35 | //config.MessageHandlers.Add(new ThrottlingHandler() 36 | //{ 37 | // Policy = ThrottlePolicy.FromStore(new PolicyConfigurationProvider()), 38 | // Repository = new MemoryCacheRepository() 39 | //}); 40 | 41 | //Web API throttling hardcoded policy 42 | //config.MessageHandlers.Add(new ThrottlingHandler() 43 | //{ 44 | // Policy = new ThrottlePolicy(perSecond: 1, perMinute: 20, perHour: 30, perDay: 35, perWeek: 3000) 45 | // { 46 | // //scope to IPs 47 | // IpThrottling = true, 48 | // IpRules = new Dictionary 49 | // { 50 | // { "::1/10", new RateLimits { PerSecond = 2 } }, 51 | // { "192.168.2.1", new RateLimits { PerMinute = 30, PerHour = 30*60, PerDay = 30*60*24 } } 52 | // }, 53 | // //white list the "::1" IP to disable throttling on localhost for Win8 54 | // IpWhitelist = new List { "127.0.0.1", "192.168.0.0/24" }, 55 | 56 | // //scope to clients (if IP throttling is applied then the scope becomes a combination of IP and client key) 57 | // ClientThrottling = true, 58 | // ClientRules = new Dictionary 59 | // { 60 | // { "api-client-key-1", new RateLimits { PerMinute = 60, PerHour = 600 } }, 61 | // { "api-client-key-9", new RateLimits { PerDay = 5000 } } 62 | // }, 63 | // //white list API keys that don’t require throttling 64 | // ClientWhitelist = new List { "admin-key" }, 65 | 66 | // //scope to routes (IP + Client Key + Request URL) 67 | // EndpointThrottling = true, 68 | // EndpointRules = new Dictionary 69 | // { 70 | // { "api/values/", new RateLimits { PerSecond = 3 } }, 71 | // { "api/values", new RateLimits { PerSecond = 4 } } 72 | // } 73 | // }, 74 | // Repository = new MemoryCacheRepository() 75 | //}); 76 | 77 | appBuilder.UseWebApi(config); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /WebApiThrottler.SelfHostOwinDemo/ValuesController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Web.Http; 7 | 8 | namespace WebApiThrottler.SelfHostOwinDemo 9 | { 10 | public class ValuesController : ApiController 11 | { 12 | // GET api/values 13 | public IEnumerable Get() 14 | { 15 | return new string[] { "value1", "value2" }; 16 | } 17 | 18 | // GET api/values/5 19 | public string Get(int id) 20 | { 21 | return "value"; 22 | } 23 | 24 | // POST api/values 25 | public void Post([FromBody]string value) 26 | { 27 | } 28 | 29 | // PUT api/values/5 30 | public void Put(int id, [FromBody]string value) 31 | { 32 | } 33 | 34 | // DELETE api/values/5 35 | public void Delete(int id) 36 | { 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /WebApiThrottler.SelfHostOwinDemo/WebApiThrottler.SelfHostOwinDemo.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {22B91BA0-EFC9-4AF8-A24B-CF1CB476A50D} 8 | Exe 9 | Properties 10 | WebApiThrottler.SelfHostOwinDemo 11 | WebApiThrottler.SelfHostOwinDemo 12 | v4.5 13 | 512 14 | ..\ 15 | true 16 | 17 | 18 | AnyCPU 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | AnyCPU 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 36 | 37 | 38 | False 39 | ..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll 40 | 41 | 42 | False 43 | ..\packages\Microsoft.Owin.Host.HttpListener.3.0.1\lib\net45\Microsoft.Owin.Host.HttpListener.dll 44 | 45 | 46 | False 47 | ..\packages\Microsoft.Owin.Hosting.3.0.1\lib\net45\Microsoft.Owin.Hosting.dll 48 | 49 | 50 | False 51 | ..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll 52 | 53 | 54 | ..\packages\Owin.1.0\lib\net40\Owin.dll 55 | 56 | 57 | 58 | 59 | 60 | 61 | False 62 | ..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll 63 | 64 | 65 | 66 | False 67 | ..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll 68 | 69 | 70 | False 71 | ..\packages\Microsoft.AspNet.WebApi.Owin.5.2.3\lib\net45\System.Web.Http.Owin.dll 72 | 73 | 74 | False 75 | ..\packages\Microsoft.AspNet.WebApi.SelfHost.5.2.3\lib\net45\System.Web.Http.SelfHost.dll 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | {f049811f-bc05-4cee-b329-ce3bf2e2e4be} 96 | WebApiThrottle 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /WebApiThrottler.SelfHostOwinDemo/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | --------------------------------------------------------------------------------