throttlingtroll / throttlingtroll Goto Github PK
View Code? Open in Web Editor NEWRate limiting/throttling middleware for ASP.NET Core and Azure Functions
License: MIT License
Rate limiting/throttling middleware for ASP.NET Core and Azure Functions
License: MIT License
This results in counters being not incremented and thus limits not being applied.
Workaround: do not use DistributedCacheCounterStore + Distributed Redis Cache, use RedisCounterStore instead.
This will allow implementing request deduplication.
ASP.NET Core Integration for Azure Functions .NET Isolated has just come out of beta.
A few changes needed to support it:
Most likely, it should be a separate NuGet package (to avoid unwanted dependencies).
So that rules can be applied to particular controllers/methods/functions by placing a ThrottlingTrollAttribute on them.
Hi, can we have a default rule on a URL when using IdentityId?
For example, I tried using this setup:
Rules = new[]
{
new ThrottlingTrollRule
{
UriPattern = "test$",
IdentityId = "f411b023-dfb0-4756-9319-7bc24d11a682",
LimitMethod = new FixedWindowRateLimitMethod
{
IntervalInSeconds = 1000,
PermitLimit = 10
}
},
new ThrottlingTrollRule
{
UriPattern = "test$",
IdentityId = "758a3ff4-0655-4183-9406-20fbc8cb1bcc",
LimitMethod = new FixedWindowRateLimitMethod
{
IntervalInSeconds = 1000,
PermitLimit = 30
}
},
new ThrottlingTrollRule
{
UriPattern = "test$",
LimitMethod = new FixedWindowRateLimitMethod
{
IntervalInSeconds = 1000,
PermitLimit = 1
}
}
}
I expected that anything that didn't have the identity of f411b023-dfb0-4756-9319-7bc24d11a682
or 758a3ff4-0655-4183-9406-20fbc8cb1bcc
would use the third rule but using this setup everything goes through the first rule.
for example, if user have a limit of 20 per minute and 100 per hour and 1000 per day, so we can setup 3 rules for same identity: [{PermitLimit = 20, IntervalInSeconds = 60}, {PermitLimit = 100, IntervalInSeconds = 360}, {PermitLimit = 1000, IntervalInSeconds = 8640}]
the code loops through each rule as expected, but when it register in memory it uses same key for all rules, which make it hit what ever rule limit is lowest.
How to implement limit per identity? Imagine we have 100k clients and different rules (or rules set) per client. The current implementation, as I understand it, requires creating a rule per key and using IdentityId to determine which rule is applicable for the client. In this scenario, I would need to add 100k clients, which probably won't work. I was thinking that instead of using IdentityId, we could create a rule id and in the IdentityIdExtractor, we can decide which rule is applicable for a given id. Converting IdentityIdExtractor to Func<IHttpRequestProxy, class> or Func<IHttpRequestProxy, <string,string> instead of Func<IHttpRequestProxy, string> could solve this. Alternatively, is there an easier way to implement this that I am not seeing?
Azure Functions (isolated .NET worker) can now also have middleware, but that middleware model is specific to Functions.
Need to reimplement and push as a separate package.
Especially useful for Functions: can just reuse underlying Storage account, no need for any extra services.
Hello,
I'm trying to use redis as a counter store but im getting this error: No service for type 'StackExchange.Redis.IConnectionMultiplexer' has been registered
. Im also getting this error on the same project ThrottlingTrollSampleWeb
. The only things I have updated is changing the redisConnString
to be localhost:6379
and uncommented line 82. Below is what in the Program.cs
please let me know if I have missed a step:
using Microsoft.Net.Http.Headers;
using StackExchange.Redis;
using System.Text.Json;
using ThrottlingTroll;
using ThrottlingTroll.CounterStores.Redis;
namespace ThrottlingTrollSampleWeb
{
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, "ThrottlingTrollSampleWeb.xml"));
});
// If RedisConnectionString is specified, then using RedisCounterStore.
// Otherwise the default MemoryCacheCounterStore will be used.
var redisConnString = "localhost:6379";
if (!string.IsNullOrEmpty(redisConnString))
{
builder.Services.AddSingleton<ICounterStore>(
new RedisCounterStore(ConnectionMultiplexer.Connect(redisConnString))
);
}
// <ThrottlingTroll Egress Configuration>
// Configuring a named HttpClient for egress throttling. Rules and limits taken from appsettings.json
builder.Services.AddHttpClient("my-throttled-httpclient").AddThrottlingTrollMessageHandler();
// Configuring a named HttpClient that does automatic retries with respect to Retry-After response header
builder.Services.AddHttpClient("my-retrying-httpclient").AddThrottlingTrollMessageHandler(options =>
{
options.ResponseFabric = async (checkResults, requestProxy, responseProxy, cancelToken) =>
{
var egressResponse = (IEgressHttpResponseProxy)responseProxy;
egressResponse.ShouldRetry = true;
};
});
// </ThrottlingTroll Egress Configuration>
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
// <ThrottlingTroll Ingress Configuration>
// Normally you'll configure ThrottlingTroll just once, but it's OK to have multiple
// middleware instances, with different settings. We're doing it here for demo purposes only.
// Simplest form. Loads config from appsettings.json and uses MemoryCacheCounterStore by default.
app.UseThrottlingTroll();
// Static programmatic configuration
app.UseThrottlingTroll(options =>
{
// Here is how to enable storing rate counters in Redis. You'll also need to add a singleton IConnectionMultiplexer instance beforehand.
options.CounterStore = new RedisCounterStore(app.Services.GetRequiredService<IConnectionMultiplexer>());
options.Config = new ThrottlingTrollConfig
{
Rules = new[]
{
new ThrottlingTrollRule
{
UriPattern = "/fixed-window-1-request-per-2-seconds-configured-programmatically",
LimitMethod = new FixedWindowRateLimitMethod
{
PermitLimit = 1,
IntervalInSeconds = 2
}
}
},
// Specifying UniqueName is needed when multiple services store their
// rate limit counters in the same cache instance, to prevent those services
// from corrupting each other's counters. Otherwise you can skip it.
UniqueName = "MyThrottledService1"
};
});
// Dynamic programmatic configuration. Allows to adjust rules and limits without restarting the service.
app.UseThrottlingTroll(options =>
{
options.GetConfigFunc = async () =>
{
// Loading settings from a custom file. You can instead load them from a database
// or from anywhere else.
string ruleFileName = Path.Combine(AppContext.BaseDirectory, "my-dynamic-throttling-rule.json");
string ruleJson = await File.ReadAllTextAsync(ruleFileName);
var rule = JsonSerializer.Deserialize<ThrottlingTrollRule>(ruleJson);
return new ThrottlingTrollConfig
{
Rules = new[] { rule }
};
};
// The above function will be periodically called every 5 seconds
options.IntervalToReloadConfigInSeconds = 5;
});
// Demonstrates how to use custom response fabrics
app.UseThrottlingTroll(options =>
{
options.Config = new ThrottlingTrollConfig
{
Rules = new[]
{
new ThrottlingTrollRule
{
UriPattern = "/fixed-window-1-request-per-2-seconds-response-fabric",
LimitMethod = new FixedWindowRateLimitMethod
{
PermitLimit = 1,
IntervalInSeconds = 2
}
}
}
};
// Custom response fabric, returns 400 BadRequest + some custom content
options.ResponseFabric = async (checkResults, requestProxy, responseProxy, requestAborted) =>
{
// Getting the rule that was exceeded and with the biggest RetryAfter value
var limitExceededResult = checkResults.OrderByDescending(r => r.RetryAfterInSeconds).FirstOrDefault(r => r.RequestsRemaining < 0);
if (limitExceededResult == null)
{
return;
}
responseProxy.StatusCode = StatusCodes.Status400BadRequest;
responseProxy.SetHttpHeader(HeaderNames.RetryAfter, limitExceededResult.RetryAfterHeaderValue);
await responseProxy.WriteAsync("Too many requests. Try again later.");
};
});
// Demonstrates how to delay the response instead of returning 429
app.UseThrottlingTroll(options =>
{
options.Config = new ThrottlingTrollConfig
{
Rules = new[]
{
new ThrottlingTrollRule
{
UriPattern = "/fixed-window-1-request-per-2-seconds-delayed-response",
LimitMethod = new FixedWindowRateLimitMethod
{
PermitLimit = 1,
IntervalInSeconds = 2
}
}
}
};
// Custom response fabric, impedes the normal response for 3 seconds
options.ResponseFabric = async (checkResults, requestProxy, responseProxy, requestAborted) =>
{
await Task.Delay(TimeSpan.FromSeconds(3));
var ingressResponse = (IIngressHttpResponseProxy)responseProxy;
ingressResponse.ShouldContinueAsNormal = true;
};
});
// Demonstrates how to use identity extractors
app.UseThrottlingTroll(options =>
{
options.Config = new ThrottlingTrollConfig
{
Rules = new[]
{
new ThrottlingTrollRule
{
UriPattern = "/fixed-window-3-requests-per-15-seconds-per-each-api-key",
LimitMethod = new FixedWindowRateLimitMethod
{
PermitLimit = 3,
IntervalInSeconds = 15
},
IdentityIdExtractor = request =>
{
// Identifying clients by their api-key
return ((IIncomingHttpRequestProxy)request).Request.Query["api-key"];
}
}
}
};
});
// Demonstrates Semaphore (Concurrency) rate limiter
// DON'T TEST IT IN BROWSER, because browsers themselves limit the number of concurrent requests to the same URL.
app.UseThrottlingTroll(options =>
{
options.Config = new ThrottlingTrollConfig
{
Rules = new[]
{
new ThrottlingTrollRule
{
UriPattern = "/semaphore-2-concurrent-requests",
LimitMethod = new SemaphoreRateLimitMethod
{
PermitLimit = 2
}
}
}
};
});
/// Demonstrates how to make a named distributed critical section with Semaphore (Concurrency) rate limiter and Identity Extractor.
/// Query string's 'id' parameter is used as identityId.
// DON'T TEST IT IN BROWSER, because browsers themselves limit the number of concurrent requests to the same URL.
app.UseThrottlingTroll(options =>
{
options.Config = new ThrottlingTrollConfig
{
Rules = new[]
{
new ThrottlingTrollRule
{
UriPattern = "/named-critical-section",
LimitMethod = new SemaphoreRateLimitMethod
{
PermitLimit = 1
},
// This must be set to something > 0 for responses to be automatically delayed
MaxDelayInSeconds = 120,
IdentityIdExtractor = request =>
{
// Identifying clients by their id
return ((IIncomingHttpRequestProxy)request).Request.Query["id"];
}
},
}
};
});
// Demonstrates how to make a distributed counter with SemaphoreRateLimitMethod
app.UseThrottlingTroll(options =>
{
options.Config = new ThrottlingTrollConfig
{
Rules = new[]
{
new ThrottlingTrollRule
{
UriPattern = "/distributed-counter",
LimitMethod = new SemaphoreRateLimitMethod
{
PermitLimit = 1
},
// This must be set to something > 0 for responses to be automatically delayed
MaxDelayInSeconds = 120,
IdentityIdExtractor = request =>
{
// Identifying counters by their id
return ((IIncomingHttpRequestProxy)request).Request.Query["id"];
}
},
}
};
});
// Demonstrates how to use cost extractors
app.UseThrottlingTroll(options =>
{
options.Config = new ThrottlingTrollConfig
{
Rules = new[]
{
new ThrottlingTrollRule
{
UriPattern = "/fixed-window-balance-of-10-per-20-seconds",
LimitMethod = new FixedWindowRateLimitMethod
{
PermitLimit = 10,
IntervalInSeconds = 20
},
// Specifying a routine to calculate the cost (weight) of each request
CostExtractor = request =>
{
// Cost comes as a 'cost' query string parameter
string? cost = ((IIncomingHttpRequestProxy)request).Request.Query["cost"];
return long.TryParse(cost, out long val) ? val : 1;
}
}
}
};
});
// </ThrottlingTroll Ingress Configuration>
app.Run();
}
}
}
![error-in-options-counterstore](https://github.com/ThrottlingTroll/ThrottlingTroll/assets/13834499/5538d3c8-3eae-4bd6-b869-d4d62d4513a8)
So that the throttled response can be customized.
, as the counter will not actually be stored and will not take effect.
Workaround: always set the interval to more than 1s. But better just use RedisCounterStore.
Ideally, supporting multiple different RDBMSes, e.g. MSSQL, MySQL etc.
Recently, I have been working on implementing rate limiting in my Azure functions to better manage incoming request traffic. To achieve this, I have been using your library.
However, during my implementation, I have noticed that it is not possible to retrieve important information related to rate limiting directly from the HTTP response generated by the library. Specifically, I would like to be able to include the following headers in my function responses:
These headers would be extremely useful for clients and consumers of my functions to better understand and manage their requests and adjust their behavior based on the rate limit status.
Thanks
So that Rules can come from config file, and yet IdentityIdExtractor can also be specified
No point in incrementing counters that are already exceeded. Just need to cache those exceeded ones in memory (can use MemoryCache for that). This promises to save a lot of roundtrips to Redis, and this is especially important under stress.
so that it can take multiple values
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.