├── .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 | [](https://ci.appveyor.com/project/stefanprodan/webapithrottle)
5 | [](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 |
--------------------------------------------------------------------------------