Добавил RateLimiter
All checks were successful
Create and publish a Docker image / Publish image (push) Successful in 11m22s
All checks were successful
Create and publish a Docker image / Publish image (push) Successful in 11m22s
This commit is contained in:
@@ -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)
|
||||||
{
|
{
|
||||||
|
@@ -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();
|
Reference in New Issue
Block a user