Поделиться через


ПО промежуточного слоя для ограничения скорости в ASP.NET Core

Арвин Кахбази, Маартен Бальяув, и Рик Андерсон

ПО промежуточного слоя Microsoft.AspNetCore.RateLimiting предоставляет функцию ограничения скорости. Приложения настраивают политики ограничения скорости, а затем присоединяют политики к конечным точкам. Приложения, использующие ограничение скорости, должны тщательно тестироваться и проверяться перед развертыванием. Дополнительные сведения см. в разделе "Тестирование конечных точек с ограничением скорости " в этой статье.

Для ознакомления с ограничением скорости см. раздел "Ограничение скорости в посредническом слое".

Почему используйте ограничение скорости

Ограничение скорости можно использовать для управления потоком входящих запросов к приложению. Основные причины реализации ограничения скорости:

  • предотвращение злоупотреблений: ограничение скорости помогает защитить приложение от злоупотреблений, ограничив количество запросов, которые пользователь или клиент могут выполнять в течение заданного периода времени. Это особенно важно для общедоступных API.
  • обеспечение справедливого использования. Путем установки ограничений все пользователи имеют справедливый доступ к ресурсам, предотвращая монополизацию системы пользователями.
  • защита ресурсов. Ограничение скорости помогает предотвратить перегрузку сервера, контролируя количество обрабатываемых запросов, что позволяет защитить внутренние ресурсы от перегрузки.
  • повышение безопасности. Это может снизить риск атак типа "отказ в обслуживании" (DoS), ограничив скорость обработки запросов, что затрудняет для злоумышленников наводнение системы.
  • Улучшение производительности: Управляя скоростью входящих запросов, можно поддерживать оптимальную производительность и скорость реагирования приложения, обеспечивая лучший пользовательский опыт.
  • Управление затратами. Для служб, которые несут затраты на основе использования, ограничение скорости может помочь контролировать и прогнозировать расходы, управляя объемом обработанных запросов.

Реализация ограничения скорости в приложении ASP.NET Core помогает поддерживать стабильность, безопасность и производительность, обеспечивая надежную и эффективную службу для всех пользователей.

Предотвращение атак DDoS

Хотя ограничение скорости может помочь снизить риск атак типа "отказ в обслуживании" (DoS), ограничив скорость обработки запросов, это не комплексное решение для атак типа "отказ в обслуживании" (DDoS). Атаки DDoS предполагают использование нескольких систем, которые перегружают приложение потоком запросов, что делает трудным управление одной только помощью ограничения скорости.

Для надежной защиты от атак DDoS рекомендуется использовать коммерческую службу защиты от атак DDoS. Эти службы предлагают дополнительные функции, такие как:

  • анализ трафика: непрерывный мониторинг и анализ входящего трафика для обнаружения и устранения атак DDoS в режиме реального времени.
  • масштабируемость. Возможность обработки крупномасштабных атак путем распределения трафика между несколькими серверами и центрами обработки данных.
  • автоматическое устранение рисков: автоматизированные механизмы реагирования для быстрого блокирования вредоносного трафика без вмешательства вручную.
  • глобальная сеть: глобальная сеть серверов для поглощения и устранения атак ближе к источнику.
  • Постоянные обновления: Коммерческие службы регулярно отслеживают и обновляют механизмы защиты для адаптации к новым и развивающимся угрозам.

При использовании облачной службы размещения защита от атак DDoS обычно доступна как часть решения для размещения, например брандмауэра веб-приложений Azure, AWS Shield или Google Cloud Armor. Выделенные средства защиты доступны как брандмауэры веб-приложений (WAF) или как часть решения CDN, например Cloudflare или Akamai Kona Site Defender

Реализация коммерческой службы защиты от атак DDoS в сочетании с ограничением скорости может обеспечить комплексную стратегию защиты, обеспечивая стабильность, безопасность и производительность приложения.

Использование промежуточного ПО для ограничения скорости

Ниже показано, как использовать ПО промежуточного слоя с ограничением скорости в приложении ASP.NET Core:

  1. Установите пакет Microsoft.AspNetCore.RateLimiting.:

Добавьте пакет Microsoft.AspNetCore.RateLimiting в проект с помощью диспетчера пакетов NuGet или следующей команды:

   dotnet add package Microsoft.AspNetCore.RateLimiting
  1. Настройка служб ограничения скорости.

В файле Program.cs настройте службы ограничения скорости, добавив соответствующие политики ограничения скорости. Политики можно определить как глобальные или именованные политики. В следующем примере допускается 10 запросов в минуту по пользователю (удостоверению) или по всему миру:

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)
            }));
});

Именованные политики должны применяться явно к страницам или конечным точкам. В следующем примере добавляется политика ограничения окон с именем "fixed", которую мы добавим в конечную точку позже:

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();

Глобальный предел применяется ко всем конечным точкам автоматически при настройке с помощью параметров . GlobalLimiter.

  1. Включите посредствующий слой ограничения скорости

    В файле Program.cs включите промежуточное ПО для ограничения скорости, вызвав UseRateLimiter:

app.UseRouting();

app.UseRateLimiter();

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
});

app.Run();

Применение политик ограничения скорости к конечным точкам или страницам

Применение ограничения скорости к конечным точкам WebAPI

Примените именованную политику к конечной точке или группе, например:


app.MapGet("/api/resource", () => "This endpoint is rate limited")
   .RequireRateLimiting("fixed"); // Apply specific policy to an endpoint

Применение ограничения скорости к контроллерам MVC

Примените настроенные политики ограничения скорости к определенным конечным точкам или глобально. Например, чтобы применить политику "фиксированной" ко всем конечным точкам контроллера:

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers().RequireRateLimiting("fixed");
});

Применение ограничения скорости к приложениям на стороне сервера Blazor

Чтобы задать ограничение скорости для всех маршрутизируемых Razor компонентов приложения, укажите RequireRateLimiting с именем политики ограничения скорости в вызове MapRazorComponents в файле Program. В следующем примере применяется политика ограничения скорости с именем "policy".

app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode()
    .RequireRateLimiting("policy");

Чтобы задать политику для одного маршрутизируемого компонента Razor или папки компонентов через файл _Imports.razor, атрибут [EnableRateLimiting] применяется с названием политики. В следующем примере применяется политика ограничения скорости с именем "override". Политика заменяет все политики, применяемые к конечной точке. Глобальный ограничитель по-прежнему работает на конечной точке с применением этого атрибута.

@page "/counter"
@using Microsoft.AspNetCore.RateLimiting
@attribute [EnableRateLimiting("override")]

<h1>Counter</h1>

Атрибут [EnableRateLimiting] применяется только к маршрутизируемому компоненту или папке компонентов через файл _Imports.razor, если RequireRateLimitingне вызывается MapRazorComponents.

Атрибут [DisableRateLimiting] используется для отключения ограничения скорости для маршрутизируемого компонента или папки компонентов с помощью файла _Imports.razor.

Алгоритмы ограничения скорости

Класс RateLimiterOptionsExtensions предоставляет следующие методы расширения для ограничения скорости:

Фиксированные, скользящие и маркерные ограничения ограничивают максимальное количество запросов за определенный период времени. Ограничение параллелизма ограничивает только количество одновременных запросов и не ограничивает количество запросов за период времени. При выборе ограничения следует учитывать стоимость конечной точки. Стоимость конечной точки включает ресурсы, используемые, например, время, доступ к данным, ЦП и ввода-вывода.

Ограничитель статичного окна

Метод AddFixedWindowLimiter использует фиксированное время для ограничения запросов. Когда истекает срок действия периода времени, запускается новое окно времени, а ограничение запроса сбрасывается.

Рассмотрим следующий код:

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();

Предыдущий код:

  • Вызывает AddRateLimiter для добавления службы ограничения частоты запросов в коллекцию сервисов.
  • AddFixedWindowLimiter используется для создания фиксированного лимитатора окна с именем политики "fixed" и устанавливает:
  • PermitLimit до 4 и время Window до 12. Допускается не более 4 запросов в каждом 12-секундном окне.
  • QueueProcessingOrder изменено на OldestFirst.
  • QueueLimit до 2 (задайте для этого значение 0, чтобы отключить механизм очереди).
  • Вызовы UseRateLimiter для включения ограничения скорости.

Приложения должны использовать конфигурацию для задания параметров ограничения. Следующий код обновляет предыдущий код, используя MyRateLimitOptions для настройки:

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 необходимо вызывать после UseRouting, когда используются ограничения скорости для конкретных API конечной точки. Например, если используется атрибут [EnableRateLimiting], UseRateLimiter необходимо вызвать после UseRouting. Когда вызываются только глобальные ограничения, UseRateLimiter можно вызвать раньше UseRouting.

Ограничитель скользящего окна

Алгоритм скользящего окна:

  • Аналогично фиксированному ограничению окна, но добавляет сегменты в окно. Окно перемещается на один сегмент через каждый интервал сегмента. Интервал сегмента — (время окна)/(сегменты на окно).
  • Ограничивает количество запросов для окна до permitLimit.
  • Каждое временное окно делится на n сегментов.
  • Запросы, взятые из истекшего временного сегмента на один шаг назад по времени (n сегменты перед текущим сегментом), добавляются в текущий сегмент. Мы называем наиболее просроченный временной сегмент на одно окно назад просроченным сегментом.

Рассмотрим следующую таблицу с скользящим ограничением окна с 30-секундным окном, тремя сегментами в окне и ограничением в 100 запросов:

  • Верхняя строка и первый столбец показывают сегмент времени.
  • Вторая строка показывает оставшиеся доступные запросы. Остальные запросы вычисляются как доступные запросы минус обработанные запросы, а также переработанные запросы.
  • Запросы в каждом случае перемещаются по синей диагональной линии.
  • По истечении 30 раз запрос, полученный из сегмента времени с истекшим сроком действия, добавляется обратно в предел запроса, как показано в красных строках.

Таблица с запросами, ограничениями и переработанными слотами

В следующей таблице показаны данные в предыдущем графе в другом формате. В столбце "Доступно" отображаются запросы, доступные из предыдущего сегмента (перенос из предыдущей строки). В первой строке отображается 100 доступных запросов, так как предыдущий сегмент отсутствует.

Время В наличии Забранный Переработано из истекших сроком годности материалов Переносить
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
шестьдесят 50 35 30 45

В следующем коде используется ограничение скорости скользящего окна:

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();

Ограничитель с помощью токенов из ёмкости

Ограничитель ёмкости маркеров аналогичен ограничителю скользящего окна, но вместо возврата запросов, взятых из истёкшего сегмента, в каждый период пополнения добавляется фиксированное число маркеров. Токены, добавляемые в каждом сегменте, не могут увеличить количество доступных токенов больше, чем лимит контейнера токенов. В следующей таблице показан ограничивающий контейнер маркеров с ограничением в 100 маркеров и периодом пополнения в 10 секунд.

Время В наличии Забранный Добавлено Переносить
0 100 20 0 80
10 80 10 20 девяносто
20 90 5 15 100
30 100 30 20 девяносто
40 девяносто 6 16 100
50 100 40 20 80
60 80 50 20 50

В следующем коде используется ограничитель ведра токенов.

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();

Если AutoReplenishment установлен на true, внутренний таймер обновляет токены каждые ReplenishmentPeriod; при установке false приложение должно вызывать TryReplenish ограничитель.

Ограничение параллелизма

Ограничение параллелизма ограничивает количество одновременных запросов. Каждый запрос уменьшает ограничение параллелизма на один. По завершении запроса ограничение увеличивается на один. В отличие от других ограничений запросов, ограничивающих общее количество запросов в течение указанного периода, ограничение параллелизма ограничивает только количество одновременных запросов и не ограничивает количество запросов за определенный период времени.

В следующем коде используется ограничение параллелизма:

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();

Ограничение скорости разделов

Разделы ограничения скорости делят трафик на отдельные "сегменты", каждый из которых получает свои собственные счетчики ограничения скорости. Это позволяет более детально контролировать, чем один глобальный счетчик. Разделы "сегменты" определяются различными ключами (например, идентификатором пользователя, IP-адресом или ключом API).

Преимущества секционирования

  • справедливость: один пользователь не может использовать весь предел скорости для всех
  • Гранулярность: различные ограничения для разных пользователей, ресурсов
  • безопасности: улучшенная защита от целевых злоупотреблений
  • Многоуровневая услуга: Поддержка уровней услуги с различными ограничениями

Ограничение секционированных скоростей позволяет точно контролировать управление трафиком API, обеспечивая справедливое распределение ресурсов.

По 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)
        }));

Пользователь Identity

options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
    RateLimitPartition.GetFixedWindowLimiter(
        partitionKey: httpContext.User.Identity?.Name ?? "anonymous",
        factory: _ => new FixedWindowRateLimiterOptions
        {
            PermitLimit = 100,
            Window = TimeSpan.FromMinutes(1)
        }));

По ключу 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)
            }),
    };
});

По пути к конечной точке

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)
        });
});

Создайте цепные ограничители

CreateChained API позволяет передавать несколькоPartitionedRateLimiter, которые объединяются в одинPartitionedRateLimiter. Объединенный ограничитель выполняет все входные ограничения в последовательности.

В приведенном ниже коде используется 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();

Дополнительные сведения см. в исходном коде CreateChained

Выбор того, что происходит при ограничении скорости запроса

В простых случаях можно просто задать код состояния:

builder.Services.AddRateLimiter(options =>
{
    // Set a custom status code for rejections
    options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;

    // Rate limiter configuration...
});

Наиболее распространенным подходом является регистрация обратного вызова OnRejected при настройке ограничения скорости:

builder.Services.AddRateLimiter(options =>
{
    // Rate limiter configuration...

    options.OnRejected = async (context, cancellationToken) =>
    {
        // Custom rejection handling logic
        context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;
        context.HttpContext.Response.Headers["Retry-After"] = "60";

        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);
    };
});

Другим вариантом является очередь запроса:

Очередь запросов

Если включена очередь, когда запрос превышает ограничение скорости, он помещается в очередь, где ожидает, пока разрешение не будет получено или не произойдет тайм-аут. Запросы обрабатываются в соответствии с настраиваемым порядком очереди.

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
    });
});

Атрибуты EnableRateLimiting и DisableRateLimiting

Атрибуты [EnableRateLimiting] и [DisableRateLimiting] могут применяться к контроллеру, методу действия или странице Razor. Для Razor Pages атрибут должен применяться к Razor странице, а не к обработчикам страниц. Например, [EnableRateLimiting] нельзя применять к OnGet, OnPost или к любому другому обработчику страниц.

Атрибут [DisableRateLimiting]отключает ограничение скорости для контроллера, метода действия или Razor страницы, независимо от того, применяются ли именованные или глобальные ограничители. Например, рассмотрим следующий код, который вызывает RequireRateLimiting, чтобы применить fixedPolicy лимитирование частоты запросов ко всем конечным точкам контроллера:

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();

Во следующем коде [DisableRateLimiting] отключает ограничение скорости и переопределяет [EnableRateLimiting("fixed")], примененные к Home2Controller и app.MapDefaultControllerRoute().RequireRateLimiting(fixedPolicy), вызываемым в 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 });
    }
}

В приведенном выше коде [EnableRateLimiting("sliding")]не применяется к методу Privacy действия, потому что вызывается Program.csapp.MapDefaultControllerRoute().RequireRateLimiting(fixedPolicy).

Рассмотрим следующий код, в котором не вызывается RequireRateLimiting на MapRazorPages или 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();

Рассмотрим следующий контроллер:

[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 });
    }
}

На предыдущем контроллере:

  • Ограничение "fixed" скорости политики применяется ко всем методам действий, которые не имеют EnableRateLimiting и DisableRateLimiting атрибуты.
  • Ограничение "sliding" скорости политики применяется к Privacy действию.
  • Ограничение скорости отключено в методе NoLimit действия.

Ограничение частоты метрик

Ограничивающее скорость посредническое программное обеспечение предоставляет встроенные метрики и возможности мониторинга, помогающие понять, как ограничения скорости влияют на производительность приложения и опыт пользователя. Список метрик см. в Microsoft.AspNetCore.RateLimiting.

Тестирование конечных точек с ограничением скорости

Прежде чем развертывать приложение с использованием ограничения скорости в рабочей среде, протестируйте приложение для проверки ограничений скорости и используемых параметров. Например, создайте сценарий JMeter с помощью таких средств, как BlazeMeter или Apache JMeter HTTP(S) Test Script Recorder, и загрузите скрипт в Azure Load Testing.

Создание секций с вводом пользователем делает приложение уязвимым к атакам типа "отказ в обслуживании " (DoS). Например, разбиение по IP-адресам клиента делает приложение уязвимым для атак типа "отказ в обслуживании", которые используют подделку IP-адресов. Дополнительные сведения см. в статье BCP 38 RFC 2827 Network Ingress Filtering: защита от атак типа "отказ в обслуживании", использующих спуфинг IP-адресов.

Дополнительные ресурсы