ASP.NET QoS middlewares
ASP.NET Core QoS is a set of high-performance and highly customizable middlewares that allow to set different limits on requests (quotas, rates).
All packages are available in Nuget.
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
// Add base QoS middleware
services.AddQos();
// Allows to use C# expressions directly in the 'appsettings.json' file
services.AddQosExpressionPolicyKeyEvaluator();
// Add middleware that allows special request to bypass the QoS policies
services.Configure<QosVipOptions>(Configuration.GetSection("Vip"));
services.AddQosVip();
// Add concurrency policies
services.Configure<QosConcurrencyOptions>(Configuration.GetSection("Concurrency"));
services.AddQosConcurrency();
// Add rate-limit policies
services.Configure<QosRateLimitOptions>(Configuration.GetSection("RateLimit"));
services.PostConfigure<QosRateLimitOptions>(o =>
{
o.Policies.Add("R_HARDCODED", new QosRateLimitPolicy()
{
MaxCount = 2,
Period = new TimeSpan(0, 0, 30),
UrlTemplates = new[] { "/api/ratelimit2" },
Key = QosPolicyKeyEvaluator.Create(c => c.HttpContext.Connection.RemoteIpAddress.ToString())
});
});
services.AddQosRateLimit();
// Add quotas
services.Configure<QosQuotaOptions>(Configuration.GetSection("Quota"));
services.AddQosQuota();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseQosVip();
app.UseQos();
}
appsettings.json
"Vip": {
"IpAddresses": [
"11.22.33.44",
"22.33.44.55"
]
},
"Concurrency": {
"Policies": {
"C1": {
"UrlTemplates": [ "*" ],
"Key": "@(context.Request.IpAddress)",
"MaxCount": 2
}
}
},
"RateLimit": {
"Policies": {
"R1": {
"UrlTemplates": [ "POST /api/ratelimit/{id}" ],
"Key": "@(context.Request.IpAddress)",
"MaxCount": 3,
"Period": "0:0:10"
}
}
},
"Quota": {
"Policies": {
"Q1": {
"UrlTemplates": [ "/api/quota1", "/api/quota2" ],
"Key": "@(context.Request.Url)",
"MaxCount": 300,
"Period": "0:0:30",
"Distributed": false
}
}
In the example above, there are several policies:
For more detailed information, see the example web site in the Github sources.
You can set different parameters to setup the policies:
"*"
URL is used as wildcard. If an HTTP method is set as first parameter, it is used to restrict the check. No method set means “every method is accepted”.services.AddQosRedisStore(...)
in this case) to create distributed counters. It is possible to mix local and distributed counters depending on the policy.services.AddExpressionPolicyKeyEvaluator()
), it is possible to write key C# expression as strings directly in the ‘appsettings.json’.@(
and )
is considered as a C# expression.@{
and }
is considered as a C# method body (and must include a return...
somewhere).context
variable that contains several properties and methods (see Github AspNetCoreExt.Qos.ExpressionPolicyKeyEvaluator.Internal.Context.DefaultContext
source for details)."*"
means that the key is the same for every request. It is useful to setup a global rate limit policy of 100 calls per second, for example.IQosPolicyKeyEvaluator
, and assign it in the options configuration.Some administration requests can bypass the policies. A white list of special IP addresses can be set for that in a specific middleware.
It is easy to link a policy directly to a MVC action.
The following code…
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddQos();
services.AddQosExpressionPolicyKeyEvaluator();
services.Configure<QosQuotaOptions>(Configuration.GetSection("Quota"));
services.AddQosQuota();
services.AddMvc();
services.AddQosMvc(); // Necessary to be able to use the QoS attribute
}
MyController.cs
public class MyController : ControllerBase
{
[HttpGet("/foo/bar1")]
public string Get1() => "value1";
[HttpGet("/foo/bar2")]
[QosPolicy("MyPolicy")] // The special QoS attribute
public string Get2() => "value2";
}
appsettings.json
"Quota": {
"Policies": {
"MyPolicy": {
"UrlTemplates": [ "/foo/bar1" ],
"Key": "@(context.Request.Url)",
"MaxCount": 300,
"Period": "0:0:30"
}
}
… is equivalent to…
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddQos();
services.AddQosExpressionPolicyKeyEvaluator();
services.Configure<QosQuotaOptions>(Configuration.GetSection("Quota"));
services.AddQosQuota();
services.AddMvc();
}
MyController.cs
public class MyController : ControllerBase
{
[HttpGet("/foo/bar1")]
public string Get1() => "value1";
[HttpGet("/foo/bar2")]
public string Get2() => "value2";
}
appsettings.json
"Quota": {
"Policies": {
"MyPolicy": {
"UrlTemplates": [ "/foo/bar1", "GET /foo/bar2" ],
"Key": "@(context.Request.Url)",
"MaxCount": 300,
"Period": "0:0:30"
}
}
You don’t have to copy/paste the routes.
IQosPolicyGate
.IQosPolicyKeyEvaluatorProvider
.IQosPolicyPostConfigure
.IQosPolicyProvider
.IQosRejectResponse
or the default base class DefaultQosRejectResponse
.IVipFeature
instance into the HttpContext.Features
property before calling the QoS middleware.IQosDistributedCounterStore
in a new class.