Добавил RateLimiter
All checks were successful
Create and publish a Docker image / Publish image (push) Successful in 11m22s

This commit is contained in:
2025-09-06 22:07:37 +03:00
parent 5f52e16dbd
commit ea254c1c02
2 changed files with 39 additions and 6 deletions

View File

@@ -1,10 +1,12 @@
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.RateLimiting;
using SfeduSchedule.Services; using SfeduSchedule.Services;
namespace SfeduSchedule.Controllers namespace SfeduSchedule.Controllers
{ {
[ApiController] [ApiController]
[Route("api/[controller]")] [Route("api/[controller]")]
[EnableRateLimiting("throttle")]
public class ScheduleController(ModeusService modeusService) : ControllerBase public class ScheduleController(ModeusService modeusService) : ControllerBase
{ {
/// <summary> /// <summary>
@@ -13,6 +15,7 @@ namespace SfeduSchedule.Controllers
/// <param name="attendeePersonId">Список GUID пользователей, для которых запрашивается расписание.</param> /// <param name="attendeePersonId">Список GUID пользователей, для которых запрашивается расписание.</param>
/// <returns>Список событий расписания.</returns> /// <returns>Список событий расписания.</returns>
/// <response code="200">Возвращает расписание</response> /// <response code="200">Возвращает расписание</response>
/// <response code="429">Слишком много запросов</response>
[HttpGet] [HttpGet]
[Route("test")] [Route("test")]
public async Task<IActionResult> Get([FromQuery] List<Guid> attendeePersonId, [FromQuery] DateTime? startDate, [FromQuery] DateTime? endDate) public async Task<IActionResult> Get([FromQuery] List<Guid> attendeePersonId, [FromQuery] DateTime? startDate, [FromQuery] DateTime? endDate)
@@ -31,6 +34,7 @@ namespace SfeduSchedule.Controllers
/// <param name="request">Объект запроса, содержащий параметры фильтрации расписания.</param> /// <param name="request">Объект запроса, содержащий параметры фильтрации расписания.</param>
/// <returns>Список событий расписания.</returns> /// <returns>Список событий расписания.</returns>
/// <response code="200">Возвращает расписание</response> /// <response code="200">Возвращает расписание</response>
/// <response code="429">Слишком много запросов</response>
[HttpPost] [HttpPost]
public async Task<IActionResult> Post([FromBody] ModeusScheduleRequest request) public async Task<IActionResult> Post([FromBody] ModeusScheduleRequest request)
{ {

View File

@@ -1,3 +1,4 @@
using System.Threading.RateLimiting;
using Microsoft.Identity.Web; using Microsoft.Identity.Web;
using Quartz; using Quartz;
using SfeduSchedule; using SfeduSchedule;
@@ -13,13 +14,13 @@ string? tgChatId = configuration["TG_CHAT_ID"];
string? tgToken = configuration["TG_TOKEN"]; string? tgToken = configuration["TG_TOKEN"];
string updateJwtCron = configuration["UPDATE_JWT_CRON"] ?? "0 4 * ? * *"; string updateJwtCron = configuration["UPDATE_JWT_CRON"] ?? "0 4 * ? * *";
// создать папку data если не существует // создать папку data если не существует
var dataDirectory = Path.Combine(AppContext.BaseDirectory, "data"); var dataDirectory = Path.Combine(AppContext.BaseDirectory, "data");
if (!Directory.Exists(dataDirectory)) if (!Directory.Exists(dataDirectory))
{ {
Directory.CreateDirectory(dataDirectory); Directory.CreateDirectory(dataDirectory);
} }
GlobalVariables.JwtFilePath = Path.Combine(dataDirectory, "jwt.txt"); GlobalVariables.JwtFilePath = Path.Combine(dataDirectory, "jwt.txt");
@@ -35,7 +36,8 @@ if (!string.IsNullOrEmpty(tgChatId) && !string.IsNullOrEmpty(tgToken))
options.LogLevel = new Dictionary<string, LogLevel> options.LogLevel = new Dictionary<string, LogLevel>
{ {
{ "Default", LogLevel.Error }, { "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); 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<ILogger<Program>>();
reqLogger.LogWarning("Rate limit exceeded for IP: {IpAddress}",
context.HttpContext.Connection.RemoteIpAddress);
};
});
var app = builder.Build(); var app = builder.Build();
app.UseForwardedHeaders();
var logger = app.Services.GetRequiredService<ILogger<Program>>(); var logger = app.Services.GetRequiredService<ILogger<Program>>();
app.UseForwardedHeaders();
if (string.IsNullOrEmpty(preinstalledJwtToken)) if (string.IsNullOrEmpty(preinstalledJwtToken))
{ {
var schedulerFactory = app.Services.GetRequiredService<ISchedulerFactory>(); var schedulerFactory = app.Services.GetRequiredService<ISchedulerFactory>();
@@ -103,7 +130,8 @@ if (string.IsNullOrEmpty(preinstalledJwtToken))
} }
else else
{ {
logger.LogInformation("Файл jwt.txt не содержит дату истечения или она некорректна, выполняем обновление токена"); logger.LogInformation(
"Файл jwt.txt не содержит дату истечения или она некорректна, выполняем обновление токена");
await scheduler.TriggerJob(jobKey); await scheduler.TriggerJob(jobKey);
} }
} }
@@ -131,5 +159,6 @@ app.MapGet("/", async context =>
app.MapControllers(); app.MapControllers();
app.Run(); app.UseRateLimiter();
app.Run();