├── samples
├── aspnet
│ ├── Global.asax
│ ├── Models
│ │ └── WeatherForecast.cs
│ ├── App_Start
│ │ └── WebApiConfig.cs
│ ├── Controllers
│ │ └── WeatherForecastController.cs
│ ├── Global.asax.cs
│ ├── packages.config
│ └── Web.config
├── IPOrClientId
│ ├── appsettings.Development.json
│ ├── appsettings.json
│ ├── WeatherForecast.cs
│ ├── IPOrClientId.csproj
│ ├── Properties
│ │ └── launchSettings.json
│ ├── Controllers
│ │ └── WeatherForecastController.cs
│ └── Program.cs
├── RuleAutoUpdate
│ ├── appsettings.Development.json
│ ├── appsettings.json
│ ├── WeatherForecast.cs
│ ├── RuleAutoUpdate.csproj
│ ├── RateLimit
│ │ ├── AutoUpdateAlgorithmService.cs
│ │ ├── NonCapturingTimer.cs
│ │ ├── RateLimitRuleDAO.cs
│ │ ├── RateLimitConfigurationManager.cs
│ │ └── AutoUpdateAlgorithmManager.cs
│ ├── Properties
│ │ └── launchSettings.json
│ ├── Program.cs
│ └── Controllers
│ │ └── WeatherForecastController.cs
├── aspnetcore
│ ├── appsettings.Development.json
│ ├── appsettings.json
│ ├── WeatherForecast.cs
│ ├── AspNetCore.csproj
│ ├── Properties
│ │ └── launchSettings.json
│ ├── Program.cs
│ └── Controllers
│ │ └── WeatherForecastController.cs
└── console
│ └── Console.csproj
├── .vscode
├── settings.json
├── tasks.json
└── launch.json
├── FireflySoft.RateLimit.AspNetCore
├── HttpRateLimitError.cs
├── Properties
│ └── launchSettings.json
├── FireflySoft.RateLimit.AspNetCore.csproj
├── HttpErrorResponse.cs
├── README.md
├── HttpInvokeInterceptor.cs
└── RateLimitService.cs
├── FireflySoft.RateLimit.Core.Test
├── SimulationRequest.cs
├── RedisClientHelper.cs
├── FireflySoft.RateLimit.Core.Test.csproj
├── CounterDictionaryTest.cs
├── TestTimeProvider.cs
├── LocalTimeProviderTest.cs
├── RedisTimeProviderTest.cs
├── AlgorithmStartTimeTest.cs
├── AlgorithmCheckResultTest.cs
└── MemorySlidingWindowTest.cs
├── FireflySoft.RateLmit.Core.BenchmarkTest
├── Program.cs
└── FireflySoft.RateLmit.Core.BenchmarkTest.csproj
├── FireflySoft.RateLimit.Core
├── InProcessAlgorithm
│ ├── TokenBucketCounter.cs
│ ├── LeakyBucketCounter.cs
│ ├── FixedWindowCounter.cs
│ ├── BaseInProcessAlgorithm.cs
│ ├── InProcessSlidingWindowAlgorithm.cs
│ ├── CounterDictionary.cs
│ ├── InProcessTokenBucketAlgorithm.cs
│ ├── MemorySlidingWindow.cs
│ └── InProcessLeakyBucketAlgorithm.cs
├── Rule
│ ├── StartTimeType.cs
│ ├── FixedWindowRule.cs
│ ├── TokenBucketRule.cs
│ ├── RateLimitRule.cs
│ ├── SlidingWindowRule.cs
│ └── LeakyBucketRule.cs
├── AlgorithmCheckResult.cs
├── Time
│ ├── ITimeProvider.cs
│ ├── LocalTimeProvider.cs
│ ├── RedisTimeProvider.cs
│ └── AlgorithmStartTime.cs
├── IAlgorithm.cs
├── RuleCheckResult.cs
├── FireflySoft.RateLimit.Core.csproj
├── README.md
└── RedisAlgorithm
│ ├── RedisTokenBucketAlgorithm.cs
│ └── RedisSlidingWindowAlgorithm.cs
├── FireflySoft.RateLimit.AspNet
├── packages.config
├── Properties
│ └── AssemblyInfo.cs
├── HttpRateLimitError.cs
├── RateLimitHandler.cs
└── FireflySoft.RateLimit.AspNet.csproj
├── README.zh-CN.md
├── FireflySoft.RateLimit.sln
├── README.md
└── .gitignore
/samples/aspnet/Global.asax:
--------------------------------------------------------------------------------
1 | <%@ Application Inherits="FireflySoft.RateLimit.AspNet.Sample.Global" %>
2 |
--------------------------------------------------------------------------------
/samples/IPOrClientId/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning"
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/samples/RuleAutoUpdate/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning"
6 | }
7 | }
8 | }
--------------------------------------------------------------------------------
/samples/IPOrClientId/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning"
6 | }
7 | },
8 | "AllowedHosts": "*"
9 | }
10 |
--------------------------------------------------------------------------------
/samples/aspnetcore/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft": "Warning",
6 | "Microsoft.Hosting.Lifetime": "Information"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/samples/aspnetcore/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft": "Warning",
6 | "Microsoft.Hosting.Lifetime": "Information"
7 | }
8 | },
9 | "AllowedHosts": "*"
10 | }
11 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "dotnet-test-explorer.testProjectPath": "**/*Test.@(csproj|vbproj|fsproj)",
3 | "dotnet-test-explorer.testArguments": "/p:CollectCoverage=true /p:CoverletOutput=./TestResults/ /p:CoverletOutputFormat=opencover",
4 | "licenser.author": "bosima"
5 | }
--------------------------------------------------------------------------------
/samples/RuleAutoUpdate/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "DbConn":"Server=127.0.0.1;User ID=root;Password=l123456;port=3306;Database=ratelimit;CharSet=utf8mb4;",
3 | "Logging": {
4 | "LogLevel": {
5 | "Default": "Information",
6 | "Microsoft.AspNetCore": "Warning"
7 | }
8 | },
9 | "AllowedHosts": "*"
10 | }
11 |
--------------------------------------------------------------------------------
/samples/IPOrClientId/WeatherForecast.cs:
--------------------------------------------------------------------------------
1 | namespace IPOrClientId;
2 |
3 | public class WeatherForecast
4 | {
5 | public DateTime Date { get; set; }
6 |
7 | public int TemperatureC { get; set; }
8 |
9 | public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
10 |
11 | public string? Summary { get; set; }
12 | }
13 |
--------------------------------------------------------------------------------
/samples/RuleAutoUpdate/WeatherForecast.cs:
--------------------------------------------------------------------------------
1 | namespace RuleAutoUpdate;
2 |
3 | public class WeatherForecast
4 | {
5 | public DateTime Date { get; set; }
6 |
7 | public int TemperatureC { get; set; }
8 |
9 | public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
10 |
11 | public string? Summary { get; set; }
12 | }
13 |
--------------------------------------------------------------------------------
/FireflySoft.RateLimit.AspNetCore/HttpRateLimitError.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace FireflySoft.RateLimit.AspNetCore
4 | {
5 | ///
6 | /// Http Rate Limit Error
7 | ///
8 | [Obsolete("The class name is not clear, please use HttpErrorResponse")]
9 | public class HttpRateLimitError : HttpErrorResponse
10 | {
11 |
12 | }
13 | }
--------------------------------------------------------------------------------
/FireflySoft.RateLimit.Core.Test/SimulationRequest.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace FireflySoft.RateLimit.Core.Test
4 | {
5 | public class SimulationRequest
6 | {
7 | public string RequestId { get; set; }
8 |
9 | public string RequestResource { get; set; }
10 |
11 | public Dictionary Parameters { get; set; }
12 | }
13 | }
--------------------------------------------------------------------------------
/samples/console/Console.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp3.1
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/samples/aspnetcore/WeatherForecast.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace FireflySoft.RateLimit.AspNetCore.Sample
4 | {
5 | public class WeatherForecast
6 | {
7 | public DateTime Date { get; set; }
8 |
9 | public int TemperatureC { get; set; }
10 |
11 | public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
12 |
13 | public string Summary { get; set; }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/FireflySoft.RateLimit.Core.Test/RedisClientHelper.cs:
--------------------------------------------------------------------------------
1 | public class RedisClientHelper{
2 | private static StackExchange.Redis.ConnectionMultiplexer _redisClient;
3 | public static StackExchange.Redis.ConnectionMultiplexer GetClient()
4 | {
5 | _redisClient = StackExchange.Redis.ConnectionMultiplexer.Connect("127.0.0.1");
6 | _redisClient.GetDatabase(0).StringGet("TestConnect");
7 | return _redisClient;
8 | }
9 | }
--------------------------------------------------------------------------------
/samples/aspnet/Models/WeatherForecast.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace FireflySoft.RateLimit.AspNet.Sample.Models
4 | {
5 | public class WeatherForecast
6 | {
7 | public DateTime Date { get; set; }
8 |
9 | public int TemperatureC { get; set; }
10 |
11 | public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
12 |
13 | public string Summary { get; set; }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/samples/aspnetcore/AspNetCore.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.1
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/FireflySoft.RateLmit.Core.BenchmarkTest/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using BenchmarkDotNet.Attributes;
3 | using BenchmarkDotNet.Running;
4 | using BenchmarkDotNet.Validators;
5 |
6 | namespace FireflySoft.RateLmit.Core.BenchmarkTest
7 | {
8 | class Program
9 | {
10 | static void Main(string[] args)
11 | {
12 | var benchmark = BenchmarkRunner.Run();
13 | Console.Read();
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/FireflySoft.RateLimit.AspNetCore/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "iisSettings": {
3 | "windowsAuthentication": false,
4 | "anonymousAuthentication": true
5 | },
6 | "profiles": {
7 | "FireflySoft.RateLimit.AspNetCore": {
8 | "commandName": "Project",
9 | "launchBrowser": true,
10 | "applicationUrl": "http://localhost:20120",
11 | "environmentVariables": {
12 | "ASPNETCORE_ENVIRONMENT": "Development"
13 | }
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/FireflySoft.RateLmit.Core.BenchmarkTest/FireflySoft.RateLmit.Core.BenchmarkTest.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp3.1;net6;
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/samples/IPOrClientId/IPOrClientId.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 | enable
6 | enable
7 | latest
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/samples/aspnetcore/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json.schemastore.org/launchsettings.json",
3 | "iisSettings": {
4 | "windowsAuthentication": false,
5 | "anonymousAuthentication": true,
6 | "iisExpress": {
7 | "applicationUrl": "http://localhost:44035",
8 | "sslPort": 44352
9 | }
10 | },
11 | "profiles": {
12 | "IIS Express": {
13 | "commandName": "IISExpress",
14 | "launchBrowser": true,
15 | "launchUrl": "weatherforecast/today",
16 | "environmentVariables": {
17 | "ASPNETCORE_ENVIRONMENT": "Development"
18 | }
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/samples/aspnet/App_Start/WebApiConfig.cs:
--------------------------------------------------------------------------------
1 | using System.Web.Http;
2 |
3 | namespace FireflySoft.RateLimit.AspNet.Sample
4 | {
5 | public static class WebApiConfig
6 | {
7 | public static void Register(HttpConfiguration config)
8 | {
9 | // Web API configuration and services
10 |
11 | // Web API routes
12 | config.MapHttpAttributeRoutes();
13 |
14 | config.Routes.MapHttpRoute(
15 | name: "DefaultApi",
16 | routeTemplate: "{controller}/{id}",
17 | defaults: new { id = RouteParameter.Optional }
18 | );
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/FireflySoft.RateLimit.Core/InProcessAlgorithm/TokenBucketCounter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace FireflySoft.RateLimit.Core.InProcessAlgorithm
4 | {
5 | ///
6 | /// Define a counter for token bucket algorithm
7 | ///
8 | public class TokenBucketCounter
9 | {
10 | ///
11 | /// The Count Value
12 | ///
13 | ///
14 | public long Value { get; set; }
15 |
16 | ///
17 | /// The last inflow time
18 | ///
19 | ///
20 | public DateTimeOffset LastInflowTime { get; set; }
21 | }
22 | }
--------------------------------------------------------------------------------
/FireflySoft.RateLimit.AspNet/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/FireflySoft.RateLimit.Core/Rule/StartTimeType.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | namespace FireflySoft.RateLimit.Core
3 | {
4 | ///
5 | /// The type of statistics start time
6 | ///
7 | public enum StartTimeType
8 | {
9 | ///
10 | /// From the current time.
11 | ///
12 | FromCurrent = 1,
13 |
14 | ///
15 | /// From the beginning of the natural period.
16 | /// In this way, the statistical time window must be an integer and coincide with the natural time period.
17 | /// This type is not valid for statistical time windows less than 1 second, which is equivalent to 'FromCurrent' type.
18 | ///
19 | FromNaturalPeriodBeign = 2
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/samples/RuleAutoUpdate/RuleAutoUpdate.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 | enable
6 | enable
7 | latest
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/samples/aspnetcore/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Hosting;
6 | using Microsoft.Extensions.Configuration;
7 | using Microsoft.Extensions.Hosting;
8 | using Microsoft.Extensions.Logging;
9 |
10 | namespace FireflySoft.RateLimit.AspNetCore.Sample
11 | {
12 | public class Program
13 | {
14 | public static void Main(string[] args)
15 | {
16 | CreateHostBuilder(args).Build().Run();
17 | }
18 |
19 | public static IHostBuilder CreateHostBuilder(string[] args) =>
20 | Host.CreateDefaultBuilder(args)
21 | .ConfigureWebHostDefaults(webBuilder =>
22 | {
23 | webBuilder.UseStartup();
24 | });
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/samples/RuleAutoUpdate/RateLimit/AutoUpdateAlgorithmService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace RuleAutoUpdate.RateLimit
4 | {
5 | public static class AutoUpdateAlgorithmService
6 | {
7 | ///
8 | /// Add auto update rate limit algorithm service
9 | ///
10 | ///
11 | ///
12 | ///
13 | ///
14 | ///
15 | public static IServiceCollection AddAutoUpdateRateLimitAlgorithm(this IServiceCollection services)
16 | {
17 | services.AddSingleton();
18 | services.AddSingleton();
19 | services.AddSingleton();
20 | return services;
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/samples/IPOrClientId/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/launchsettings.json",
3 | "iisSettings": {
4 | "windowsAuthentication": false,
5 | "anonymousAuthentication": true,
6 | "iisExpress": {
7 | "applicationUrl": "http://localhost:17675",
8 | "sslPort": 44356
9 | }
10 | },
11 | "profiles": {
12 | "IPOrClientId": {
13 | "commandName": "Project",
14 | "dotnetRunMessages": true,
15 | "launchBrowser": true,
16 | "launchUrl": "swagger",
17 | "applicationUrl": "https://localhost:7137;http://localhost:5217",
18 | "environmentVariables": {
19 | "ASPNETCORE_ENVIRONMENT": "Development"
20 | }
21 | },
22 | "IIS Express": {
23 | "commandName": "IISExpress",
24 | "launchBrowser": true,
25 | "launchUrl": "swagger",
26 | "environmentVariables": {
27 | "ASPNETCORE_ENVIRONMENT": "Development"
28 | }
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/samples/RuleAutoUpdate/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/launchsettings.json",
3 | "iisSettings": {
4 | "windowsAuthentication": false,
5 | "anonymousAuthentication": true,
6 | "iisExpress": {
7 | "applicationUrl": "http://localhost:5777",
8 | "sslPort": 44342
9 | }
10 | },
11 | "profiles": {
12 | "aspnetcore6": {
13 | "commandName": "Project",
14 | "dotnetRunMessages": true,
15 | "launchBrowser": true,
16 | "launchUrl": "swagger",
17 | "applicationUrl": "https://localhost:7265;http://localhost:5092",
18 | "environmentVariables": {
19 | "ASPNETCORE_ENVIRONMENT": "Development"
20 | }
21 | },
22 | "IIS Express": {
23 | "commandName": "IISExpress",
24 | "launchBrowser": true,
25 | "launchUrl": "swagger",
26 | "environmentVariables": {
27 | "ASPNETCORE_ENVIRONMENT": "Development"
28 | }
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/FireflySoft.RateLimit.Core.Test/FireflySoft.RateLimit.Core.Test.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.1
5 | false
6 |
7 |
8 |
9 |
10 | runtime; build; native; contentfiles; analyzers; buildtransitive
11 | all
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/FireflySoft.RateLimit.Core/InProcessAlgorithm/LeakyBucketCounter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.Threading.Tasks;
5 | using FireflySoft.RateLimit.Core.Rule;
6 | using FireflySoft.RateLimit.Core.Time;
7 |
8 | namespace FireflySoft.RateLimit.Core.InProcessAlgorithm
9 | {
10 | ///
11 | /// Define a counter for leaky bucket algorithm
12 | ///
13 | public class LeakyBucketCounter
14 | {
15 | ///
16 | /// The number of requests that allowed to be processed in the current time window,
17 | /// including the requests in the leaky bucket and the requests that have flowed out in the current time window.
18 | ///
19 | ///
20 | public long Value { get; set; }
21 |
22 | ///
23 | /// The last flow-out time
24 | ///
25 | ///
26 | public DateTimeOffset LastFlowOutTime { get; set; }
27 | }
28 | }
--------------------------------------------------------------------------------
/FireflySoft.RateLimit.Core/InProcessAlgorithm/FixedWindowCounter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.Threading.Tasks;
5 | using FireflySoft.RateLimit.Core.Rule;
6 | using FireflySoft.RateLimit.Core.Time;
7 |
8 | namespace FireflySoft.RateLimit.Core.InProcessAlgorithm
9 | {
10 | ///
11 | /// Define a counter for fixed window algorithm
12 | ///
13 | public class FixedWindowCounter
14 | {
15 | ///
16 | /// The Count Value
17 | ///
18 | ///
19 | public long Value { get; set; }
20 |
21 | ///
22 | /// The start time of current window
23 | ///
24 | ///
25 | public DateTimeOffset StartTime { get; set; }
26 |
27 | ///
28 | /// The statistical time window
29 | ///
30 | ///
31 | public TimeSpan StatWindow { get; set; }
32 | }
33 | }
--------------------------------------------------------------------------------
/samples/RuleAutoUpdate/RateLimit/NonCapturingTimer.cs:
--------------------------------------------------------------------------------
1 | namespace RuleAutoUpdate.RateLimit;
2 |
3 | internal static class NonCapturingTimer
4 | {
5 | public static Timer Create(TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period)
6 | {
7 | if (callback == null)
8 | {
9 | throw new ArgumentNullException(nameof(callback));
10 | }
11 |
12 | // Don't capture the current ExecutionContext and its AsyncLocals onto the timer
13 | bool restoreFlow = false;
14 | try
15 | {
16 | if (!ExecutionContext.IsFlowSuppressed())
17 | {
18 | ExecutionContext.SuppressFlow();
19 | restoreFlow = true;
20 | }
21 |
22 | return new Timer(callback, state, dueTime, period);
23 | }
24 | finally
25 | {
26 | // Restore the current ExecutionContext
27 | if (restoreFlow)
28 | {
29 | ExecutionContext.RestoreFlow();
30 | }
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/FireflySoft.RateLimit.Core/Rule/FixedWindowRule.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace FireflySoft.RateLimit.Core.Rule
5 | {
6 | ///
7 | /// Fixed Window Algorithm
8 | ///
9 | public class FixedWindowRule : RateLimitRule
10 | {
11 | ///
12 | /// The statistical time window, which counts the number of requests in this time.
13 | /// When using redis storage, it needs to be an integral multiple of one second.
14 | ///
15 | public TimeSpan StatWindow { get; set; }
16 |
17 | ///
18 | /// The threshold of triggering rate limiting in the statistical time window.
19 | /// If less than 0, it means no limit.
20 | ///
21 | public int LimitNumber { get; set; }
22 |
23 | ///
24 | /// Get the rate limit threshold.
25 | ///
26 | ///
27 | public override long GetLimitThreshold()
28 | {
29 | return LimitNumber;
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/samples/aspnet/Controllers/WeatherForecastController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Web.Http;
5 | using FireflySoft.RateLimit.AspNet.Sample.Models;
6 |
7 | namespace FireflySoft.RateLimit.AspNet.Sample.Controllers
8 | {
9 | public class WeatherForecastController : ApiController
10 | {
11 | private static readonly string[] Summaries = new[]
12 | {
13 | "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
14 | };
15 |
16 | public WeatherForecastController()
17 | {
18 | }
19 |
20 | [HttpGet]
21 | public IEnumerable Get()
22 | {
23 | var rng = new Random();
24 | return Enumerable.Range(1, 5).Select(index => new WeatherForecast
25 | {
26 | Date = DateTime.Now.AddDays(index),
27 | TemperatureC = rng.Next(-20, 55),
28 | Summary = Summaries[rng.Next(Summaries.Length)]
29 | })
30 | .ToArray();
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/FireflySoft.RateLimit.AspNet/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 |
4 | // Information about this assembly is defined by the following attributes.
5 | // Change them to the values specific to your project.
6 |
7 | [assembly: AssemblyTitle("FireflySoft.RateLimit.AspNet")]
8 | [assembly: AssemblyDescription("")]
9 | [assembly: AssemblyConfiguration("")]
10 | [assembly: AssemblyCompany("fireflysoft.net")]
11 | [assembly: AssemblyProduct("")]
12 | [assembly: AssemblyCopyright("${AuthorCopyright}")]
13 | [assembly: AssemblyTrademark("")]
14 | [assembly: AssemblyCulture("")]
15 |
16 | // The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
17 | // The form "{Major}.{Minor}.*" will automatically update the build and revision,
18 | // and "{Major}.{Minor}.{Build}.*" will update just the revision.
19 |
20 | [assembly: AssemblyVersion("1.2.0")]
21 | [assembly: AssemblyFileVersion("1.2.0")]
22 |
23 | // The following attributes are used to specify the signing key for the assembly,
24 | // if desired. See the Mono documentation for more information about signing.
25 |
26 | //[assembly: AssemblyDelaySign(false)]
27 | //[assembly: AssemblyKeyFile("")]
28 |
--------------------------------------------------------------------------------
/samples/RuleAutoUpdate/Program.cs:
--------------------------------------------------------------------------------
1 | using RuleAutoUpdate.RateLimit;
2 | using FireflySoft.RateLimit.AspNetCore;
3 |
4 | var builder = WebApplication.CreateBuilder(args);
5 |
6 | // Add services to the container.
7 |
8 | builder.Services.AddControllers();
9 | // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
10 | builder.Services.AddEndpointsApiExplorer();
11 | builder.Services.AddSwaggerGen();
12 |
13 | // Add firefly soft rate limit service
14 | builder.Services.AddAutoUpdateRateLimitAlgorithm();
15 | builder.Services.AddRateLimit(serviceProvider =>
16 | {
17 | var algorithmManager = serviceProvider.GetService();
18 | if (algorithmManager != null)
19 | {
20 | return algorithmManager.GetAlgorithmInstance();
21 | }
22 |
23 | return null;
24 | });
25 |
26 | var app = builder.Build();
27 |
28 | // Configure the HTTP request pipeline.
29 | if (app.Environment.IsDevelopment())
30 | {
31 | app.UseSwagger();
32 | app.UseSwaggerUI();
33 | }
34 |
35 | app.UseHttpsRedirection();
36 |
37 | app.UseAuthorization();
38 |
39 | // Use firefly soft rate limit middleware
40 | app.UseRateLimit();
41 |
42 | app.MapControllers();
43 |
44 | app.Run();
45 |
--------------------------------------------------------------------------------
/FireflySoft.RateLimit.Core/AlgorithmCheckResult.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading;
5 |
6 | namespace FireflySoft.RateLimit.Core
7 | {
8 | ///
9 | /// Algorithm Check Result
10 | ///
11 | public class AlgorithmCheckResult
12 | {
13 | private IEnumerable _ruleCheckResults;
14 |
15 | ///
16 | /// Create a new instance.
17 | ///
18 | ///
19 | public AlgorithmCheckResult(IEnumerable ruleCheckResults)
20 | {
21 | _ruleCheckResults = ruleCheckResults;
22 | }
23 |
24 | ///
25 | /// If true, it means that the current request should be limited
26 | ///
27 | ///
28 | public bool IsLimit
29 | {
30 | get
31 | {
32 | return _ruleCheckResults.Any(d => d.IsLimit);
33 | }
34 | }
35 |
36 | ///
37 | /// The rule check results.
38 | ///
39 | ///
40 | public IEnumerable RuleCheckResults
41 | {
42 | get
43 | {
44 | return _ruleCheckResults;
45 | }
46 | }
47 | }
48 | }
--------------------------------------------------------------------------------
/samples/aspnet/Global.asax.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net.Http;
3 | using System.Web;
4 | using System.Web.Http;
5 | using FireflySoft.RateLimit.Core;
6 | using FireflySoft.RateLimit.Core.Rule;
7 |
8 | namespace FireflySoft.RateLimit.AspNet.Sample
9 | {
10 | public class Global : HttpApplication
11 | {
12 | protected void Application_Start()
13 | {
14 | GlobalConfiguration.Configuration.MessageHandlers.Add(new RateLimitHandler(
15 | new Core.InProcessAlgorithm.InProcessFixedWindowAlgorithm(
16 | new[] {
17 | new FixedWindowRule()
18 | {
19 | ExtractTarget = context =>
20 | {
21 | return (context as HttpRequestMessage).RequestUri.AbsolutePath;
22 | },
23 | CheckRuleMatching = context =>
24 | {
25 | return true;
26 | },
27 | Name="default limit rule",
28 | LimitNumber=30,
29 | StatWindow=TimeSpan.FromSeconds(1)
30 | }
31 | })
32 | ));
33 |
34 | GlobalConfiguration.Configure(WebApiConfig.Register);
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/samples/aspnetcore/Controllers/WeatherForecastController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Mvc;
6 | using Microsoft.Extensions.Logging;
7 |
8 | namespace FireflySoft.RateLimit.AspNetCore.Sample.Controllers
9 | {
10 | [ApiController]
11 | [Route("[controller]")]
12 | public class WeatherForecastController : ControllerBase
13 | {
14 | private static readonly string[] Summaries = new[]
15 | {
16 | "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
17 | };
18 |
19 | private readonly ILogger _logger;
20 |
21 | public WeatherForecastController(ILogger logger)
22 | {
23 | _logger = logger;
24 | }
25 |
26 | [HttpGet]
27 | public IEnumerable Get()
28 | {
29 | var rng = new Random();
30 | return Enumerable.Range(1, 5).Select(index => new WeatherForecast
31 | {
32 | Date = DateTime.Now.AddDays(index),
33 | TemperatureC = rng.Next(-20, 55),
34 | Summary = Summaries[rng.Next(Summaries.Length)]
35 | })
36 | .ToArray();
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/FireflySoft.RateLimit.Core/Time/ITimeProvider.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 |
4 | namespace FireflySoft.RateLimit.Core.Time
5 | {
6 | ///
7 | /// The interface for time provider
8 | ///
9 | public interface ITimeProvider
10 | {
11 | ///
12 | /// Get the milliseconds of current unix time
13 | ///
14 | ///
15 | long GetCurrentUtcMilliseconds();
16 |
17 | ///
18 | /// Get current utc time
19 | ///
20 | ///
21 | DateTimeOffset GetCurrentUtcTime();
22 |
23 | ///
24 | /// Get current local time
25 | ///
26 | ///
27 | DateTimeOffset GetCurrentLocalTime();
28 |
29 | ///
30 | /// Get the milliseconds of current unix time
31 | ///
32 | ///
33 | Task GetCurrentUtcMillisecondsAsync();
34 |
35 | ///
36 | /// Get current local time
37 | ///
38 | ///
39 | Task GetCurrentLocalTimeAsync();
40 |
41 | ///
42 | /// Get current utc time
43 | ///
44 | ///
45 | Task GetCurrentUtcTimeAsync();
46 | }
47 | }
--------------------------------------------------------------------------------
/samples/IPOrClientId/Controllers/WeatherForecastController.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Mvc;
2 |
3 | namespace IPOrClientId.Controllers;
4 |
5 | [ApiController]
6 | [Route("[controller]")]
7 | public class WeatherForecastController : ControllerBase
8 | {
9 | private static readonly string[] Summaries = new[]
10 | {
11 | "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
12 | };
13 |
14 | private readonly ILogger _logger;
15 |
16 | public WeatherForecastController(ILogger logger)
17 | {
18 | _logger = logger;
19 | }
20 |
21 | [HttpGet("Future")]
22 | public IEnumerable GetFuture()
23 | {
24 | return Enumerable.Range(1, 5).Select(index => new WeatherForecast
25 | {
26 | Date = DateTime.Now.AddDays(index),
27 | TemperatureC = Random.Shared.Next(-20, 55),
28 | Summary = Summaries[Random.Shared.Next(Summaries.Length)]
29 | })
30 | .ToArray();
31 | }
32 |
33 | [HttpGet("Today")]
34 | public WeatherForecast GetToday()
35 | {
36 | return new WeatherForecast
37 | {
38 | Date = DateTime.Now,
39 | TemperatureC = Random.Shared.Next(-20, 55),
40 | Summary = Summaries[Random.Shared.Next(Summaries.Length)]
41 | };
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/samples/RuleAutoUpdate/Controllers/WeatherForecastController.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Mvc;
2 |
3 | namespace RuleAutoUpdate.Controllers;
4 |
5 | [ApiController]
6 | [Route("[controller]")]
7 | public class WeatherForecastController : ControllerBase
8 | {
9 | private static readonly string[] Summaries = new[]
10 | {
11 | "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
12 | };
13 |
14 | private readonly ILogger _logger;
15 |
16 | public WeatherForecastController(ILogger logger)
17 | {
18 | _logger = logger;
19 | }
20 |
21 | [HttpGet("GetToday")]
22 | public IEnumerable GetToday(string userId)
23 | {
24 | return Enumerable.Range(1, 5).Select(index => new WeatherForecast
25 | {
26 | Date = DateTime.Now.AddDays(index),
27 | TemperatureC = Random.Shared.Next(-20, 55),
28 | Summary = Summaries[Random.Shared.Next(Summaries.Length)]
29 | })
30 | .ToArray();
31 | }
32 |
33 | [HttpGet("GetTomorrow")]
34 | public IEnumerable GetTomorrow(string userId)
35 | {
36 | return Enumerable.Range(1, 5).Select(index => new WeatherForecast
37 | {
38 | Date = DateTime.Now.AddDays(index),
39 | TemperatureC = Random.Shared.Next(-20, 55),
40 | Summary = Summaries[Random.Shared.Next(Summaries.Length)]
41 | })
42 | .ToArray();
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/FireflySoft.RateLimit.Core/IAlgorithm.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Threading.Tasks;
3 | using FireflySoft.RateLimit.Core.Rule;
4 |
5 | namespace FireflySoft.RateLimit.Core
6 | {
7 | ///
8 | /// Defines a mechanism for using rate limit algorithm.
9 | ///
10 | public interface IAlgorithm
11 | {
12 | ///
13 | /// Check the request and return the rate limit result
14 | ///
15 | ///
16 | ///
17 | AlgorithmCheckResult Check(object request);
18 |
19 | ///
20 | /// Check the request and return the rate limit result
21 | ///
22 | ///
23 | ///
24 | Task CheckAsync(object request);
25 |
26 | ///
27 | /// Update the rate limit rules
28 | ///
29 | ///
30 | void UpdateRules(IEnumerable rules);
31 |
32 | ///
33 | /// Update the rate limit rules
34 | ///
35 | ///
36 | Task UpdateRulesAsync(IEnumerable rules);
37 |
38 | ///
39 | /// Peek at the rate limit check results at the current time.
40 | ///
41 | ///
42 | ///
43 | AlgorithmCheckResult Peek(string target);
44 |
45 | ///
46 | /// Peek at the rate limit check results at the current time.
47 | ///
48 | ///
49 | ///
50 | Task PeekAsync(string target);
51 | }
52 | }
--------------------------------------------------------------------------------
/FireflySoft.RateLimit.AspNetCore/FireflySoft.RateLimit.AspNetCore.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.1;net5;net6
5 | Library
6 | FireflySoft.RateLimit.AspNetCore
7 | 3.0.0
8 | bossma
9 | https://github.com/bosima/FireflySoft.RateLimit
10 |
11 | Return X-RateLimit-XXX in HTTP response.
12 |
13 | ASP.NET Core;Rate Limit;Fixed Window;Sliding Window;Leaky Bucket;Token Bucket
14 |
15 | A rate limit library for ASP.NET Core.
16 |
17 | true
18 | true
19 | Apache-2.0
20 | true
21 | 3.0.0.0
22 | 3.0.0.0
23 |
24 |
25 |
26 |
27 | runtime; build; native; contentfiles; analyzers; buildtransitive
28 | all
29 |
30 |
31 |
32 |
33 |
34 |
35 | README.md
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/FireflySoft.RateLimit.AspNet/HttpRateLimitError.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Net.Http;
4 | using System.Threading.Tasks;
5 | using FireflySoft.RateLimit.Core;
6 |
7 | namespace FireflySoft.RateLimit.AspNet
8 | {
9 | ///
10 | /// Rate Limit Error
11 | ///
12 | public class HttpRateLimitError
13 | {
14 | ///
15 | /// Create a new instance
16 | ///
17 | public HttpRateLimitError()
18 | {
19 | }
20 |
21 | ///
22 | /// Get or set the http response status code.
23 | ///
24 | ///
25 | public int HttpStatusCode { get; set; } = 429;
26 |
27 | ///
28 | /// A delegates that defines from which response headers are builded.
29 | ///
30 | ///
31 | public Func> BuildHttpHeaders { get; set; }
32 |
33 | ///
34 | /// A delegates that defines from which response content are builded.
35 | ///
36 | ///
37 | public Func BuildHttpContent { get; set; }
38 |
39 | ///
40 | /// A delegates that defines from which response headers are builded.
41 | ///
42 | ///
43 | public Func>> BuildHttpHeadersAsync { get; set; }
44 |
45 | ///
46 | /// A delegates that defines from which response content are builded.
47 | ///
48 | ///
49 | public Func> BuildHttpContentAsync { get; set; }
50 | }
51 | }
--------------------------------------------------------------------------------
/FireflySoft.RateLimit.Core/RuleCheckResult.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using FireflySoft.RateLimit.Core.Rule;
3 |
4 | namespace FireflySoft.RateLimit.Core
5 | {
6 | ///
7 | /// Defines the result of single rule check
8 | ///
9 | public class RuleCheckResult
10 | {
11 | ///
12 | /// If true, it means that the current request should be limited
13 | ///
14 | ///
15 | public bool IsLimit { get; set; }
16 |
17 | ///
18 | /// The time to open the next time window,
19 | /// or the time when the rate limiting lock ends.
20 | ///
21 | ///
22 | public DateTimeOffset ResetTime { get; set; }
23 |
24 | ///
25 | /// The number of requests passed in the current time window.
26 | ///
27 | ///
28 | public long Count { get; set; }
29 |
30 | ///
31 | /// The number of requests remaining in the current time window that will not be limited.
32 | ///
33 | ///
34 | ///
35 | public long Remaining { get; set; }
36 |
37 | ///
38 | /// The queue waiting time of the current request, which is only for the leaky bucket algorithm.
39 | /// With Task.Dealy, you can simulate queue processing requests.
40 | ///
41 | ///
42 | public long Wait { get; set; } = -1;
43 |
44 | ///
45 | /// The current rate limit target
46 | ///
47 | ///
48 | public string Target { get; set; }
49 |
50 | ///
51 | /// The current rule
52 | ///
53 | ///
54 | public RateLimitRule Rule { get; set; }
55 | }
56 | }
--------------------------------------------------------------------------------
/FireflySoft.RateLimit.Core.Test/CounterDictionaryTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 | using FireflySoft.RateLimit.Core.InProcessAlgorithm;
5 | using FireflySoft.RateLimit.Core.Time;
6 | using Microsoft.VisualStudio.TestTools.UnitTesting;
7 |
8 | namespace FireflySoft.RateLimit.Core.Test
9 | {
10 | [TestClass]
11 | public class CounterDictionaryTest
12 | {
13 | [DataTestMethod]
14 | public void TryGet_ExpiredItem_ReturnFalse()
15 | {
16 | var stubTimeProvider = new TestTimeProvider(TimeSpan.FromMilliseconds(1));
17 | CounterDictionary dic = new CounterDictionary(stubTimeProvider);
18 |
19 | dic.Set("key", new CounterDictionaryItem("key", "value")
20 | {
21 | ExpireTime = DateTimeOffset.Parse("2022-01-01T00:00:20+00:00")
22 | });
23 | stubTimeProvider.IncrementSeconds(21);
24 |
25 | bool result = dic.TryGet("key", out CounterDictionaryItem value);
26 |
27 | // run ScanForExpiredItems
28 | Thread.Sleep(10);
29 |
30 | Assert.AreEqual(false, result);
31 | }
32 |
33 | [DataTestMethod]
34 | public void TryGet_NotExpiredItem_ReturnTrue()
35 | {
36 | var stubTimeProvider = new TestTimeProvider(TimeSpan.FromMilliseconds(1));
37 | CounterDictionary dic = new CounterDictionary(stubTimeProvider);
38 |
39 | dic.Set("key", new CounterDictionaryItem("key", "value")
40 | {
41 | ExpireTime = DateTimeOffset.Parse("2022-01-01T00:00:20+00:00")
42 | });
43 | stubTimeProvider.IncrementSeconds(10);
44 |
45 | bool result = dic.TryGet("key", out CounterDictionaryItem value);
46 |
47 | Assert.AreEqual(true, result);
48 | }
49 | }
50 | }
--------------------------------------------------------------------------------
/samples/RuleAutoUpdate/RateLimit/RateLimitRuleDAO.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.ObjectModel;
2 | using System.Data;
3 | using Dapper;
4 | using MySql.Data.MySqlClient;
5 |
6 | namespace RuleAutoUpdate.RateLimit;
7 |
8 | /*
9 | First execute the sql in mysql.
10 | Then add 'DbConn' section in appsettings.json.
11 |
12 | CREATE TABLE `rate_limit_rule` (
13 | `Id` varchar(40) NOT NULL,
14 | `Path` varchar(100) NOT NULL,
15 | `PathType` int(11) NOT NULL,
16 | `TokenCapacity` int(11) NOT NULL,
17 | `TokenSpeed` int(11) NOT NULL,
18 | `AddTime` datetime NOT NULL,
19 | `UpdateTime` datetime NOT NULL,
20 | PRIMARY KEY (`Id`)
21 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
22 |
23 | INSERT INTO rate_limit_rule (Id,`Path`,PathType,TokenCapacity,TokenSpeed,AddTime,UpdateTime) VALUES
24 | ('1','/WeatherForecast/GetToday',1,26,20,'2021-11-16 00:00:00.0','2021-11-16 00:00:00.0'),
25 | ('2','/WeatherForecast/GetTomorrow',1,13,10,'2021-11-16 00:00:00.0','2021-11-16 00:00:00.0'),
26 | ('3','All',2,29,25,'2021-11-16 00:00:00.0','2021-11-16 00:00:00.0');
27 | */
28 |
29 | public class RateLimitRule
30 | {
31 | public string? Id { get; set; }
32 | public string? Path { get; set; }
33 | public LimitPathType PathType { get; set; }
34 | public int TokenCapacity { get; set; }
35 | public int TokenSpeed { get; set; }
36 | public DateTime AddTime { get; set; }
37 | public DateTime UpdateTime { get; set; }
38 | }
39 |
40 | public class RateLimitRuleDAO
41 | {
42 | private readonly IConfiguration _configuration;
43 |
44 | public RateLimitRuleDAO(IConfiguration configuration)
45 | {
46 | _configuration = configuration;
47 | }
48 |
49 | public async Task> GetAllRulesAsync()
50 | {
51 | var conn = _configuration.GetValue("DbConn");
52 | using (IDbConnection db = new MySqlConnection(conn))
53 | {
54 | return await db.QueryAsync("select * from rate_limit_rule");
55 | }
56 | }
57 | }
--------------------------------------------------------------------------------
/FireflySoft.RateLimit.Core/Time/LocalTimeProvider.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 |
4 | namespace FireflySoft.RateLimit.Core.Time
5 | {
6 | ///
7 | /// Local time provider
8 | ///
9 | public class LocalTimeProvider : ITimeProvider
10 | {
11 | ///
12 | /// Get current utc time
13 | ///
14 | ///
15 | public DateTimeOffset GetCurrentUtcTime()
16 | {
17 | return DateTimeOffset.UtcNow;
18 | }
19 |
20 | ///
21 | /// Get the milliseconds of current unix time
22 | ///
23 | ///
24 | public long GetCurrentUtcMilliseconds()
25 | {
26 | return DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
27 | }
28 |
29 | ///
30 | /// Get current local time
31 | ///
32 | ///
33 | public DateTimeOffset GetCurrentLocalTime()
34 | {
35 | return DateTimeOffset.Now;
36 | }
37 |
38 | ///
39 | /// Get current utc time
40 | ///
41 | ///
42 | public async Task GetCurrentUtcTimeAsync()
43 | {
44 | return await Task.FromResult(GetCurrentUtcTime()).ConfigureAwait(false);
45 | }
46 |
47 | ///
48 | /// Get the milliseconds of current unix time
49 | ///
50 | ///
51 | public async Task GetCurrentUtcMillisecondsAsync()
52 | {
53 | return await Task.FromResult(GetCurrentUtcMilliseconds()).ConfigureAwait(false);
54 | }
55 |
56 | ///
57 | /// Get current local time
58 | ///
59 | ///
60 | public async Task GetCurrentLocalTimeAsync()
61 | {
62 | return await Task.FromResult(GetCurrentLocalTime()).ConfigureAwait(false);
63 | }
64 | }
65 | }
--------------------------------------------------------------------------------
/FireflySoft.RateLimit.AspNetCore/HttpErrorResponse.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 | using FireflySoft.RateLimit.Core;
5 | using Microsoft.AspNetCore.Http;
6 | using Microsoft.Extensions.Primitives;
7 |
8 | namespace FireflySoft.RateLimit.AspNetCore
9 | {
10 | ///
11 | /// Defines the http error response for rate limit
12 | ///
13 | public class HttpErrorResponse
14 | {
15 | ///
16 | /// Create a new instance
17 | ///
18 | public HttpErrorResponse()
19 | {
20 | }
21 |
22 | ///
23 | /// Get or set the http response status code.
24 | ///
25 | ///
26 | public int HttpStatusCode { get; set; } = 429;
27 |
28 | ///
29 | /// A delegates that defines from which response headers are builded.
30 | /// Asynchronous method is preferred, and this method is used when asynchronous method does not exist.
31 | ///
32 | ///
33 | public Func> BuildHttpHeaders { get; set; }
34 |
35 | ///
36 | /// A delegates that defines from which response content are builded.
37 | /// Asynchronous method is preferred, and this method is used when asynchronous method does not exist.
38 | ///
39 | ///
40 | public Func BuildHttpContent { get; set; }
41 |
42 | ///
43 | /// A delegates that defines from which response headers are builded.
44 | ///
45 | ///
46 | public Func>> BuildHttpHeadersAsync { get; set; }
47 |
48 | ///
49 | /// A delegates that defines from which response content are builded.
50 | ///
51 | ///
52 | public Func> BuildHttpContentAsync { get; set; }
53 | }
54 | }
--------------------------------------------------------------------------------
/samples/RuleAutoUpdate/RateLimit/RateLimitConfigurationManager.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.ObjectModel;
2 |
3 | namespace RuleAutoUpdate.RateLimit;
4 |
5 | public class RateLimitConfiguration
6 | {
7 | public string? Path { get; set; }
8 | public LimitPathType PathType { get; set; }
9 | public int TokenCapacity { get; set; }
10 | public int TokenSpeed { get; set; }
11 | }
12 |
13 | public enum LimitPathType
14 | {
15 | Single = 1,
16 | All = 2
17 | }
18 |
19 | public class RateLimitConfiguratioinManager
20 | {
21 | readonly RateLimitRuleDAO _dao;
22 | readonly Timer _checkConfigChangedTimer;
23 | DateTime _lastConfigChangedTime;
24 |
25 | Action>? _action;
26 |
27 | public RateLimitConfiguratioinManager(RateLimitRuleDAO dao)
28 | {
29 | _dao = dao;
30 | _lastConfigChangedTime = DateTime.MinValue;
31 | _checkConfigChangedTimer = NonCapturingTimer.Create(new TimerCallback(CheckConfigChangedTimerCallbackAsync), this, TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(10));
32 | }
33 |
34 | public void Watch(Action> action)
35 | {
36 | _action = action;
37 | }
38 |
39 | private async void CheckConfigChangedTimerCallbackAsync(object? state)
40 | {
41 | var rules = await _dao.GetAllRulesAsync();
42 | if (rules.Any())
43 | {
44 | var latestChangedTime = rules.OrderByDescending(d => d.UpdateTime).Select(d => d.UpdateTime).First();
45 | if (latestChangedTime > _lastConfigChangedTime)
46 | {
47 |
48 | var configs = rules.Select(d =>
49 | {
50 | return new RateLimitConfiguration()
51 | {
52 | Path = d.Path,
53 | PathType = d.PathType,
54 | TokenCapacity = d.TokenCapacity,
55 | TokenSpeed = d.TokenSpeed
56 | };
57 | });
58 |
59 | _action?.Invoke(configs);
60 |
61 | _lastConfigChangedTime = latestChangedTime;
62 | }
63 | }
64 | }
65 | }
--------------------------------------------------------------------------------
/FireflySoft.RateLimit.Core.Test/TestTimeProvider.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using FireflySoft.RateLimit.Core.Time;
4 |
5 | namespace FireflySoft.RateLimit.Core.Test
6 | {
7 | internal class TestTimeProvider : ITimeProvider
8 | {
9 | private DateTimeOffset _startTime;
10 |
11 | private TimeSpan _interval;
12 |
13 | public TestTimeProvider(TimeSpan interval)
14 | {
15 | _startTime = DateTimeOffset.Parse("2022-01-01T00:00:00+00:00");
16 | _interval = interval;
17 | }
18 |
19 | public TestTimeProvider(DateTimeOffset startTime, TimeSpan interval)
20 | {
21 | _startTime = startTime;
22 | _interval = interval;
23 | }
24 |
25 | public long GetCurrentUtcMilliseconds()
26 | {
27 | return _startTime.ToUnixTimeMilliseconds();
28 | }
29 |
30 | public DateTimeOffset GetCurrentUtcTime()
31 | {
32 | return _startTime;
33 | }
34 |
35 | public DateTimeOffset GetCurrentLocalTime()
36 | {
37 | return _startTime.ToLocalTime();
38 | }
39 |
40 | public Task GetCurrentUtcMillisecondsAsync()
41 | {
42 | return Task.FromResult(GetCurrentUtcMilliseconds());
43 | }
44 |
45 | public Task GetCurrentLocalTimeAsync()
46 | {
47 | return Task.FromResult(GetCurrentLocalTime());
48 | }
49 |
50 | public Task GetCurrentUtcTimeAsync()
51 | {
52 | return Task.FromResult(GetCurrentUtcTime());
53 | }
54 |
55 | public void Increment()
56 | {
57 | _startTime = _startTime.Add(_interval);
58 | }
59 |
60 | public void Increment(TimeSpan interval)
61 | {
62 | _startTime = _startTime.Add(interval);
63 | }
64 |
65 | public void IncrementMilliseconds(int milliseconds)
66 | {
67 | Increment(TimeSpan.FromMilliseconds(milliseconds));
68 | }
69 |
70 | public void IncrementSeconds(int seconds)
71 | {
72 | Increment(TimeSpan.FromSeconds(seconds));
73 | }
74 | }
75 | }
--------------------------------------------------------------------------------
/FireflySoft.RateLimit.Core.Test/LocalTimeProviderTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 | using FireflySoft.RateLimit.Core.Time;
5 | using Microsoft.VisualStudio.TestTools.UnitTesting;
6 |
7 | namespace FireflySoft.RateLimit.Core.Test
8 | {
9 | [TestClass]
10 | public class LocalTimeProviderTest
11 | {
12 | [DataTestMethod]
13 | public void TestGetCurrentUtcTime()
14 | {
15 | var currentUtcTime = GetTimeProvider().GetCurrentUtcTime();
16 | Assert.AreEqual(true, currentUtcTime <= DateTimeOffset.UtcNow);
17 | }
18 |
19 | [DataTestMethod]
20 | public void TestGetCurrentUtcMilliseconds()
21 | {
22 | var currentTs = GetTimeProvider().GetCurrentUtcMilliseconds();
23 | Assert.AreEqual(true, currentTs <= DateTimeOffset.UtcNow.ToUnixTimeMilliseconds());
24 | }
25 |
26 | [DataTestMethod]
27 | public void TestGetCurrentLocalTime()
28 | {
29 | var localTime = GetTimeProvider().GetCurrentLocalTime();
30 | Assert.AreEqual(true, localTime <= DateTimeOffset.Now);
31 | }
32 |
33 | [DataTestMethod]
34 | public async Task TestGetCurrentUtcTimeAsync()
35 | {
36 | var currentUtcTime = await GetTimeProvider().GetCurrentUtcTimeAsync();
37 | Assert.AreEqual(true, currentUtcTime.Offset == TimeSpan.FromHours(0));
38 | Assert.AreEqual(true, currentUtcTime <= DateTimeOffset.UtcNow);
39 | }
40 |
41 | [DataTestMethod]
42 | public async Task TestGetCurrentUtcMillisecondsAsync()
43 | {
44 | var currentTs = await GetTimeProvider().GetCurrentUtcMillisecondsAsync();
45 | Assert.AreEqual(true, currentTs <= DateTimeOffset.UtcNow.ToUnixTimeMilliseconds());
46 | }
47 |
48 | [DataTestMethod]
49 | public async Task TestGetCurrentLocalTimeAsync()
50 | {
51 | var localTime = await GetTimeProvider().GetCurrentLocalTimeAsync();
52 | Assert.AreEqual(true, localTime <= DateTimeOffset.Now);
53 | }
54 |
55 | private ITimeProvider GetTimeProvider()
56 | {
57 | return new LocalTimeProvider();
58 | }
59 | }
60 | }
--------------------------------------------------------------------------------
/FireflySoft.RateLimit.Core.Test/RedisTimeProviderTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 | using FireflySoft.RateLimit.Core.Time;
5 | using Microsoft.VisualStudio.TestTools.UnitTesting;
6 |
7 | namespace FireflySoft.RateLimit.Core.Test
8 | {
9 | [TestClass]
10 | public class RedisTimeProviderTest
11 | {
12 | [DataTestMethod]
13 | public void TestGetCurrentUtcTime()
14 | {
15 | var currentUtcTime = GetTimeProvider().GetCurrentUtcTime();
16 | Assert.AreEqual(true, currentUtcTime.Year >= 2021);
17 | }
18 |
19 | [DataTestMethod]
20 | public void TestGetCurrentUtcMilliseconds()
21 | {
22 | var currentTs = GetTimeProvider().GetCurrentUtcMilliseconds();
23 | Assert.AreEqual(true, currentTs > DateTimeOffset.Parse("2021-1-1").ToUnixTimeMilliseconds());
24 | }
25 |
26 | [DataTestMethod]
27 | public void TestGetCurrentLocalTime()
28 | {
29 | var localTime = GetTimeProvider().GetCurrentLocalTime();
30 | Assert.AreEqual(true, localTime.Year >= 2021);
31 | }
32 |
33 | [DataTestMethod]
34 | public async Task TestGetCurrentUtcTimeAsync()
35 | {
36 | var currentUtcTime = await GetTimeProvider().GetCurrentUtcTimeAsync();
37 | Assert.AreEqual(true, currentUtcTime.Offset == TimeSpan.FromHours(0));
38 | Assert.AreEqual(true, currentUtcTime.Year >= 2021);
39 | }
40 |
41 | [DataTestMethod]
42 | public async Task TestGetCurrentUtcMillisecondsAsync()
43 | {
44 | var currentTs = await GetTimeProvider().GetCurrentUtcMillisecondsAsync();
45 | Assert.AreEqual(true, currentTs > DateTimeOffset.Parse("2021-1-1").ToUnixTimeMilliseconds());
46 | }
47 |
48 | [DataTestMethod]
49 | public async Task TestGetCurrentLocalTimeAsync()
50 | {
51 | var localTime = await GetTimeProvider().GetCurrentLocalTimeAsync();
52 | Assert.AreEqual(true, localTime.Year >= 2021);
53 | }
54 |
55 | private ITimeProvider GetTimeProvider()
56 | {
57 | return new RedisTimeProvider(RedisClientHelper.GetClient());
58 | }
59 | }
60 | }
--------------------------------------------------------------------------------
/FireflySoft.RateLimit.Core/FireflySoft.RateLimit.Core.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | FireflySoft.RateLimit.Core
6 | 3.0.1
7 | bossma
8 | https://github.com/bosima/FireflySoft.RateLimit
9 |
10 | 1. Break change: Modify the Count returned by the token bucket algorithm to the cumulative number of visits in the current time window to be consistent with other algorithms.
11 | 2. Add a property 'Maintaining' to 'RuleCheckResult', which represents the number of remaining allowed requests in the current time window.
12 | 3. Add a property 'ResetTime' to 'RuleCheckResult', which represents the next reset time of the rate limit time window of the current algorithm.
13 | 4. Some other optimizations.
14 |
15 | Rate Limit;Fixed Window;Sliding Window;Leaky Bucket;Token Bucket
16 |
17 | It is a rate limiting library based on .Net standard.
18 |
19 | true
20 | true
21 | Apache-2.0
22 | 3.0.1.0
23 | 3.0.1.0
24 |
25 |
26 | README.md
27 |
28 |
29 |
30 |
31 |
32 |
33 | runtime; build; native; contentfiles; analyzers; buildtransitive
34 | all
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/FireflySoft.RateLimit.Core/Rule/TokenBucketRule.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace FireflySoft.RateLimit.Core.Rule
5 | {
6 | ///
7 | /// The rule of token bucket algorithm
8 | ///
9 | public class TokenBucketRule : RateLimitRule
10 | {
11 | ///
12 | /// the capacity of token bucket
13 | ///
14 | public int Capacity { get; private set; }
15 |
16 | ///
17 | /// The inflow quantity per unit time
18 | ///
19 | public int InflowQuantityPerUnit { get; private set; }
20 |
21 | ///
22 | /// The time unit of inflow to the token bucket
23 | ///
24 | public TimeSpan InflowUnit { get; private set; }
25 |
26 | ///
27 | /// The min time of fill to the full.
28 | ///
29 | ///
30 | public TimeSpan MinFillTime { get; private set; }
31 |
32 | ///
33 | /// create a new instance
34 | ///
35 | ///
36 | ///
37 | ///
38 | public TokenBucketRule(int capacity, int inflowQuantityPerUnit, TimeSpan inflowUnit)
39 | {
40 | if (capacity < 1)
41 | {
42 | throw new ArgumentException("the capacity can not less than 1.");
43 | }
44 |
45 | if (inflowQuantityPerUnit < 1)
46 | {
47 | throw new ArgumentException("the inflow quantity per unit can not less than 1.");
48 | }
49 |
50 | if (inflowUnit.TotalMilliseconds < 1)
51 | {
52 | throw new ArgumentException("the inflow unit can not less than 1ms.");
53 | }
54 |
55 | Capacity = capacity;
56 | InflowQuantityPerUnit = inflowQuantityPerUnit;
57 | InflowUnit = inflowUnit;
58 | MinFillTime = TimeSpan.FromMilliseconds(((int)Math.Ceiling(capacity / (double)inflowQuantityPerUnit) + 1) * inflowUnit.TotalMilliseconds);
59 | }
60 |
61 | ///
62 | /// Get the rate limit threshold.
63 | ///
64 | ///
65 | public override long GetLimitThreshold()
66 | {
67 | return Capacity;
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/FireflySoft.RateLimit.Core/Rule/RateLimitRule.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 |
4 | namespace FireflySoft.RateLimit.Core.Rule
5 | {
6 | ///
7 | /// The rule of rate limit
8 | ///
9 | public abstract class RateLimitRule
10 | {
11 | ///
12 | /// The identity of the rule, required and cannot be duplicated within the storage currently in use.
13 | /// The default value is a Guid string.
14 | ///
15 | public string Id { get; set; } = Guid.NewGuid().ToString();
16 |
17 | ///
18 | /// The name of the rule.
19 | ///
20 | public string Name { get; set; }
21 |
22 | ///
23 | /// The number of seconds locked after triggering rate limiting. 0 means not locked.
24 | ///
25 | public int LockSeconds { get; set; }
26 |
27 | ///
28 | /// The type of statistics start time
29 | ///
30 | public StartTimeType StartTimeType { get; set; } = StartTimeType.FromCurrent;
31 |
32 | ///
33 | /// Extract the rate limit target from the instance of T, such as a value in HTTP Header. A fixed value can be returned to restrict the access of all users.
34 | ///
35 | public Func