/
ArtSerg
/
IntegrationProject
Обзор
Документация
Войти
/
ArtSerg
/
IntegrationProject
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
main
Integration1/Services/RateLimitMiddleware.cs
116 строк
4 KB
artS3rg
Added idempotency header filter + swagger integration + middleware improvements
20 ноя 2025, 05:16
20 ноя 2025, 05:16
e27f9a4
Код
Авторство
О чём код?
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Options; using System.Collections.Concurrent; namespace Integration1.Services { //public class RateLimitMiddleware //{ // private readonly RequestDelegate _next; // private static readonly ConcurrentDictionary<string, (int Count, DateTime WindowStart)> _clients = new(); // private const int LIMIT = 10; // private const int WINDOW = 60; // public RateLimitMiddleware(RequestDelegate next) => _next = next; // public async Task InvokeAsync(HttpContext context) // { // var key = context.Connection.RemoteIpAddress?.ToString() ?? "unknown"; // var now = DateTime.UtcNow; // var entry = _clients.GetOrAdd(key, _ => (0, now)); // if ((now - entry.WindowStart).TotalSeconds > WINDOW) // entry = (0, now); // if (entry.Count >= LIMIT) // { // context.Response.Headers["Retry-After"] = "60"; // context.Response.StatusCode = 429; // await context.Response.WriteAsync("Too many requests"); // return; // } // _clients[key] = (entry.Count + 1, entry.WindowStart); // context.Response.Headers["X-Limit-Remaining"] = (LIMIT - entry.Count - 1).ToString(); // await _next(context); // } //} public class RateLimitOptions { public int RequestsPerMinute { get; set; } = 60; } public class RateLimitMiddleware { private readonly RequestDelegate _next; private readonly IMemoryCache _cache; private readonly int _limit; public RateLimitMiddleware(RequestDelegate next, IMemoryCache cache, IOptions<RateLimitOptions> options) { _next = next; _cache = cache; _limit = options.Value.RequestsPerMinute <= 0 ? 60 : options.Value.RequestsPerMinute; } public async Task InvokeAsync(HttpContext context) { var key = GetClientKey(context); var now = DateTime.UtcNow; var entry = _cache.GetOrCreate(key, e => { e.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(1); return new RateEntry { Count = 0, WindowStart = now }; }); var secondsLeft = (int)Math.Ceiling((entry.WindowStart.AddMinutes(1) - now).TotalSeconds); var remaining = Math.Max(0, _limit - entry.Count); if (entry.Count >= _limit) { context.Response.StatusCode = StatusCodes.Status429TooManyRequests; context.Response.Headers["X-Limit-Remaining"] = "0"; context.Response.Headers["Retry-After"] = secondsLeft.ToString(); await context.Response.WriteAsync($"Rate limit exceeded. Retry after {secondsLeft} seconds."); return; } entry.Count++; _cache.Set(key, entry, entry.WindowStart.AddMinutes(1) - now); context.Response.OnStarting(() => { var currentEntry = _cache.Get<RateEntry>(key) ?? entry; var rem = Math.Max(0, _limit - currentEntry.Count); context.Response.Headers["X-Limit-Remaining"] = rem.ToString(); // If limit not reached, still set Retry-After as seconds remaining in window var retryAfter = Math.Max(0, (int)Math.Ceiling((currentEntry.WindowStart.AddMinutes(1) - DateTime.UtcNow).TotalSeconds)); context.Response.Headers["Retry-After"] = retryAfter.ToString(); return Task.CompletedTask; }); await _next(context); } private string GetClientKey(HttpContext ctx) { if (ctx.User?.Identity?.IsAuthenticated == true) { var id = ctx.User.FindFirst("UserId")?.Value; if (!string.IsNullOrEmpty(id)) return $"rl_user_{id}"; } var ip = ctx.Connection.RemoteIpAddress?.ToString() ?? "unknown"; return $"rl_ip_{ip}"; } private class RateEntry { public int Count { get; set; } public DateTime WindowStart { get; set; } } } }