Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Par Arvin Kahbazi, Maarten Balliauw et Rick Anderson
L’intergiciel Microsoft.AspNetCore.RateLimiting fournit un intergiciel de limitation de débit. Les applications configurent des stratégies de limitation du débit, puis attachent les stratégies aux points de terminaison. Les applications utilisant la limitation de débit doivent être soigneusement testées et examinées avant le déploiement. Pour plus d’informations, consultez Test des points de terminaison avec limitation de débit.
Pour obtenir une introduction à la limitation de débit, consultez Intergiciel de limitation de débit.
Pourquoi utiliser la limitation du taux d’utilisation
La limitation du débit peut être utilisée pour gérer le flux des requêtes entrantes vers une application. Principales raisons d’implémenter la limitation de débit :
- Prévention des abus : la limitation du débit permet de protéger une application contre les abus en limitant le nombre de demandes qu’un utilisateur ou un client peut effectuer dans une période donnée. Cette protection est particulièrement importante pour les API publiques.
- Garantir l’utilisation équitable : en définissant des limites qui empêchent les utilisateurs de monopoliser le système, vous assurez que tous les utilisateurs ont un accès équitable aux ressources.
- Protection des ressources : la limitation du débit permet d’empêcher la surcharge du serveur en contrôlant le nombre de requêtes qui peuvent être traitées. Il protège les ressources back-end contre l’surcharge.
- Amélioration de la sécurité : elle peut atténuer le risque d’attaques par déni de service (DoS) en limitant le taux de traitement des demandes. Cela rend plus difficile pour les attaquants d’inonder un système.
- Amélioration des performances : en contrôlant le taux de requêtes entrantes, vous pouvez maintenir des performances et une réactivité optimales d’une application, ce qui garantit une meilleure expérience utilisateur.
- Gestion des coûts : Pour les services qui entraînent des coûts en fonction de l’utilisation, la limitation des taux peut aider à gérer et prédire les dépenses en contrôlant le volume des demandes traitées.
L’implémentation de la limitation du débit dans une application ASP.NET Core peut aider à maintenir la stabilité, la sécurité et les performances. Le résultat est un service fiable et efficace pour tous les utilisateurs.
Empêcher les attaques DDoS
Bien que la limitation de débit puisse aider à atténuer le risque d’attaques par déni de service (DoS) en limitant le taux de traitement des demandes, il ne s’agit pas d’une solution complète pour les attaques par déni de service distribué (DDoS). Les attaques DDoS impliquent plusieurs systèmes accablant une application avec une inondation de requêtes, ce qui rend difficile la gestion avec la limitation du débit seule.
Pour une protection DDoS robuste, envisagez d’utiliser un service de protection DDoS commercial. Ces services offrent des fonctionnalités avancées telles que :
- Analyse du trafic : surveillance et analyse continues du trafic entrant pour détecter et atténuer les attaques DDoS en temps réel.
- Scalabilité : capacité à gérer des attaques à grande échelle en distribuant le trafic entre plusieurs serveurs et centres de données.
- Atténuation automatisée : mécanismes de réponse automatisés pour bloquer rapidement le trafic malveillant sans intervention manuelle.
- Réseau global : un réseau mondial de serveurs pour absorber et atténuer les attaques plus proches de la source.
- Mises à jour constantes : les services commerciaux suivent et mettent à jour en permanence leurs mécanismes de protection pour s’adapter aux menaces nouvelles et en constante évolution.
Lorsque vous utilisez un service d’hébergement cloud, la protection DDoS est généralement disponible dans le cadre de la solution d’hébergement, telle qu’Azure Web Application Firewall, AWS Shield ou Google Cloud Armor. Les protections dédiées sont disponibles en tant que pare-feu d'applications web (WAF) ou dans le cadre d'une solution CDN telle que Cloudflare ou Akamai Kona Site Defender
L’implémentation d’un service de protection DDoS commercial conjointement avec la limitation de débit peut fournir une stratégie de défense complète, garantissant ainsi la stabilité, la sécurité et les performances d’une application.
Limitation du débit de requêtes avec plusieurs instances d’application
Le middleware de limitation de débit conserve ses compteurs en mémoire dans chaque instance d’application. Lorsqu’une application s’exécute sur plusieurs instances, comme derrière un équilibreur de charge ou après un scale-out, chaque instance applique ses limites séparément. La limite effective est la limite configurée multipliée par le nombre d’instances. Par exemple, une stratégie qui autorise 100 requêtes par minute autorise jusqu’à 300 requêtes par minute sur trois instances. Cela s’applique également à un limiteur global : il couvre toutes les demandes adressées à une seule instance, et non à l’ensemble de l’application. Pour appliquer une limite unique commune à toutes les instances, appliquez la limitation de débit à une couche partagée, telle qu’une passerelle d’API, ou écrivez un limiteur de débit personnalisé qui stocke ses compteurs dans un magasin partagé.
Utiliser l’intergiciel de limitation de débit
L’exemple de cette section décrit sa stratégie comme appliquant « globalement », mais le comportement de limitation du débit s’applique uniquement à l’instance de l’application. Pour plus d’informations, consultez la section Limitation de débit avec plusieurs instances d’application .
Les étapes suivantes montrent comment utiliser l’intergiciel de limitation de débit dans une application ASP.NET Core :
- Configurez les services de limitation de débit.
Dans le Program.cs fichier, configurez les services de limitation de débit en ajoutant les stratégies de limitation de débit appropriées. Définissez des stratégies en tant que stratégies globales ou nommées. L’exemple suivant autorise 10 requêtes par minute par utilisateur (identité) ou globalement :
builder.Services.AddRateLimiter(options =>
{
options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
RateLimitPartition.GetFixedWindowLimiter(
partitionKey: httpContext.User.Identity?.Name ?? httpContext.Request.Headers.Host.ToString(),
factory: partition => new FixedWindowRateLimiterOptions
{
AutoReplenishment = true,
PermitLimit = 10,
QueueLimit = 0,
Window = TimeSpan.FromMinutes(1)
}));
});
Les stratégies nommées doivent être appliquées explicitement aux pages ou aux points de terminaison. L’exemple suivant ajoute une stratégie de limitation à fenêtre fixe nommée "fixed", que vous ajoutez ensuite à un point de terminaison :
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRateLimiter(options =>
{
options.AddFixedWindowLimiter("fixed", opt =>
{
opt.PermitLimit = 4;
opt.Window = TimeSpan.FromSeconds(12);
opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
opt.QueueLimit = 2;
});
});
var app = builder.Build();
Le limiteur global s’applique automatiquement à tous les points de terminaison lorsque vous le configurez via options.GlobalLimiter.
Activer le middleware de limitation de débit
Dans le
Program.csfichier, activez l’intergiciel de limitation de débit en appelant UseRateLimiter :
app.UseRouting();
app.UseRateLimiter();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.Run();
Appliquer des stratégies de limitation de débit aux points de terminaison ou aux pages
Appliquer la limitation de débit aux points de terminaison d’API web
Appliquez une stratégie nommée au point de terminaison ou au groupe, par exemple :
app.MapGet("/api/resource", () => "This endpoint is rate limited")
.RequireRateLimiting("fixed"); // Apply specific policy to an endpoint
Appliquer la limitation de débit aux contrôleurs MVC
Appliquez les stratégies de limitation de débit configurées à des points de terminaison spécifiques ou globalement. Par exemple, pour appliquer la stratégie « fixe » à tous les points de terminaison du contrôleur :
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers().RequireRateLimiting("fixed");
});
Appliquer la limitation du débit aux applications côté Blazor serveur
Pour définir la limitation de débit pour tous les composants routables Razor de l'application, spécifiez RequireRateLimiting avec le nom de la stratégie de limitation de débit sur l'appel MapRazorComponents dans le fichier Program. Dans l’exemple suivant, la stratégie de limitation de débit nommée «policy » est appliquée :
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode()
.RequireRateLimiting("policy");
Pour définir une stratégie pour un seul composant routable Razor ou un dossier de composants via un fichier d’imports (_Imports.razor), appliquez l’attribut [EnableRateLimiting] en indiquant le nom de la stratégie. Dans l’exemple suivant, la stratégie de limitation de débit nommée «override » est appliquée. La stratégie remplace toutes les stratégies actuellement appliquées au point de terminaison. Le limiteur global s’exécute toujours sur le point de terminaison avec cet attribut appliqué.
@page "/counter"
@using Microsoft.AspNetCore.RateLimiting
@attribute [EnableRateLimiting("override")]
<h1>Counter</h1>
Utilisez [EnableRateLimiting] l’attribut uniquement sur un composant routable ou sur un dossier de composants au moyen d’un fichier d’imports si RequireRateLimiting n’est pas appelé sur MapRazorComponents.
Utilisez l’attribut pour désactiver la [DisableRateLimiting] limitation de débit pour un composant routable ou un dossier de composants via un fichier d’importation.
Algorithmes de limiteur de débit
La classe RateLimiterOptionsExtensions fournit les méthodes d’extension suivantes pour la limitation du débit :
Les limiteurs fixes, glissants et de jetons limitent tous le nombre maximal de requêtes dans une période donnée. Le limiteur d’accès concurrentiel limite uniquement le nombre de requêtes simultanées et ne limite pas le nombre de requêtes dans une période donnée. Tenez compte du coût d’un point de terminaison lorsque vous sélectionnez un limiteur. Le coût d’un point de terminaison inclut les ressources utilisées, telles que l’heure, l’accès aux données, le processeur et les E/S.
Limiteur de fenêtre fixe
La méthode AddFixedWindowLimiter utilise une fenêtre de temps fixe pour limiter les requêtes. Lorsque la fenêtre de temps expire, une nouvelle fenêtre de temps démarre et la limite de requête est réinitialisée.
Prenez le code suivant :
using Microsoft.AspNetCore.RateLimiting;
using System.Threading.RateLimiting;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRateLimiter(_ => _
.AddFixedWindowLimiter(policyName: "fixed", options =>
{
options.PermitLimit = 4;
options.Window = TimeSpan.FromSeconds(12);
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
options.QueueLimit = 2;
}));
var app = builder.Build();
app.UseRateLimiter();
static string GetTicks() => (DateTime.Now.Ticks & 0x11111).ToString("00000");
app.MapGet("/", () => Results.Ok($"Hello {GetTicks()}"))
.RequireRateLimiting("fixed");
app.Run();
Le code précédent :
- Appelle AddRateLimiter pour ajouter un service de limitation de débit à la collection de services.
- Appelle
AddFixedWindowLimiterpour créer un limiteur de fenêtre fixe avec un nom de stratégie de"fixed"et définit : - PermitLimit sur 4 et le temps Window sur 12. Un maximum de 4 requêtes pour chaque fenêtre de 12 secondes est autorisé.
- QueueProcessingOrder sur OldestFirst.
- QueueLimit à 2 (définissez cette valeur sur 0 pour désactiver le mécanisme de mise en file d’attente).
- Appelle UseRateLimiter pour activer la limitation du débit.
Les applications doivent utiliser Configuration pour définir les options de limiteur. Le code suivant met à jour le code précédent à l’aide de MyRateLimitOptions pour la configuration :
using System.Threading.RateLimiting;
using Microsoft.AspNetCore.RateLimiting;
using WebRateLimitAuth.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.Configure<MyRateLimitOptions>(
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit));
var myOptions = new MyRateLimitOptions();
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit).Bind(myOptions);
var fixedPolicy = "fixed";
builder.Services.AddRateLimiter(_ => _
.AddFixedWindowLimiter(policyName: fixedPolicy, options =>
{
options.PermitLimit = myOptions.PermitLimit;
options.Window = TimeSpan.FromSeconds(myOptions.Window);
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
options.QueueLimit = myOptions.QueueLimit;
}));
var app = builder.Build();
app.UseRateLimiter();
static string GetTicks() => (DateTime.Now.Ticks & 0x11111).ToString("00000");
app.MapGet("/", () => Results.Ok($"Fixed Window Limiter {GetTicks()}"))
.RequireRateLimiting(fixedPolicy);
app.Run();
UseRateLimiter doit être appelé après UseRouting lorsque des API spécifiques au point de terminaison de limitation de débit sont utilisées. Par exemple, si l’attribut [EnableRateLimiting] est utilisé, UseRateLimiter doit être appelé après UseRouting. Lorsque vous appelez uniquement des limiteurs globaux, UseRateLimiter peut être appelé avant UseRouting.
Si l’application appelle UseStatusCodePagesWithReExecute, appelez-le avant UseRouting et UseRateLimiter. Lorsque le middleware des pages de code d’état réexécute une requête, seul l’intergiciel inscrit après lui s’exécute de nouveau. Si UseRateLimiter est appelé avant UseStatusCodePagesWithReExecute, le limiteur de débit ne s’exécute pas pour le point de terminaison réexécuté, de sorte que la stratégie de limitation de débit du point de terminaison n’est pas appliquée, même si la stratégie n’a plus d’autorisation disponible. L’ordre suivant permet au limiteur de débit d’évaluer l’endpoint réexécuté :
app.UseStatusCodePagesWithReExecute("/StatusCode/{0}");
app.UseRouting();
app.UseRateLimiter();
Pour plus d’informations, consultez la UseStatusCodePagesWithReExecutesection de l’article de gestion des erreurs.
Limiteur à fenêtre glissante
Un algorithme à fenêtre glissante :
- Est similaire au limiteur de fenêtre fixe, mais avec des segments par fenêtre. La fenêtre se déplace d'un segment à chaque intervalle. L’intervalle de segment est (heure de la fenêtre)/(segments par fenêtre).
- Limites les requêtes pour une fenêtre aux requêtes
permitLimit. - Chaque fenêtre de temps est divisée en
nsegments par fenêtre. - Les requêtes extraites du segment de temps expiré d’une fenêtre plus tôt (
nsegments avant le segment actuel) sont ajoutées au segment actuel. Nous désignons le segment de temps le plus ancien, une fenêtre en arrière, comme étant le segment expiré.
Considérez le tableau suivant qui montre un limiteur à fenêtre glissante avec une fenêtre de 30 secondes, trois segments par fenêtre et une limite de 100 requêtes :
- La ligne supérieure et la première colonne affichent le segment de temps.
- La deuxième ligne indique les requêtes restantes disponibles. Les requêtes restantes sont calculées en soustrayant aux demandes disponibles les demandes traitées et en ajoutant les demandes recyclées.
- Les requêtes se déplacent à chaque fois le long de la ligne bleue diagonale.
- À partir de l’heure 30, la requête issue du segment de temps expiré est ajoutée à la limite de requête, comme indiqué dans les lignes rouges.
Le tableau suivant montre les données du graphique précédent dans un autre format. La colonne Available affiche les requêtes disponibles provenant du segment précédent (le report de la ligne précédente). La première ligne indique 100 requêtes disponibles, car il n’y a pas de segment précédent.
| Temps | Disponible | Utilisé | Recyclé à partir de l’élément expiré | Report |
|---|---|---|---|---|
| 0 | 100 | 20 | 0 | 80 |
| 10 | 80 | 30 | 0 | 50 |
| 20 | 50 | 40 | 0 | 10 |
| 30 | 10 | 30 | 20 | 0 |
| 40 | 0 | 10 | 30 | 20 |
| 50 | 20 | 10 | 40 | 50 |
| 60 | 50 | 35 | 30 | 45 |
Le code suivant utilise le limiteur de débit à fenêtre glissante :
using Microsoft.AspNetCore.RateLimiting;
using System.Threading.RateLimiting;
using WebRateLimitAuth.Models;
var builder = WebApplication.CreateBuilder(args);
var myOptions = new MyRateLimitOptions();
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit).Bind(myOptions);
var slidingPolicy = "sliding";
builder.Services.AddRateLimiter(_ => _
.AddSlidingWindowLimiter(policyName: slidingPolicy, options =>
{
options.PermitLimit = myOptions.PermitLimit;
options.Window = TimeSpan.FromSeconds(myOptions.Window);
options.SegmentsPerWindow = myOptions.SegmentsPerWindow;
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
options.QueueLimit = myOptions.QueueLimit;
}));
var app = builder.Build();
app.UseRateLimiter();
static string GetTicks() => (DateTime.Now.Ticks & 0x11111).ToString("00000");
app.MapGet("/", () => Results.Ok($"Sliding Window Limiter {GetTicks()}"))
.RequireRateLimiting(slidingPolicy);
app.Run();
Limiteur de compartiment de jetons
Le limiteur de compartiment de jetons est similaire au limiteur à fenêtre glissante, mais au lieu d’ajouter en arrière les requêtes prises à partir du segment expiré, un nombre fixe de jetons est ajouté à chaque période de réapprovisionnement. Les jetons ajoutés à chaque segment ne peuvent pas augmenter les jetons disponibles à un nombre supérieur à la limite du compartiment de jetons. Le tableau suivant montre un limiteur de compartiment de jetons avec une limite de 100 jetons et une période de réapprovisionnement de 10 secondes.
| Temps | Disponible | Utilisé | Ajouté(e) | Report |
|---|---|---|---|---|
| 0 | 100 | 20 | 0 | 80 |
| 10 | 80 | 10 | 20 | 90 |
| 20 | 90 | 5 | 15 | 100 |
| 30 | 100 | 30 | 20 | 90 |
| 40 | 90 | 6 | 16 | 100 |
| 50 | 100 | 40 | 20 | 80 |
| 60 | 80 | 50 | 20 | 50 |
Le code suivant utilise le limiteur de compartiment de jetons :
using Microsoft.AspNetCore.RateLimiting;
using System.Threading.RateLimiting;
using WebRateLimitAuth.Models;
var builder = WebApplication.CreateBuilder(args);
var tokenPolicy = "token";
var myOptions = new MyRateLimitOptions();
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit).Bind(myOptions);
builder.Services.AddRateLimiter(_ => _
.AddTokenBucketLimiter(policyName: tokenPolicy, options =>
{
options.TokenLimit = myOptions.TokenLimit;
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
options.QueueLimit = myOptions.QueueLimit;
options.ReplenishmentPeriod = TimeSpan.FromSeconds(myOptions.ReplenishmentPeriod);
options.TokensPerPeriod = myOptions.TokensPerPeriod;
options.AutoReplenishment = myOptions.AutoReplenishment;
}));
var app = builder.Build();
app.UseRateLimiter();
static string GetTicks() => (DateTime.Now.Ticks & 0x11111).ToString("00000");
app.MapGet("/", () => Results.Ok($"Token Limiter {GetTicks()}"))
.RequireRateLimiting(tokenPolicy);
app.Run();
Quand AutoReplenishment est défini sur true, un minuteur interne réapprovisionne les jetons tous les ReplenishmentPeriod. Quand il est défini sur false, l’application doit appeler TryReplenish sur le limiteur.
Limiteur d’accès concurrentiel
Le limiteur d’accès concurrentiel limite le nombre de requêtes simultanées. Chaque requête réduit la limite d’accès concurrentiel de un. Lorsqu’une requête se termine, la limite est augmentée de un. Contrairement aux autres limiteurs de requêtes qui limitent le nombre total de requêtes pour une période spécifiée, le limiteur d’accès concurrentiel limite uniquement le nombre de requêtes simultanées et ne limite pas le nombre de requêtes dans une période donnée.
Le code suivant utilise le limiteur d’accès concurrentiel :
using Microsoft.AspNetCore.RateLimiting;
using System.Threading.RateLimiting;
using WebRateLimitAuth.Models;
var builder = WebApplication.CreateBuilder(args);
var concurrencyPolicy = "Concurrency";
var myOptions = new MyRateLimitOptions();
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit).Bind(myOptions);
builder.Services.AddRateLimiter(_ => _
.AddConcurrencyLimiter(policyName: concurrencyPolicy, options =>
{
options.PermitLimit = myOptions.PermitLimit;
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
options.QueueLimit = myOptions.QueueLimit;
}));
var app = builder.Build();
app.UseRateLimiter();
static string GetTicks() => (DateTime.Now.Ticks & 0x11111).ToString("00000");
app.MapGet("/", async () =>
{
await Task.Delay(500);
return Results.Ok($"Concurrency Limiter {GetTicks()}");
}).RequireRateLimiting(concurrencyPolicy);
app.Run();
Partitions de limitation de débit
Les partitions de limitation de débit divisent le trafic en compartiments distincts qui obtiennent chacun leurs propres compteurs de limite de débit. Cette approche fournit un contrôle plus granulaire qu’un seul compteur global. Différentes clés, telles que l’ID utilisateur, l’adresse IP ou la clé API, définissent les compartiments de partition.
Avantages du partitionnement
- Équité : un utilisateur ne peut pas consommer toute la limite de débit pour tout le monde.
- Granularité : limites différentes pour différents utilisateurs et ressources.
- Sécurité : meilleure protection contre les abus ciblés.
- Service hiérarchisé : prise en charge des niveaux de service avec différentes limites.
La limitation de débit partitionnée vous donne un contrôle précis sur la façon dont vous gérez le trafic d’API tout en garantissant une allocation équitable des ressources.
Par adresse IP
options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
RateLimitPartition.GetFixedWindowLimiter(
partitionKey: httpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown",
factory: _ => new FixedWindowRateLimiterOptions
{
PermitLimit = 50,
Window = TimeSpan.FromMinutes(1)
}));
Par identité de l’utilisateur
options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
RateLimitPartition.GetFixedWindowLimiter(
partitionKey: httpContext.User.Identity?.Name ?? "anonymous",
factory: _ => new FixedWindowRateLimiterOptions
{
PermitLimit = 100,
Window = TimeSpan.FromMinutes(1)
}));
Par clé API
options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
{
string apiKey = httpContext.Request.Headers["X-API-Key"].ToString() ?? "no-key";
// Different limits based on key tier
return apiKey switch
{
"premium-key" => RateLimitPartition.GetFixedWindowLimiter(
partitionKey: apiKey,
factory: _ => new FixedWindowRateLimiterOptions
{
PermitLimit = 1000,
Window = TimeSpan.FromMinutes(1)
}),
_ => RateLimitPartition.GetFixedWindowLimiter(
partitionKey: apiKey,
factory: _ => new FixedWindowRateLimiterOptions
{
PermitLimit = 100,
Window = TimeSpan.FromMinutes(1)
}),
};
});
Par chemin d’accès au point de terminaison
options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
{
string path = httpContext.Request.Path.ToString();
// Different limits for different paths
if (path.StartsWith("/api/public"))
{
return RateLimitPartition.GetFixedWindowLimiter(
partitionKey: $"{httpContext.Connection.RemoteIpAddress}-public",
factory: _ => new FixedWindowRateLimiterOptions
{
PermitLimit = 30,
Window = TimeSpan.FromSeconds(10)
});
}
return RateLimitPartition.GetFixedWindowLimiter(
partitionKey: httpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown",
factory: _ => new FixedWindowRateLimiterOptions
{
PermitLimit = 100,
Window = TimeSpan.FromMinutes(1)
});
});
Créer des limiteurs chaînés
L’API CreateChained accepte plusieurs PartitionedRateLimiter instances et les combine en une PartitionedRateLimiterseule. Le limiteur combiné exécute tous les limiteurs d’entrée dans l’ordre. Puisque le résultat est un PartitionedRateLimiter affecté à GlobalLimiter, la chaîne s’applique à chaque point de terminaison. Pour chaîner des limiteurs pour un point de terminaison spécifique, utilisez plutôt une stratégie nommée, comme indiqué dans Chaîner des limiteurs dans une stratégie nommée.
Le code suivant utilise CreateChained :
using System.Globalization;
using System.Threading.RateLimiting;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRateLimiter(_ =>
{
_.OnRejected = async (context, cancellationToken) =>
{
if (context.Lease.TryGetMetadata(MetadataName.RetryAfter, out var retryAfter))
{
context.HttpContext.Response.Headers.RetryAfter =
((int) retryAfter.TotalSeconds).ToString(NumberFormatInfo.InvariantInfo);
}
context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;
await context.HttpContext.Response.WriteAsync("Too many requests. Please try again later.", cancellationToken);
};
_.GlobalLimiter = PartitionedRateLimiter.CreateChained(
PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
{
var userAgent = httpContext.Request.Headers.UserAgent.ToString();
return RateLimitPartition.GetFixedWindowLimiter
(userAgent, _ =>
new FixedWindowRateLimiterOptions
{
AutoReplenishment = true,
PermitLimit = 4,
Window = TimeSpan.FromSeconds(2)
});
}),
PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
{
var userAgent = httpContext.Request.Headers.UserAgent.ToString();
return RateLimitPartition.GetFixedWindowLimiter
(userAgent, _ =>
new FixedWindowRateLimiterOptions
{
AutoReplenishment = true,
PermitLimit = 20,
Window = TimeSpan.FromSeconds(30)
});
}));
});
var app = builder.Build();
app.UseRateLimiter();
static string GetTicks() => (DateTime.Now.Ticks & 0x11111).ToString("00000");
app.MapGet("/", () => Results.Ok($"Hello {GetTicks()}"));
app.Run();
Pour plus d’informations, consultez le code source CreateChained.
Limiteurs de chaîne dans une stratégie nommée
CreateChained enchaîne les limiteurs globaux qui s’appliquent à chaque point de terminaison. Pour combiner plusieurs types de limiteurs et les étendre à des points de terminaison spécifiques, chaînez les limiteurs à l’intérieur d’une stratégie nommée avec CreateChained. Cette surcharge retourne un unique RateLimiter qui exécute successivement chaque limiteur, correspondant au type de retour requis par la fabrique de partition d’une stratégie nommée.
La stratégie suivante "combined" enchaîne un limiteur à seau de jetons et un limiteur de concurrence avec RateLimiter.CreateChained, puis applique la stratégie à un point de terminaison unique avec RequireRateLimiting :
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRateLimiter(options =>
{
options.AddPolicy("combined", httpContext =>
{
// Partition on the authenticated identity name when available. Each distinct key creates and
// caches its own limiter, so partitioning on unbounded user-controlled
// input can exhaust memory (a DoS risk).
string partitionKey = httpContext.User.Identity?.Name ?? "anonymous";
return RateLimitPartition.Get(partitionKey, _ =>
RateLimiter.CreateChained(
new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions
{
TokenLimit = 100,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 5,
ReplenishmentPeriod = TimeSpan.FromSeconds(10),
TokensPerPeriod = 10,
AutoReplenishment = true
}),
new ConcurrencyLimiter(new ConcurrencyLimiterOptions
{
PermitLimit = 5,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 2
})));
});
});
var app = builder.Build();
app.MapGet("/api/resource", () => "This endpoint uses multiple limiters")
.RequireRateLimiting("combined");
Une requête doit obtenir un bail auprès de chaque limiteur de la chaîne pour pouvoir se poursuivre, et les limiteurs s’exécutent dans l’ordre fourni à RateLimiter.CreateChained. Si un limiteur rejette la demande, la demande est rejetée et les baux déjà acquis à partir de limiteurs antérieurs de la chaîne sont supprimés dans l’ordre inverse.
Lorsque vous chaînez des limiteurs dans une stratégie nommée, gardez à l’esprit ce qui suit :
- La libération d’un bail restitue l’autorisation d’un limiteur de concurrence. Les limiteurs basés sur le temps (compartiment de jeton, fenêtre fixe et fenêtre glissante) ne retournent pas un permis qui a déjà été acquis lorsqu’un limiteur ultérieur dans la chaîne rejette la demande, donc prenez cela en compte lors de l’ordre des limiteurs dans la chaîne.
- Construire directement un
TokenBucketRateLimiteravecAutoReplenishmentdéfini surtrueattribue à chaque instance du limiteur son propre temporisateur. Les assistantsAddTokenBucketLimiteretRateLimitPartition.GetTokenBucketLimiterdéfinissent plutôtAutoReplenishmentsurfalseet réinitialisent tous leurs limiteurs à partir d’un unique retardateur partagé. -
RateLimiter.CreateChainedne supprime pas les limiteurs passés à celui-ci. Dans l’exemple précédent, la partition met en cache le limiteur chaîné et l’infrastructure gère sa durée de vie. Si vous créez des limiteurs chaînés en dehors d’une partition, supprimez les limiteurs internes lorsqu’ils ne sont plus utilisés. - Préférez l’approche globale
PartitionedRateLimiter.CreateChainedlorsque la chaîne doit s’appliquer à chaque point de terminaison. Utilisez une stratégie nommée avecRateLimiter.CreateChaineduniquement lorsque la chaîne doit être étendue à des points de terminaison spécifiques.
Choisir ce qui se passe lorsqu’une requête est limitée
Pour les cas simples, vous pouvez simplement définir le code d’état :
builder.Services.AddRateLimiter(options =>
{
// Set a custom status code for rejections
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
// Rate limiter configuration...
});
L’approche la plus courante consiste à inscrire un OnRejected rappel lors de la configuration de la limitation du débit :
builder.Services.AddRateLimiter(options =>
{
// Rate limiter configuration...
options.OnRejected = async (context, cancellationToken) =>
{
context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;
if (context.Lease.TryGetMetadata(MetadataName.RetryAfter, out var retryAfter))
{
context.HttpContext.Response.Headers.RetryAfter =
((long)Math.Ceiling(retryAfter.TotalSeconds)).ToString(NumberFormatInfo.InvariantInfo);
}
await context.HttpContext.Response.WriteAsync("Rate limit exceeded. Please try again later.", cancellationToken);
// Optional logging
logger.LogWarning("Rate limit exceeded for IP: {IpAddress}",
context.HttpContext.Connection.RemoteIpAddress);
};
});
L’exemple nécessite using System.Globalization; et using System.Threading.RateLimiting;.
Note
FixedWindowRateLimiter indique MetadataName.RetryAfter en fonction de la prochaine limite de la fenêtre fixe. Les applications qui copient ces métadonnées dans l’en-tête Retry-After de réponse retournent OnRejected automatiquement un délai précis avant une nouvelle tentative. Les limiteurs qui ne peuvent pas estimer à quel moment des autorisations seront disponibles, tels que ConcurrencyLimiter, ne fournissent pas RetryAfter de métadonnées.
Une autre option consiste à mettre en file d’attente la requête :
Mise en file d’attente des requêtes
Lorsque vous activez la mise en file d’attente, si une requête dépasse la limite de débit, le système le place dans une file d’attente. La requête reste dans la file d’attente jusqu’à ce qu’une autorisation devienne disponible ou qu’un délai d’expiration soit atteint. Le système traite les demandes en fonction d’un ordre de file d’attente configurable.
builder.Services.AddRateLimiter(options =>
{
options.AddFixedWindowLimiter("api", options =>
{
options.PermitLimit = 10; // Allow 10 requests
options.Window = TimeSpan.FromSeconds(10); // Per 10-second window
options.QueueLimit = 5; // Queue up to 5 additional requests
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; // Process oldest requests first
options.AutoReplenishment = true; // Default: automatically replenish permits
});
});
Attributs EnableRateLimiting et DisableRateLimiting
Appliquez les attributs [DisableRateLimiting] et [EnableRateLimiting] à un contrôleur, une méthode d’action ou une RazorPage. Pour Razor Pages, appliquez l’attribut à la Razor page et non aux gestionnaires de pages. Par exemple, vous ne pouvez pas appliquer [EnableRateLimiting] à , OnPostou à OnGettout autre gestionnaire de pages.
L’attribut [DisableRateLimiting]désactive la limitation de débit pour le contrôleur, la méthode d’action ou Razor La page, indépendamment des limiteurs de débit nommés ou des limiteurs globaux appliqués. Par exemple, considérez le code suivant qui appelle RequireRateLimiting pour appliquer la limitation de débit fixedPolicy à tous les points de terminaison de contrôleur :
using Microsoft.AspNetCore.RateLimiting;
using System.Threading.RateLimiting;
using WebRateLimitAuth.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();
builder.Services.Configure<MyRateLimitOptions>(
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit));
var myOptions = new MyRateLimitOptions();
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit).Bind(myOptions);
var fixedPolicy = "fixed";
builder.Services.AddRateLimiter(_ => _
.AddFixedWindowLimiter(policyName: fixedPolicy, options =>
{
options.PermitLimit = myOptions.PermitLimit;
options.Window = TimeSpan.FromSeconds(myOptions.Window);
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
options.QueueLimit = myOptions.QueueLimit;
}));
var slidingPolicy = "sliding";
builder.Services.AddRateLimiter(_ => _
.AddSlidingWindowLimiter(policyName: slidingPolicy, options =>
{
options.PermitLimit = myOptions.SlidingPermitLimit;
options.Window = TimeSpan.FromSeconds(myOptions.Window);
options.SegmentsPerWindow = myOptions.SegmentsPerWindow;
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
options.QueueLimit = myOptions.QueueLimit;
}));
var app = builder.Build();
app.UseRateLimiter();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.MapRazorPages().RequireRateLimiting(slidingPolicy);
app.MapDefaultControllerRoute().RequireRateLimiting(fixedPolicy);
app.Run();
Dans le code suivant, [DisableRateLimiting] désactive la limitation du débit et remplace [EnableRateLimiting("fixed")] appliqué au Home2Controller et app.MapDefaultControllerRoute().RequireRateLimiting(fixedPolicy) appelés dans Program.cs :
[EnableRateLimiting("fixed")]
public class Home2Controller : Controller
{
private readonly ILogger<Home2Controller> _logger;
public Home2Controller(ILogger<Home2Controller> logger)
{
_logger = logger;
}
public ActionResult Index()
{
return View();
}
[EnableRateLimiting("sliding")]
public ActionResult Privacy()
{
return View();
}
[DisableRateLimiting]
public ActionResult NoLimit()
{
return View();
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
Dans le code précédent, le [EnableRateLimiting("sliding")]n’est pas appliqué à la méthode d’action Privacy, car Program.cs a appelé app.MapDefaultControllerRoute().RequireRateLimiting(fixedPolicy).
Considérez le code suivant qui n’appelle pas RequireRateLimiting sur MapRazorPages ou MapDefaultControllerRoute :
using Microsoft.AspNetCore.RateLimiting;
using System.Threading.RateLimiting;
using WebRateLimitAuth.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();
builder.Services.Configure<MyRateLimitOptions>(
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit));
var myOptions = new MyRateLimitOptions();
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit).Bind(myOptions);
var fixedPolicy = "fixed";
builder.Services.AddRateLimiter(_ => _
.AddFixedWindowLimiter(policyName: fixedPolicy, options =>
{
options.PermitLimit = myOptions.PermitLimit;
options.Window = TimeSpan.FromSeconds(myOptions.Window);
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
options.QueueLimit = myOptions.QueueLimit;
}));
var slidingPolicy = "sliding";
builder.Services.AddRateLimiter(_ => _
.AddSlidingWindowLimiter(policyName: slidingPolicy, options =>
{
options.PermitLimit = myOptions.SlidingPermitLimit;
options.Window = TimeSpan.FromSeconds(myOptions.Window);
options.SegmentsPerWindow = myOptions.SegmentsPerWindow;
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
options.QueueLimit = myOptions.QueueLimit;
}));
var app = builder.Build();
app.UseRateLimiter();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.MapRazorPages();
app.MapDefaultControllerRoute(); // RequireRateLimiting not called
app.Run();
Examinons le contrôleur ci-dessous :
[EnableRateLimiting("fixed")]
public class Home2Controller : Controller
{
private readonly ILogger<Home2Controller> _logger;
public Home2Controller(ILogger<Home2Controller> logger)
{
_logger = logger;
}
public ActionResult Index()
{
return View();
}
[EnableRateLimiting("sliding")]
public ActionResult Privacy()
{
return View();
}
[DisableRateLimiting]
public ActionResult NoLimit()
{
return View();
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
Dans le contrôleur précédent :
- Le limiteur de taux de stratégie
"fixed"est appliqué à toutes les méthodes d’action qui n’ont pas les attributsEnableRateLimitingetDisableRateLimiting. - Le limiteur de taux de stratégie
"sliding"est appliqué à l’actionPrivacy. - La limitation du débit est désactivée sur la méthode d’action
NoLimit.
Métriques de limitation de débit
Le middleware de limitation de débit fournit des fonctionnalités intégrées de métriques et de surveillance pour vous aider à comprendre comment les limites de débit affectent les performances des applications et l’expérience utilisateur. Pour obtenir la liste des métriques, consultez Microsoft.AspNetCore.RateLimiting.
Test des points de terminaison avec limitation de débit
Avant de déployer une application qui utilise la limitation du débit en production, testez l’application pour valider les limites de débit et les options que vous avez utilisées. Par exemple, créez un script JMeter à l’aide d’un outil tel que BlazeMeter ou Apache JMeter HTTP(S) Test Script Recorder et chargez le script sur Test de charge Azure.
Si vous créez des partitions à l’aide d’une entrée utilisateur, votre application devient vulnérable aux attaques par déni de service (DoS). Par exemple, si vous créez des partitions à l’aide d’adresses IP clientes, votre application devient vulnérable aux attaques par déni de service qui utilisent l’usurpation d’adresse source IP. Pour plus d’informations, consultez Filtrage d’entrée réseau BCP 38 RFC 2827 : Contrer les attaques par déni de service (DoS) qui utilisent l’usurpation d’adresses IP Source.
Contenu connexe
- L’Intergiciel de limitation de débit par Maarten Balliauw offre une excellente présentation et une vue d’ensemble de la limitation de débit.
- Limite de débit d’un gestionnaire HTTP dans .NET