Rate Limiting — .NET 7+ built-in, Fixed/Sliding window, per user

~20 daq · .NET Backend · Ilg'or ASP.NET Core

Muammodan boshlaylik

  • Bot login endpoint iga soniyasiga 500 ta parol yuboryapti (brute force)
  • Raqobatchi skript butun katalogni har daqiqada yuklab olyapti
  • Bitta mijozning xatoli while (true) sikli API ni band qilib, boshqalar uchun sekinlashtirdi
  • SMS yuborish endpoint i — har bir so'rov pul turadi, kimdir uni 10 000 marta chaqirdi

API ga kim qancha so'rov yuborishi mumkinligi cheklanmagan.


Hayotiy o'xshatma

Bankomat. Kartadan kuniga ko'pi bilan 5 million so'm yechish mumkin, PIN kodni 3 marta noto'g'ri kiritsangiz — karta bloklanadi. Bu cheklovlar oddiy mijozga xalaqit bermaydi, lekin o'g'ri bitta urinishda hamma pulni yecha olmaydi va PIN ni tanlab topa olmaydi.


O'rnatilgan middleware (.NET 7+)

using System.Threading.RateLimiting;
using Microsoft.AspNetCore.RateLimiting;

builder.Services.AddRateLimiter(o =>
{
    o.RejectionStatusCode = StatusCodes.Status429TooManyRequests;

    o.AddFixedWindowLimiter("umumiy", opt =>
    {
        opt.PermitLimit = 100;                    // 100 ta so'rov
        opt.Window = TimeSpan.FromMinutes(1);     // har daqiqada
        opt.QueueLimit = 0;                       // navbatsiz — darhol rad
    });
});

var app = builder.Build();

app.UseRouting();
app.UseRateLimiter();
[EnableRateLimiting("umumiy")]
[ApiController]
[Route("api/mahsulotlar")]
public class MahsulotlarController : ControllerBase { ... }

Limit oshsa — 429 Too Many Requests.


Algoritmlar

Algoritm Qanday ishlaydi Qachon
Fixed window Har daqiqa — 100 ta, daqiqa boshida nolga Oddiy, ko'p holatlar uchun
Sliding window Oyna segmentlarga bo'linib siljiydi Oyna chegarasidagi "ikki baravar" portlashni kamaytirish
Token bucket Chelakka muntazam token qo'shiladi, har so'rov bittasini oladi Qisqa portlashlarga ruxsat, o'rtacha tezlik cheklangan
Concurrency Bir vaqtda ko'pi bilan N ta so'rov Og'ir operatsiyalar (eksport, hisobot)
o.AddSlidingWindowLimiter("sliding", opt =>
{
    opt.PermitLimit = 100;
    opt.Window = TimeSpan.FromMinutes(1);
    opt.SegmentsPerWindow = 6;                    // 10 soniyalik segmentlar
});

o.AddTokenBucketLimiter("bucket", opt =>
{
    opt.TokenLimit = 20;                          // birdaniga 20 tagacha
    opt.TokensPerPeriod = 5;                      // har 10 soniyada +5
    opt.ReplenishmentPeriod = TimeSpan.FromSeconds(10);
});

o.AddConcurrencyLimiter("eksport", opt =>
{
    opt.PermitLimit = 2;
    opt.QueueLimit = 5;
    opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
});

Har bir mijoz uchun alohida — partitioned

Yuqoridagi limitlar — butun ilova uchun umumiy: bitta bot 100 ta so'rovni ishlatib qo'ysa, hamma 429 oladi. Kerak — har bir foydalanuvchi yoki IP uchun alohida hisob:

o.AddPolicy("foydalanuvchi", ctx =>
{
    var userId = ctx.User.FindFirstValue(ClaimTypes.NameIdentifier);

    if (userId is not null)
        return RateLimitPartition.GetTokenBucketLimiter(
quot;u:{userId}", _ => new TokenBucketRateLimiterOptions { TokenLimit = 60, TokensPerPeriod = 60, ReplenishmentPeriod = TimeSpan.FromMinutes(1) }); var ip = ctx.Connection.RemoteIpAddress?.ToString() ?? "nomalum"; return RateLimitPartition.GetFixedWindowLimiter(
quot;ip:{ip}", _ => new FixedWindowRateLimiterOptions { PermitLimit = 20, Window = TimeSpan.FromMinutes(1) }); });

Kirgan foydalanuvchi — ID bo'yicha (60/daqiqa), mehmon — IP bo'yicha (20/daqiqa).

Login uchun qattiq limit

o.AddPolicy("login", ctx =>
    RateLimitPartition.GetFixedWindowLimiter(
        ctx.Connection.RemoteIpAddress?.ToString() ?? "nomalum",
        _ => new FixedWindowRateLimiterOptions { PermitLimit = 5, Window = TimeSpan.FromMinutes(1) }));
[HttpPost("login")]
[AllowAnonymous]
[EnableRateLimiting("login")]
public async Task<IActionResult> Login(LoginDto dto, CancellationToken ct) { ... }

Global limiter va javob

o.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(ctx =>
    RateLimitPartition.GetFixedWindowLimiter(
        ctx.Connection.RemoteIpAddress?.ToString() ?? "nomalum",
        _ => new FixedWindowRateLimiterOptions { PermitLimit = 300, Window = TimeSpan.FromMinutes(1) }));

o.OnRejected = async (ctx, ct) =>
{
    if (ctx.Lease.TryGetMetadata(MetadataName.RetryAfter, out var kutish))
        ctx.HttpContext.Response.Headers.RetryAfter = ((int)kutish.TotalSeconds).ToString();

    await ctx.HttpContext.Response.WriteAsJsonAsync(
        new { xato = "Juda ko'p so'rov. Birozdan keyin urinib ko'ring." }, ct);
};

Retry-After sarlavhasi — mijozga qancha kutish kerakligini aytadi.

Health check kabi endpoint lar uchun — [DisableRateLimiting].


Muhim nuanslar

[!WARNING] Reverse proxy (Nginx, Cloudflare, load balancer) ortida RemoteIpAddress — proxy ning IP si: hamma foydalanuvchi bitta "IP" bo'lib qoladi va birgalikda limitga tushadi. UseForwardedHeaders ni sozlang va faqat ishonchli proxy larni (KnownProxies/KnownNetworks) ko'rsating — aks holda hujumchi X-Forwarded-For ni soxtalashtirib limitni aylanib o'tadi.

[!INFO] O'rnatilgan rate limiter holatni server xotirasida saqlaydi. 3 ta server nusxasi bo'lsa — har biri alohida hisoblaydi (amalda limit 3 baravar). Aniq taqsimlangan limit kerak bo'lsa — Redis asosidagi yechim yoki API gateway / Cloudflare darajasida cheklash.


Tez-tez uchraydigan xatolar

Faqat global umumiy limit — bitta yomon mijoz hammani bloklaydi; partition ishlating.

UseRateLimiter endpoint atributlari bilan, lekin UseRouting dan oldin — endpoint metadata si hali ma'lum emas, atributlar ishlamaydi.

Login ga limit yo'q — brute force uchun ochiq eshik (58-dars).


Xulosa

Rate limiting — kim qancha so'rov yuborishini cheklash: brute force, scraping, xatoli mijozlar va qimmat amallarga qarshi. AddRateLimiter + UseRateLimiter, [EnableRateLimiting("nom")], rad etilsa — 429.

Algoritmlar: fixed window, sliding window, token bucket, concurrency. Partitioned limiter — har bir foydalanuvchi/IP uchun alohida; login uchun qattiq limit. OnRejected da Retry-After. Proxy ortida — forwarded headers; bir nechta serverda — taqsimlangan yechim.


Keyingi bo'lim

71-dars: RESTful API best practices — nomlash, versiyalash, javob formati