From ea254c1c0281cab3170f49d654917f48b34a9501 Mon Sep 17 00:00:00 2001 From: Sergey Karmanov Date: Sat, 6 Sep 2025 22:07:37 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20Ra?= =?UTF-8?q?teLimiter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/ScheduleController.cs | 4 ++ SfeduSchedule/Program.cs | 41 ++++++++++++++++--- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/SfeduSchedule/Controllers/ScheduleController.cs b/SfeduSchedule/Controllers/ScheduleController.cs index 8e54378..7c31b96 100644 --- a/SfeduSchedule/Controllers/ScheduleController.cs +++ b/SfeduSchedule/Controllers/ScheduleController.cs @@ -1,10 +1,12 @@ using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.RateLimiting; using SfeduSchedule.Services; namespace SfeduSchedule.Controllers { [ApiController] [Route("api/[controller]")] + [EnableRateLimiting("throttle")] public class ScheduleController(ModeusService modeusService) : ControllerBase { /// @@ -13,6 +15,7 @@ namespace SfeduSchedule.Controllers /// Список GUID пользователей, для которых запрашивается расписание. /// Список событий расписания. /// Возвращает расписание + /// Слишком много запросов [HttpGet] [Route("test")] public async Task Get([FromQuery] List attendeePersonId, [FromQuery] DateTime? startDate, [FromQuery] DateTime? endDate) @@ -31,6 +34,7 @@ namespace SfeduSchedule.Controllers /// Объект запроса, содержащий параметры фильтрации расписания. /// Список событий расписания. /// Возвращает расписание + /// Слишком много запросов [HttpPost] public async Task Post([FromBody] ModeusScheduleRequest request) { diff --git a/SfeduSchedule/Program.cs b/SfeduSchedule/Program.cs index 4bf6b72..cb3433a 100644 --- a/SfeduSchedule/Program.cs +++ b/SfeduSchedule/Program.cs @@ -1,3 +1,4 @@ +using System.Threading.RateLimiting; using Microsoft.Identity.Web; using Quartz; using SfeduSchedule; @@ -13,13 +14,13 @@ string? tgChatId = configuration["TG_CHAT_ID"]; string? tgToken = configuration["TG_TOKEN"]; string updateJwtCron = configuration["UPDATE_JWT_CRON"] ?? "0 4 * ? * *"; - // создать папку data если не существует var dataDirectory = Path.Combine(AppContext.BaseDirectory, "data"); if (!Directory.Exists(dataDirectory)) { Directory.CreateDirectory(dataDirectory); } + GlobalVariables.JwtFilePath = Path.Combine(dataDirectory, "jwt.txt"); @@ -35,7 +36,8 @@ if (!string.IsNullOrEmpty(tgChatId) && !string.IsNullOrEmpty(tgToken)) options.LogLevel = new Dictionary { { "Default", LogLevel.Error }, - { "SfeduSchedule.Jobs.UpdateJwtJob", LogLevel.Information } + { "SfeduSchedule.Jobs.UpdateJwtJob", LogLevel.Information }, + { "Program", LogLevel.Information } }; }); @@ -70,12 +72,37 @@ builder.Services.AddSwaggerGen(options => options.IncludeXmlComments(xmlPath); }); +builder.Services.AddRateLimiter(options => +{ + options.AddPolicy("throttle", httpContext => + RateLimitPartition.GetFixedWindowLimiter( + partitionKey: httpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown", + factory: _ => new FixedWindowRateLimiterOptions + { + PermitLimit = 20, + Window = TimeSpan.FromSeconds(10) + })); + + options.OnRejected = async (context, cancellationToken) => + { + 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); + + var reqLogger = context.HttpContext.RequestServices.GetRequiredService>(); + reqLogger.LogWarning("Rate limit exceeded for IP: {IpAddress}", + context.HttpContext.Connection.RemoteIpAddress); + }; +}); + var app = builder.Build(); -app.UseForwardedHeaders(); - var logger = app.Services.GetRequiredService>(); +app.UseForwardedHeaders(); + if (string.IsNullOrEmpty(preinstalledJwtToken)) { var schedulerFactory = app.Services.GetRequiredService(); @@ -103,7 +130,8 @@ if (string.IsNullOrEmpty(preinstalledJwtToken)) } else { - logger.LogInformation("Файл jwt.txt не содержит дату истечения или она некорректна, выполняем обновление токена"); + logger.LogInformation( + "Файл jwt.txt не содержит дату истечения или она некорректна, выполняем обновление токена"); await scheduler.TriggerJob(jobKey); } } @@ -131,5 +159,6 @@ app.MapGet("/", async context => app.MapControllers(); -app.Run(); +app.UseRateLimiter(); +app.Run(); \ No newline at end of file