Rate Limiting — .NET 7+ built-in, Fixed/Sliding window, per user
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.UseForwardedHeadersni sozlang va faqat ishonchli proxy larni (KnownProxies/KnownNetworks) ko'rsating — aks holda hujumchiX-Forwarded-Forni 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